models with order (+admin editing)

  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
117
118
119
120
121
# -------- 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 '<a href="%s">up</a> | <a href="%s">down</a>' % (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/(?P<direction>up|down)/(?P<model_type_id>\d+)/(?P<model_id>\d+)/$', 'your.views.admin_move_ordered_model', name="admin-move"),
    # ...
)

More like this

  1. Ordered items in the database - alternative by Leonidas 6 years ago
  2. Complex Formsets by smagala 4 years, 5 months ago
  3. Additional Change List Columns by sansmojo 5 years, 11 months ago
  4. Drag and drop ordering of admin list elements using jQuery UI by johj 3 years ago
  5. Django admin inline ordering - javascript only implementation by ojhilt 6 months, 2 weeks ago

Comments

andybak (on September 1, 2008):

Some very nice ideas here. It would be nice to combine it with http://www.djangosnippets.org/snippets/568/ and have the reordering via Ajax.

Even nicer would be if I could figure out how to get this working for inline forms... Hmmmmmm.

#

mdgart (on June 12, 2009):

I'm sorry for the silly question, but i'm a beginner: Where i should insert the metaclass code? And the view code is only for the admin, right? Again, sorry for the stupid question, but i wish to have this functionality on a website that i'm developing. Thanks

#

areich (on July 9, 2009):

Thanks for this! I went looking for how to order user data in ModelAdmin and was led here. I agree w/andybak, this would be nice to be ajax-y. It would be really nice w/some up/down triangles too.

It looks like the metaclass needs to stay w/the models, unless you splay it out to another file, in which case you would need to also import it. Yes, the view is only for the admin. good luck w/your django site, mdgart!

#

JeffreyATW (on August 11, 2009):

Thanks for the snippet! Found a bug in move_up() though...

With the code as is, whenever you try to move something up, it goes to the top of the list. This is because you're querying the database for the first element in the collection of elements whose order is less than the current element. Since we aren't ordering, the first element will always have order 0.

Change the lower_model assignment to this:

lower_model = ModelClass.objects.filter(order__lt=higher_model.order).order_by('-order')[0]

(note the order_by) and it should work properly.

#

nemesis (on January 28, 2010):

Very useful, thanks.

#

slafs (on March 9, 2010):

that's a nice one! thanks!

#

(Forgotten your password?)