# -------- the usage (the good stuff first) # models.py class Category(OrderedModel): name = models.CharField(max_length=50) def __unicode__(self): return self.name # admin.py class CategoryAdmin(admin.ModelAdmin): list_display = ('name', 'order', 'order_link') # notice the order_link !! admin.site.register(Category, CategoryAdmin) # -------- the metaclass from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse from django.db import models class OrderedModel(models.Model): order = models.PositiveIntegerField(editable=False) def save(self): if not self.id: try: self.order = self.__class__.objects.all().order_by("-order")[0].order + 1 except IndexError: self.order = 0 super(OrderedModel, self).save() def order_link(self): model_type_id = ContentType.objects.get_for_model(self.__class__).id model_id = self.id kwargs = {"direction": "up", "model_type_id": model_type_id, "model_id": model_id} url_up = reverse("admin-move", kwargs=kwargs) kwargs["direction"] = "down" url_down = reverse("admin-move", kwargs=kwargs) return 'up | down' % (url_up, url_down) order_link.allow_tags = True order_link.short_description = 'Move' order_link.admin_order_field = 'order' @staticmethod def move_down(model_type_id, model_id): try: ModelClass = ContentType.objects.get(id=model_type_id).model_class() lower_model = ModelClass.objects.get(id=model_id) higher_model = ModelClass.objects.filter(order__gt=lower_model.order)[0] lower_model.order, higher_model.order = higher_model.order, lower_model.order higher_model.save() lower_model.save() except IndexError: pass except ModelClass.DoesNotExist: pass @staticmethod def move_up(model_type_id, model_id): try: ModelClass = ContentType.objects.get(id=model_type_id).model_class() higher_model = ModelClass.objects.get(id=model_id) lower_model = ModelClass.objects.filter(order__lt=higher_model.order)[0] lower_model.order, higher_model.order = higher_model.order, lower_model.order higher_model.save() lower_model.save() except IndexError: pass except ModelClass.DoesNotExist: pass class Meta: ordering = ["order"] abstract = True # -------- the view from django.contrib.admin.views.decorators import staff_member_required from django.contrib.contenttypes.models import ContentType from django.http import HttpResponseRedirect from models import OrderedModel from django.db import transaction @staff_member_required @transaction.commit_on_success def admin_move_ordered_model(request, direction, model_type_id, model_id): if direction == "up": OrderedModel.move_up(model_type_id, model_id) else: OrderedModel.move_down(model_type_id, model_id) ModelClass = ContentType.objects.get(id=model_type_id).model_class() app_label = ModelClass._meta.app_label model_name = ModelClass.__name__.lower() url = "/admin/%s/%s/" % (app_label, model_name) return HttpResponseRedirect(url) # -------- the url config urlpatterns = patterns('', # ... url(r'^admin/orderedmove/(?Pup|down)/(?P\d+)/(?P\d+)/$', 'your.views.admin_move_ordered_model', name="admin-move"), # ... )