Package: django-ordered-model
django-ordered-modeldjango-ordered-model allows models to be ordered and provides a simple admin
interface for reordering them.Based onhttps://djangosnippets.org/snippets/998/andhttps://djangosnippets.org/snippets/259/See ourcompatability notesfor the appropriate version to use with older Django and Python releases.InstallationPlease install using Pip:$pipinstalldjango-ordered-modelOr if you have checked out the repository:$pythonsetup.pyinstallOr to use the latest development code from our master branch:$pipuninstalldjango-ordered-model
$pipinstallgit+git://github.com/django-ordered-model/django-ordered-model.gitUsageAddordered_modelto yourSETTINGS.INSTALLED_APPS.Inherit your model fromOrderedModelto make it ordered:fromdjango.dbimportmodelsfromordered_model.modelsimportOrderedModelclassItem(OrderedModel):name=models.CharField(max_length=100)Then run the usual$ ./manage.py makemigrationsand$ ./manage.py migrateto update your database schema.Model instances now have a set of methods to move them relative to each other.
To demonstrate those methods we create two instances ofItem:foo=Item.objects.create(name="Foo")bar=Item.objects.create(name="Bar")Swap positionsfoo.swap(bar)This swaps the position of two objects.Move position up on positionfoo.up()foo.down()Moving an object up or down just makes it swap its position with the neighbouring
object directly above of below depending on the direction.Move to arbitrary positionfoo.to(12)bar.to(13)Move the object to an arbitrary position in the stack. This essentially sets the
order value to the specified integer. Objects between the original and the new
position get their order value increased or decreased according to the direction
of the move.Move object above or below referencefoo.above(bar)foo.below(bar)Move the object directly above or below the reference object, increasing or
decreasing the order value for all objects between the two, depending on the
direction of the move.Move to top of stackfoo.top()This sets the order value to the lowest value found in the stack and increases
the order value of all objects that were above the moved object by one.Move to bottom of stackfoo.bottom()This sets the order value to the highest value found in the stack and decreases
the order value of all objects that were below the moved object by one.Updating fields that would be updated during save()For performance reasons, thedelete(),to(),below(),above(),top(), andbottom()methods use Django'supdate()method to change the order of other objects
that are shifted as a result of one of these calls. If the model has fields that
are typically updated in a customized save() method, or through other app level
functionality such asDateTimeField(auto_now=True), you can add additional fields
to be passed through toupdate(). This will only impact objects where their order
is being shifted as a result of an operation on the target object, not the target
object itself.foo.to(12,extra_update={'modified':now()})Get the previous or next objectsfoo.previous()foo.next()Theprevious()andnext()methods return the neighbouring objects directly above or below
within the ordered stack.Subset OrderingIn some cases, ordering objects is required only on a subset of objects. For example,
an application that manages contact lists for users, in a many-to-one/many relationship,
would like to allow each user to order their contacts regardless of how other users
choose their order. This option is supported via theorder_with_respect_toparameter.A simple example might look like so:classContact(OrderedModel):user=models.ForeignKey(User,on_delete=models.CASCADE)phone=models.CharField()order_with_respect_to='user'If objects are ordered with respect to more than one field,order_with_respect_tosupports
tuples to define multiple fields:classModel(OrderedModel)# ...order_with_respect_to=('field_a','field_b')In a many-to-many relationship you need to use a separate through model which is derived from the OrderedModel.
For example, an application which manages pizzas with toppings.A simple example might look like so:classTopping(models.Model):name=models.CharField(max_length=100)classPizza(models.Model):name=models.CharField(max_length=100)toppings=models.ManyToManyField(Topping,through='PizzaToppingsThroughModel')classPizzaToppingsThroughModel(OrderedModel):pizza=models.ForeignKey(Pizza,on_delete=models.CASCADE)topping=models.ForeignKey(Topping,on_delete=models.CASCADE)order_with_respect_to='pizza'classMeta:ordering=('pizza','order')You can also specifyorder_with_respect_toto a field on a related model. An example use-case can be made with the following models:classItemGroup(models.Model):user=models.ForeignKey(User,on_delete=models.CASCADE)general_info=models.CharField(max_length=100)classGroupedItem(OrderedModel):group=models.ForeignKey(ItemGroup,on_delete=models.CASCADE)specific_info=models.CharField(max_length=100)order_with_respect_to='group__user'Here items are put into groups that have some general information used by its items, but the ordering of the items is independent of the group the item is in.In all casesorder_with_respect_tomust specify aForeignKeyfield on the model, or a Django CheckE002,E005orE006error will be raised with further help.When you want ordering on the baseclass instead of subclasses in an ordered list of objects of various classes, specify the full module path of the base class:classBaseQuestion(OrderedModel):order_class_path=__module__+'.BaseQuestion'question=models.TextField(max_length=100)classMeta:ordering=('order',)classMultipleChoiceQuestion(BaseQuestion):good_answer=models.TextField(max_length=100)wrong_answer1=models.TextField(max_length=100)wrong_answer2=models.TextField(max_length=100)wrong_answer3=models.TextField(max_length=100)classOpenQuestion(BaseQuestion):answer=models.TextField(max_length=100)Custom Manager and QuerySetWhen your model your extendsOrderedModel, it inherits a customModelManagerinstance which in turn provides additional operations on the resultingQuerySet. For example ifItemis anOrderedModelsubclass, the  querysetItem.objects.all()has functions:above_instance(object),below_instance(object),get_min_order(),get_max_order(),above(index),below(index)If yourModeluses a customModelManager(such asItemManagerbelow) please have it extendOrderedModelManager, or else Django CheckE003will be raised.If yourModelManagerreturns a customQuerySet(such asItemQuerySetbelow) please have it extendOrderedModelQuerySet, or Django CheckE004will be raised.fromordered_model.modelsimportOrderedModel,OrderedModelManager,OrderedModelQuerySetclassItemQuerySet(OrderedModelQuerySet):passclassItemManager(OrderedModelManager):defget_queryset(self):returnItemQuerySet(self.model,using=self._db)classItem(OrderedModel):objects=ItemManager()If another Django plugin requires you to use specificModel,QuerySetorModelManagerclasses, you might need to construct intermediate classes using multiple inheritance,see an example in issue 270.Custom ordering fieldExtendingOrderedModelcreates amodels.PositiveIntegerFieldfield calledorderand the appropriate migrations. It customises the defaultclass Metato then order returned querysets by this field. If you wish to use an existing model field to store the ordering, subclassOrderedModelBaseinstead and set the attributeorder_field_nameto match your field name and theorderingattribute onMeta:classMyModel(OrderedModelBase):...sort_order=models.PositiveIntegerField(editable=False,db_index=True)order_field_name="sort_order"classMeta:ordering=("sort_order",)Settingorder_field_nameis specific for this library to know which field to change when ordering actions are taken. TheMetaorderingline is existing Django functionality to use a field for sorting.
Seetests/models.pyobjectCustomOrderFieldModelfor an example.Admin integrationTo add arrows in the admin change list page to do reordering, you can use theOrderedModelAdminand themove_up_down_linksfield:fromdjango.contribimportadminfromordered_model.adminimportOrderedModelAdminfrommodelsimportItemclassItemAdmin(OrderedModelAdmin):list_display=('name','move_up_down_links')admin.site.register(Item,ItemAdmin)For a many-to-many relationship you need one of the following inlines.OrderedTabularInlineorOrderedStackedInlinejust like the django admin.For theOrderedTabularInlineit will look like this:fromdjango.contribimportadminfromordered_model.adminimportOrderedTabularInline,OrderedInlineModelAdminMixinfrommodelsimportPizza,PizzaToppingsThroughModelclassPizzaToppingsTabularInline(OrderedTabularInline):model=PizzaToppingsThroughModelfields=('topping','order','move_up_down_links',)readonly_fields=('order','move_up_down_links',)ordering=('order',)extra=1classPizzaAdmin(OrderedInlineModelAdminMixin,admin.ModelAdmin):model=Pizzalist_display=('name',)inlines=(PizzaToppingsTabularInline,)admin.site.register(Pizza,PizzaAdmin)For theOrderedStackedInlineit will look like this:fromdjango.contribimportadminfromordered_model.adminimportOrderedStackedInline,OrderedInlineModelAdminMixinfrommodelsimportPizza,PizzaToppingsThroughModelclassPizzaToppingsStackedInline(OrderedStackedInline):model=PizzaToppingsThroughModelfields=('topping','move_up_down_links',)readonly_fields=('move_up_down_links',)ordering=('order',)extra=1classPizzaAdmin(OrderedInlineModelAdminMixin,admin.ModelAdmin):list_display=('name',)inlines=(PizzaToppingsStackedInline,)admin.site.register(Pizza,PizzaAdmin)Note:OrderedModelAdminrequires the inline subclasses ofOrderedTabularInlineandOrderedStackedInlineto be listed oninlinesso that we register appropriate URL routes. If you are using Django 3.0 featureget_inlines()orget_inline_instances()to return the list of inlines dynamically, consider it a filter and still add them toinlinesor you might encounter a “No Reverse Match” error when accessing model change view.Re-ordering modelsIn certain cases the models will end up in a not properly ordered state. This can be caused
by bypassing the 'delete' / 'save' methods, or when a user changes a foreign key of a object
which is part of the 'order_with_respect_to' fields. You can use the following command to
re-order one or more models.$ ./manage.py reorder_model <app_name>.<model_name> \
        [<app_name>.<model_name> ... ]

