Package: django-admin-sortable
This project makes it easy to add drag-and-drop ordering to any model in
Django admin. Inlines for a sortable model may also be made sortable,
enabling individual items or groups of items to be sortable.If you find Django Admin Sortable to be helpful, considerbuying me a
coffee!Sorting model instances with a sortable parent:sortable-modelsSorting inlines:sortable-inlinesSupported Django VersionsFor Django 4 use the latest versionFor Django 3 use 2.2.4For Django 1.8.x < 3.0, use 2.1.8.For Django 1.5.x to 1.7.x, use version 2.0.18.Other notes of interest regarding versionsdjango-admin-sortable 1.5.2 introduced backward-incompatible changes for
Django 1.4.xdjango-admin-sortable 1.6.6 introduced a backward-incompatible change
for thesorting_filtersattribute. Please convert your attributes to
the new tuple-based format if you haven’t already.django-admin-sortable 1.7.1 and higher are compatible with Python 3.django-admin-sortable 2.1.6 has a bug. Please don’t use it :)Installation$ pip installdjango-admin-sortable–or–Download django-admin-sortable fromsourceUnzip the directory and cd into the uncompressed project directoryOptional: Enable your virtualenvRun$ python setup.py installor addadminsortableto your
PYTHONPATH.ConfigurationAddadminsortableto yourINSTALLED_APPS.Ensuredjango.template.context_processors.staticis in yourTEMPLATES["OPTIONS"]["context_processors"].(In older versions of Django, ensuredjango.core.context_processors.staticis inTEMPLATE_CONTEXT_PROCESSORSinstead.)Ensure thatCSRF_COOKIE_HTTPONLYhas not been set toTrue, as
django-admin-sortable is currently incompatible with that setting.Static MediaPreferred: Use thestaticfiles
appAlternate: Copy theadminsortablefolder from thestaticfolder
to the location you serve static files from.TestingHave a look at the included sample_project to see working examples. The
login credentials for admin are: admin/adminWhen a model is sortable, a tool-area link will be added that says
“Change Order”. Click this link, and you will be taken to the custom
view where you can drag-and-drop the records into order.Inlines may be drag-and-dropped into any order directly from the change
form.UsageModelsTo add “sortability” to a model, you need to inheritSortableMixinand at minimum, define:The field which should be used forMeta.ordering, which must
resolve to one of the integer fields defined in Django’s ORM:PositiveIntegerFieldIntegerFieldPositiveSmallIntegerFieldSmallIntegerFieldBigIntegerFieldMeta.orderingmust only contain one value, otherwise, your
objects will not be sorted correctly.IMPORTANT: You must name the field you use for ordering something
other than “order_field” as this name is reserved by theSortableMixinclass.It is recommended that you seteditable=Falseanddb_index=Trueon the field defined inMeta.orderingfor a
seamless Django admin experience and faster lookups on the objects.Sample Model:# models.pyfromadminsortable.modelsimportSortableMixinclassMySortableClass(SortableMixin):title=models.CharField(max_length=50)classMeta:verbose_name='My Sortable Class'verbose_name_plural='My Sortable Classes'ordering=['the_order']# define the field the model should be ordered bythe_order=models.PositiveIntegerField(default=0,editable=False,db_index=True)def__unicode__(self):returnself.titleSupport for models that don’t use anAutoFieldfor their primary key
are also supported in version 2.0.20 or higher.Common Use CaseA common use case is to have child objects that are sortable relative to
a parent. If your parent object is also sortable, here’s how you would
set up your models and admin options:# models.pyfromadminsortable.fieldsimportSortableForeignKeyclassCategory(SortableMixin):classMeta:ordering=['category_order']verbose_name_plural='Categories'title=models.CharField(max_length=50)# ordering fieldcategory_order=models.PositiveIntegerField(default=0,editable=False,db_index=True)classProject(SortableMixin):classMeta:ordering=['project_order']category=SortableForeignKey(Category)title=models.CharField(max_length=50)# ordering fieldproject_order=models.PositiveIntegerField(default=0,editable=False,db_index=True)def__unicode__(self):returnself.title# admin.pyfromadminsortable.adminimportSortableAdminfromyour_app.modelsimportCategory,Projectadmin.site.register(Category,SortableAdmin)admin.site.register(Project,SortableAdmin)Sometimes you might have a parent model that is not sortable, but has
child models that are. In that case define your models and admin options
as such:fromadminsortable.fieldsimportSortableForeignKey# models.pyclassCategory(models.Model):classMeta:verbose_name_plural='Categories'title=models.CharField(max_length=50)...classProject(SortableMixin):classMeta:ordering=['project_order']category=SortableForeignKey(Category)title=models.CharField(max_length=50)# ordering fieldproject_order=models.PositiveIntegerField(default=0,editable=False,db_index=True)def__unicode__(self):returnself.title# adminfromadminsortable.adminimportNonSortableParentAdmin,SortableStackedInlinefromyour_app.modelsimportCategory,ProjectclassProjectInline(SortableStackedInline):model=Projectextra=1classCategoryAdmin(NonSortableParentAdmin):inlines=[ProjectInline]admin.site.register(Category,CategoryAdmin)TheNonSortableParentAdminclass is necessary to wire up the
additional URL patterns and JavaScript that Django Admin Sortable needs
to make your models sortable. The child model does not have to be an
inline model, it can be wired directly to Django admin and the objects
will be grouped by the non-sortable foreign key when sorting.Backwards CompatibilityIf you previously used Django Admin Sortable,DON’T PANIC-
everything will still work exactly as beforewithout any changes to
your code. Going forward, it is recommended that you use the newSortableMixinon your models, as pre-2.0 compatibility might not be
a permanent thing.Please note however that theSortableclass still contains the
hard-codedorderfield, and meta inheritance requirements:# legacy model definitionfromadminsortable.modelsimportSortableclassProject(Sortable):classMeta(Sortable.Meta):passtitle=models.CharField(max_length=50)def__unicode__(self):returnself.titleModel Instance MethodsEach instance of a sortable model has two convenience methods to get the
next or previous instance:.get_next().get_previous()By default, these methods will respect their order in relation to aSortableForeignKeyfield, if present. Meaning, that given the
following data:| Parent Model 1 |               |
|                | Child Model 1 |
|                | Child Model 2 |
| Parent Model 2 |               |
|                | Child Model 3 |
|                | Child Model 4 |
|                | Child Model 5 |“Child Model 2”get_next()would returnNone“Child Model 3”get_previouswould returnNoneIf you wish to override this behavior, pass in:filter_on_sortable_fk=False:your_instance.get_next(filter_on_sortable_fk=False)You may also pass in additional ORM “filer_args” as a list, or
“filter_kwargs” as a dictionary, should you need to:your_instance.get_next(filter_args=[Q(field1=True)|Q(field2=True)],filter_kwargs={'title__icontains':'blue'})Deprecation WarningPreviously “filter_kwargs” was named “extra_filters”. With the addition
of “filter_args”, “extra_filters” was renamed for consistency.Adding Sorting to an existing modelDjango 1.5.x to 1.6.xIf you’re adding Sorting to an existing model, it is recommended that
you usedjango-southto create a schema
migration to add the “order” field to your model. You will also need to
create a data migration in order to add the appropriate values for the
“order” column.Example assuming a model named “Category”:defforwards(self,orm):forindex,categoryinenumerate(orm.Category.objects.all()):category.order=index+1category.save()See:this
linkfor
more information on South Data Migrations.Django 1.7.x or higherSince schema migrations are built into Django 1.7, you don’t have to use
south, but the process of adding and running migrations is nearly
identical. Take a look at theMigrationsdocumentation to get started.Django Admin IntegrationTo enable sorting in the admin, you need to inherit fromSortableAdmin:fromdjango.contribimportadminfrommyapp.modelsimportMySortableClassfromadminsortable.adminimportSortableAdminclassMySortableAdminClass(SortableAdmin):"""Any admin options you need go here"""admin.site.register(MySortableClass,MySortableAdminClass)To enable sorting on TabularInline models, you need to inherit from
SortableTabularInline:fromadminsortable.adminimportSortableTabularInlineclassMySortableTabularInline(SortableTabularInline):"""Your inline options go here"""To enable sorting on StackedInline models, you need to inherit from
SortableStackedInline:fromadminsortable.adminimportSortableStackedInlineclassMySortableStackedInline(SortableStackedInline):"""Your inline options go here"""There are also generic equivalents that you can inherit from:fromadminsortable.adminimport(SortableGenericTabularInline,SortableGenericStackedInline)"""Your generic inline options go here"""If your parent model isnotsortable, but has child inlines that are,
your parent model needs to inherit fromNonSortableParentAdmin:fromadminsortable.adminimport(NonSortableParentAdmin,SortableTabularInline)classChildTabularInline(SortableTabularInline):model=YourModelclassParentAdmin(NonSortableParentAdmin):inlines=[ChildTabularInline]Overridingqueryset()django-admin-sortable supports custom queryset overrides on admin models
and inline models in Django admin!If you’re providing an override of a SortableAdmin or Sortable inline
model, you don’t need to do anything extra. django-admin-sortable will
automatically honor your queryset.Have a look at the WidgetAdmin class in the sample project for an
example of an admin class with a customqueryset()override.Overridingqueryset()for an inline modelThis is a special case, which requires a few lines of extra code to
properly determine the sortability of your model. Example:# add this import to your admin.pyfromadminsortable.utilsimportget_is_sortableclassComponentInline(SortableStackedInline):model=Componentdefqueryset(self,request):qs=super(ComponentInline,self).queryset(request).filter(title__icontains='foo')# You'll need to add these lines to determine if your model# is sortable once we hit the change_form() for the parent model.ifget_is_sortable(qs):self.model.is_sortable=Trueelse:self.model.is_sortable=FalsereturnqsIf you override the queryset of an inline, the number of objects present
may change, and adminsortable won’t be able to automatically determine
if the inline model is sortable from here, which is why we have to set
theis_sortableproperty of the model in this method.Sorting subsets of objectsIt is also possible to sort a subset of objects in your model by adding
asorting_filterstuple. This works exactly the same as.filter()on a QuerySet, and is appliedafterget_queryset()on the admin class, allowing you to override the queryset as you would
normally in admin but apply additional filters for sorting. The text
“Change Order of” will appear before each filter in the Change List
template, and the filter groups are displayed from left to right in the
order listed. If nosorting_filtersare specified, the text “Change
Order” will be displayed for the link.Self-Referential SortableForeignKeyYou can specify a self-referential SortableForeignKey field, however the
admin interface will currently show a model that is a grandchild at the
same level as a child. I’m working to resolve this issue.Important!django-admin-sortable 1.6.6 introduced a backwards-incompatible change
forsorting_filters. Previously this attribute was defined as a
dictionary, so you’ll need to change your values over to the new
tuple-based format.An example of sorting subsets would be a “Board of Directors”. In this
use case, you have a list of “People” objects. Some of these people are
on the Board of Directors and some not, and you need to sort them
independently.classPerson(Sortable):classMeta(Sortable.Meta):verbose_name_plural='People'first_name=models.CharField(max_length=50)last_name=models.CharField(max_length=50)is_board_member=models.BooleanField('Board Member',default=False)sorting_filters=(('Board Members',{'is_board_member':True}),('Non-Board Members',{'is_board_member':False}),)def__unicode__(self):return'{}{}'.format(self.first_name,self.last_name)Extending custom templatesBy default, adminsortable’s change form and change list views inherit
from Django admin’s standard templates. Sometimes you need to have a
custom change form or change list, but also need adminsortable’s CSS and
JavaScript for inline models that are sortable for example.SortableAdmin has two attributes you can override for this use case:change_form_template_extendschange_list_template_extendsThese attributes have default values of:change_form_template_extends='admin/change_form.html'change_list_template_extends='admin/change_list.html'If you need to extend the inline change form templates, you’ll need to
select the right one, depending on your version of Django. For 1.10.x or
below, you’ll need to extend one of the following:templates/adminsortable/edit_inline/stacked-1.10.x.html
templates/adminsortable/edit_inline/tabular-inline-1.10.x.htmlotherwise, extend:templates/adminsortable/edit_inline/stacked.html
templates/adminsortable/edit_inline/tabular.htmlA Special Note About Stacked Inlines…The height of a stacked inline model can dynamically increase, which can
make them difficult to sort. If you anticipate the height of a stacked
inline is going to be very tall, I would suggest using
SortableTabularInline instead.Custom JS callbacks after sorting is completeIf you need to define a custom event or other callback to be executed
after sorting is completed, you’ll need to:Create a custom template for to add your JavaScriptPopulate theafter_sorting_js_callback_nameon your model adminAn example of this can be found in the “samples” application in the
source. Here’s a model admin for a model called “Project”:classProjectAdmin(SortableAdmin):inlines=[CreditInline,NoteInline,GenericNoteInline,NonSortableCreditInline,NonSortableNoteInline]list_display=['__str__','category']after_sorting_js_callback_name='afterSortCallback'# do not include () - just function namesortable_change_list_template='adminsortable/custom_change_list.html'sortable_change_form_template="adminsortable/custom_change_form.html"This example is going to add a custom callback on the parent model, and
it’s inlines. Here is the JavaScript added to the custom change list:{%extends'adminsortable/change_list.html'%}{%blockextrahead%}{{block.super}}<script>django.jQuery(document).on('order:changed',function(event){console.log(event.message);// your code here});window['{{after_sorting_js_callback_name}}']=function(){django.jQuery(document).trigger({type:'order:changed',message:'Order changed',time:newDate()});};</script>{%endblock%}and the custom change form, for the inline models:{%extends"adminsortable/change_form.html"%}{%blockextrahead%}{{block.super}}<script>django.jQuery(document).on('order:changed',function(event){console.log(event.message);// your code here});window['{{after_sorting_js_callback_name}}']=function(){django.jQuery(document).trigger({type:'order:changed',message:'Order changed',time:newDate()});};</script>{%endblock%}Ideally, you’d pull in a shared piece of code for your callback to keep
your code DRY.Django-CMS integrationDjango-CMS plugins use their own change form, and thus won’t
automatically include the necessary JavaScript for django-admin-sortable
to work. Fortunately, this is easy to resolve, as theCMSPluginclass allows a change form template to be specified:# example pluginfromcms.plugin_baseimportCMSPluginBaseclassCMSCarouselPlugin(CMSPluginBase):admin_preview=Falsechange_form_template='cms/sortable-stacked-inline-change-form.html'inlines=[SlideInline]model=Carouselname=_('Carousel')render_template='carousels/carousel.html'defrender(self,context,instance,placeholder):context.update({'carousel':instance,'placeholder':placeholder})returncontextplugin_pool.register_plugin(CMSCarouselPlugin)The contents ofsortable-stacked-inline-change-form.htmlat a
minimum need to extend the extrahead block with:{%extends"admin/cms/page/plugin_change_form.html"%}{%loadstaticfromstaticfiles%}{%blockextrahead%}{{block.super}}<scriptsrc="{%static'adminsortable/js/jquery-ui-django-admin.min.js'%}"></script><scriptsrc="{%static'adminsortable/js/jquery.ui.touch-punch.min.js'%}"></script><scriptsrc="{%static'adminsortable/js/jquery.django-csrf.js'%}"></script><scriptsrc="{%static'adminsortable/js/admin.sortable.stacked.inlines.js'%}"></script><linkrel="stylesheet"type="text/css"href="{%static'adminsortable/css/admin.sortable.inline.css'%}"/>{%endblockextrahead%}Sorting within Django-CMS is really only feasible for inline models of a
plugin as Django-CMS already includes sorting for plugin instances. For
tabular inlines, just substitute:<scriptsrc="{%static'adminsortable/js/admin.sortable.stacked.inlines.js'%}"></script>with:<scriptsrc="{%static'adminsortable/js/admin.sortable.tabular.inlines.js'%}"></script>NotesFromdjango-cms3.xthe path of change_form.html has changed.
Replace the follwing line:{%extends"admin/cms/page/plugin_change_form.html"%}with{%extends"admin/cms/page/plugin/change_form.html"%}Fromdjango-admin-sortable2.0.13thejquery.django-csrf.jswas
removed and you have to include the snippet-template. Change the
following line:<scripttype="text/javascript"src="{%static'adminsortable/js/jquery.django-csrf.js'%}"></script>to{%include'adminsortable/csrf/jquery.django-csrf.html'withcsrf_cookie_name='csrftoken'%}Please note, if you change theCSRF_COOKIE_NAMEyou have to adjustcsrf_cookie_name='YOUR_CSRF_COOKIE_NAME'RationaleOther projects have added drag-and-drop ordering to the ChangeList view,
however this introduces a couple of problems…The ChangeList view supports pagination, which makes drag-and-drop
ordering across pages impossible.The ChangeList view by default, does not order records based on a
foreign key, nor distinguish between rows that are associated with a
foreign key. This makes ordering the records grouped by a foreign key
impossible.The ChangeList supports in-line editing, and adding drag-and-drop
ordering on top of that just seemed a little much in my opinion.Statusdjango-admin-sortable is currently used in production.What’s new in 2.3.0?Django 4 compatibilityFutureBetter template support for foreign keys that are self referential.
If someone would like to take on rendering recursive sortables, that
would be super.Licensedjango-admin-sortable is released under the Apache Public License v2.