Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 1814f6fee0deb7dd3c9908714612914559b3e5d8 0 parents
@bfirsh bfirsh authored
2  .gitignore
@@ -0,0 +1,2 @@
+/build
+*.pyc
24 LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2009, Ben Firshman
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * The names of its contributors may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 README.markdown
@@ -0,0 +1,41 @@
+django-ordered-model
+====================
+
+django-ordered-model allows models to be ordered and provides a simple admin
+interface for reordering them.
+
+Requires:
+
+ * Django 1.1
+
+Installation
+------------
+
+ $ python setup.py install
+
+Usage
+-----
+
+Inherit your model from `OrderedModel` to make it ordered:
+
+ from django.db import models
+ from ordered_model.models import OrderedModel
+
+ class Item(OrderedModel):
+ name = models.CharField(max_length=100)
+
+Model instances now have `move_up()` and `move_down()` methods to move them
+relative to each other.
+
+To add arrows in the admin change list page to do reordering, you can use the
+`OrderedModelAdmin` and the `move_up_down_links` field:
+
+ from django.contrib import admin
+ from ordered_model.admin import OrderedModelAdmin
+ from models import Item
+
+ class ItemAdmin(OrderedModelAdmin):
+ list_display = ('name', 'move_up_down_links')
+
+ admin.site.register(Item, ItemAdmin)
+
1  ordered_model/__init__.py
@@ -0,0 +1 @@
+# Based on http://www.djangosnippets.org/snippets/998/ and http://www.djangosnippets.org/snippets/259/
43 ordered_model/admin.py
@@ -0,0 +1,43 @@
+from django.conf import settings
+from django.contrib import admin
+from django.contrib.admin.util import unquote
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+from django.shortcuts import get_object_or_404
+from django.utils.functional import update_wrapper
+
+class OrderedModelAdmin(admin.ModelAdmin):
+ def get_urls(self):
+ from django.conf.urls.defaults import patterns, url
+ def wrap(view):
+ def wrapper(*args, **kwargs):
+ return self.admin_site.admin_view(view)(*args, **kwargs)
+ return update_wrapper(wrapper, view)
+ info = self.model._meta.app_label, self.model._meta.module_name
+ return patterns('',
+ url(r'^(.+)/move-(up)/$',
+ wrap(self.move_view),
+ name='%s_%s_move_up' % info),
+ url(r'^(.+)/move-(down)/$',
+ wrap(self.move_view),
+ name='%s_%s_move_down' % info),
+ ) + super(OrderedModelAdmin, self).get_urls()
+
+ def move_view(self, request, object_id, direction):
+ obj = get_object_or_404(self.model, pk=unquote(object_id))
+ if direction == 'up':
+ obj.move_up()
+ else:
+ obj.move_down()
+ return HttpResponseRedirect('../../')
+
+ def move_up_down_links(self, obj):
+ return '<a href="../../%(app_label)s/%(module_name)s/%(object_id)s/move-up/"><img src="%(ADMIN_MEDIA_PREFIX)simg/admin/arrow-up.gif" alt="Move up" /></a> <a href="../../%(app_label)s/%(module_name)s/%(object_id)s/move-down/"><img src="%(ADMIN_MEDIA_PREFIX)simg/admin/arrow-down.gif" alt="Move up" /></a>' % {
+ 'app_label': self.model._meta.app_label,
+ 'module_name': self.model._meta.module_name,
+ 'object_id': obj.id,
+ 'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX,
+ }
+ move_up_down_links.allow_tags = True
+ move_up_down_links.short_description = 'Move'
+
51 ordered_model/models.py
@@ -0,0 +1,51 @@
+from django.contrib.contenttypes.models import ContentType
+from django.core.urlresolvers import reverse
+from django.db import models
+
+class OrderedModel(models.Model):
+ """
+ An abstract model that allows objects to be ordered relative to each other.
+ Provides an ``order`` field.
+ """
+
+ order = models.PositiveIntegerField(editable=False)
+
+ class Meta:
+ abstract = True
+ ordering = ('order',)
+
+ def save(self, *args, **kwargs):
+ if not self.id:
+ qs = self.__class__.objects.order_by('-order')
+ try:
+ self.order = qs[0].order + 1
+ except IndexError:
+ self.order = 0
+ super(OrderedModel, self).save(*args, **kwargs)
+
+ def _move(self, up):
+ qs = self.__class__._default_manager
+ if up:
+ qs = qs.order_by('-order').filter(order__lt=self.order)
+ else:
+ qs = qs.filter(order__gt=self.order)
+ try:
+ replacement = qs[0]
+ except IndexError:
+ # already first/last
+ return
+ self.order, replacement.order = replacement.order, self.order
+ self.save()
+ replacement.save()
+
+ def move_down(self):
+ """
+ Move this object down one position.
+ """
+ return self._move(up=False)
+
+ def move_up(self):
+ """
+ Move this object up one position.
+ """
+ return self._move(up=True)
0  ordered_model/tests/__init__.py
No changes.
34 ordered_model/tests/fixtures/test_items.json
@@ -0,0 +1,34 @@
+[
+ {
+ "pk": 1,
+ "model": "tests.item",
+ "fields": {
+ "name": "1",
+ "order": 0
+ }
+ },
+ {
+ "pk": 2,
+ "model": "tests.item",
+ "fields": {
+ "name": "2",
+ "order": 1
+ }
+ },
+ {
+ "pk": 3,
+ "model": "tests.item",
+ "fields": {
+ "name": "3",
+ "order": 5
+ }
+ },
+ {
+ "pk": 4,
+ "model": "tests.item",
+ "fields": {
+ "name": "4",
+ "order": 6
+ }
+ }
+]
5 ordered_model/tests/models.py
@@ -0,0 +1,5 @@
+from django.db import models
+from ordered_model.models import OrderedModel
+
+class Item(OrderedModel):
+ name = models.CharField(max_length=100)
10 ordered_model/tests/settings.py
@@ -0,0 +1,10 @@
+DATABASE_ENGINE = 'sqlite3'
+ROOT_URLCONF = 'tests.urls'
+INSTALLED_APPS = [
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'ordered_model',
+ 'ordered_model.tests',
+]
39 ordered_model/tests/tests.py
@@ -0,0 +1,39 @@
+from django.test import TestCase
+from ordered_model.tests.models import Item
+
+class ModelTestCase(TestCase):
+ fixtures = ['test_items.json']
+
+ def assertNames(self, names):
+ self.assertEqual(names, [i.name for i in Item.objects.all()])
+
+ def test_inserting_new_models(self):
+ Item.objects.create(name='Wurble')
+ self.assertNames(['1', '2', '3', '4', 'Wurble'])
+
+ def test_move_up(self):
+ Item.objects.get(pk=4).move_up()
+ self.assertNames(['1', '2', '4', '3'])
+ Item.objects.get(pk=1).move_up()
+ self.assertNames(['1', '2', '4', '3'])
+
+ def test_move_up_with_gap(self):
+ Item.objects.get(pk=3).move_up()
+ self.assertNames(['1', '3', '2', '4'])
+
+ def test_move_down(self):
+ Item.objects.get(pk=1).move_down()
+ self.assertNames(['2', '1', '3', '4'])
+ Item.objects.get(pk=4).move_down()
+ self.assertNames(['2', '1', '3', '4'])
+
+ def test_move_down_with_gap(self):
+ Item.objects.get(pk=2).move_down()
+ self.assertNames(['1', '3', '2', '4'])
+
+ def test_delete(self):
+ Item.objects.get(pk=2).delete()
+ self.assertNames(['1', '3', '4'])
+ Item.objects.get(pk=3).move_up()
+ self.assertNames(['3', '1', '4'])
+
4 ordered_model/tests/urls.py
@@ -0,0 +1,4 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+)
22 setup.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from distutils.core import setup
+
+setup(
+ name='django-ordered-model',
+ version='0.1',
+ description='Allows Django models to be ordered and provides a simple admin interface for reordering them.',
+ author='Ben Firshman',
+ author_email='ben@firshman.co.uk',
+ url='http://github.com/bfirsh/django-ordered-model/',
+ packages=[
+ 'ordered_model',
+ ],
+ classifiers=['Development Status :: 4 - Beta',
+ 'Framework :: Django',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ ],
+)
Please sign in to comment.
Something went wrong with that request. Please try again.