The arguments are as follows:
- `<app_name>`: Name of the application for the model.
- `<model_name>`: Name of the model that's an OrderedModel.Django Rest FrameworkTo support updating ordering fields by Django Rest Framework, we include a serializerOrderedModelSerializerthat intercepts writes to the ordering field, and callsOrderedModel.to()method to effect a re-ordering:from rest_framework import routers, serializers, viewsets
from ordered_model.serializers import OrderedModelSerializer
from tests.models import CustomItem

class ItemSerializer(serializers.HyperlinkedModelSerializer, OrderedModelSerializer):
    class Meta:
        model = CustomItem
        fields = ['pkid', 'name', 'modified', 'order']

class ItemViewSet(viewsets.ModelViewSet):
    queryset = CustomItem.objects.all()
    serializer_class = ItemSerializer

router = routers.DefaultRouter()
router.register(r'items', ItemViewSet)Note that you need to include the 'order' field (or your custom field name) in theSerializer'sfieldslist, either explicitly or using__all__. Seeordered_model/serializers.pyfor the implementation.Test suiteTo run the tests against your current environment, use:$pipinstalldjangorestframework
$django-admintest--pythonpath=.--settings=tests.settingsOtherwise please installtoxand run the tests for a specific environment with-eor all environments:$tox-epy36-django30
$toxCompatibility with Django and Pythondjango-ordered-model versionDjango versionPython versionDRF (optional)3.6.x3.x,4.x3.5and above3.12 and above3.5.x3.x,4.x3.5and above-3.4.x2.x,3.x3.5and above-3.3.x2.x3.4and above-3.2.x2.x3.4and above-3.1.x2.x3.4and above-3.0.x2.x3.4and above-2.1.x1.x2.7to 3.6-2.0.x1.x2.7to 3.6-MaintainersBen FirshmanChris ShucksmithSardorbek Imomaliev