Login

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

Author:
spoof
Posted:
June 1, 2010
Language:
Python
Version:
1.2
Score:
1 (after 1 ratings)

Makes models orderable on the change list page of the admin using drag and drop with jQuery UI (via sortable()). So you can order your objects in more easy way.

Inspired by snippets #1053 and #998

First, ordering field to your model (default called 'order). You can specify other name for this field, but you should add 'order_field' attr to model (i.e order_field = 'your_new_order_field_name')

Also, snippet adds 'order_link' field to admin's changelist and hides it by javascript.

  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. Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months 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.

#

Please login first before commenting.