diff --git a/README.md b/README.md
index 7bc0d0f..90eda8f 100644
--- a/README.md
+++ b/README.md
@@ -100,7 +100,6 @@ class MySortableClass(SortableMixin):
ordering = ['the_order']
-
# define the field the model should be ordered by
the_order = models.PositiveIntegerField(default=0, editable=False, db_index=True)
@@ -429,6 +428,71 @@ 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 complete
+If you need to define a custom event or other callback to be executed after sorting is completed, you'll need to:
+
+1. Create a custom template for to add your JavaScript
+2. Populate the `after_sorting_js_callback_name` on your model admin
+
+An example of this can be found in the "samples" application in the source. Here's a model admin for a model called "Project":
+
+```python
+class ProjectAdmin(SortableAdmin):
+ inlines = [
+ CreditInline, NoteInline, GenericNoteInline,
+ NonSortableCreditInline, NonSortableNoteInline
+ ]
+ list_display = ['__str__', 'category']
+
+ after_sorting_js_callback_name = 'afterSortCallback' # do not include () - just function name
+ sortable_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:
+
+```html+django
+{% extends 'adminsortable/change_list.html' %}
+
+{% block extrahead %}
+ {{ block.super }}
+
+
+{% endblock %}
+```
+
+and the custom change form, for the inline models:
+
+```html+django
+{% extends "adminsortable/change_form.html" %}
+
+{% block extrahead %}
+ {{ block.super }}
+
+
+{% endblock %}
+```
+
+Ideally, you'd pull in a shared piece of code for your callback to keep your code DRY.
+
### Django-CMS integration
Django-CMS plugins use their own change form, and thus won't automatically
include the necessary JavaScript for django-admin-sortable to work. Fortunately,
@@ -531,8 +595,8 @@ ordering on top of that just seemed a little much in my opinion.
### Status
django-admin-sortable is currently used in production.
-### What's new in 2.1.5?
-- Support for Django Admin filters. Credit to [timur-orudzhov](https://github.com/timur-orudzhov).
+### What's new in 2.1.6?
+- Added inclusion of custom JavaScript callbacks after sorting is performed, if desired.
### Future
- Better template support for foreign keys that are self referential. If someone would like to take on rendering recursive sortables, that would be super.
diff --git a/README.rst b/README.rst
index 3d396c1..697a545 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,9 @@
Django Admin Sortable
=====================
-|PyPI version| |Python versions| |Build Status|
+`PyPI version `__
+`Python versions `__
+`Build Status `__
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,
@@ -51,7 +53,7 @@ Download django-admin-sortable from
`source `__
1. Unzip the directory and cd into the uncompressed project directory
-2.
+2.
- Optional: Enable your virtualenv
@@ -124,24 +126,23 @@ Sample Model:
.. code:: python
- # models.py
- from adminsortable.models import SortableMixin
+ # models.py
+ from adminsortable.models import SortableMixin
- class MySortableClass(SortableMixin):
- title = models.CharField(max_length=50)
+ class MySortableClass(SortableMixin):
+ title = models.CharField(max_length=50)
- class Meta:
- verbose_name = 'My Sortable Class'
- verbose_name_plural = 'My Sortable Classes'
- ordering = ['the_order']
+ class Meta:
+ verbose_name = 'My Sortable Class'
+ verbose_name_plural = 'My Sortable Classes'
+ ordering = ['the_order']
+ # define the field the model should be ordered by
+ the_order = models.PositiveIntegerField(default=0, editable=False, db_index=True)
- # define the field the model should be ordered by
- the_order = models.PositiveIntegerField(default=0, editable=False, db_index=True)
-
- def __unicode__(self):
- return self.title
+ def __unicode__(self):
+ return self.title
Support for models that don’t use an ``AutoField`` for their primary key
are also supported in version 2.0.20 or higher.
@@ -155,39 +156,39 @@ set up your models and admin options:
.. code:: python
- # models.py
- from adminsortable.fields import SortableForeignKey
+ # models.py
+ from adminsortable.fields import SortableForeignKey
- class Category(SortableMixin):
- class Meta:
- ordering = ['category_order']
- verbose_name_plural = 'Categories'
+ class Category(SortableMixin):
+ class Meta:
+ ordering = ['category_order']
+ verbose_name_plural = 'Categories'
- title = models.CharField(max_length=50)
+ title = models.CharField(max_length=50)
- # ordering field
- category_order = models.PositiveIntegerField(default=0, editable=False, db_index=True)
+ # ordering field
+ category_order = models.PositiveIntegerField(default=0, editable=False, db_index=True)
- class Project(SortableMixin):
- class Meta:
- ordering = ['project_order']
+ class Project(SortableMixin):
+ class Meta:
+ ordering = ['project_order']
- category = SortableForeignKey(Category)
- title = models.CharField(max_length=50)
+ category = SortableForeignKey(Category)
+ title = models.CharField(max_length=50)
- # ordering field
- project_order = models.PositiveIntegerField(default=0, editable=False, db_index=True)
+ # ordering field
+ project_order = models.PositiveIntegerField(default=0, editable=False, db_index=True)
- def __unicode__(self):
- return self.title
+ def __unicode__(self):
+ return self.title
- # admin.py
- from adminsortable.admin import SortableAdmin
+ # admin.py
+ from adminsortable.admin import SortableAdmin
- from your_app.models import Category, Project
+ from your_app.models import Category, Project
- admin.site.register(Category, SortableAdmin)
- admin.site.register(Project, SortableAdmin)
+ admin.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
@@ -195,42 +196,42 @@ as such:
.. code:: python
- from adminsortable.fields import SortableForeignKey
+ from adminsortable.fields import SortableForeignKey
- # models.py
- class Category(models.Model):
- class Meta:
- verbose_name_plural = 'Categories'
+ # models.py
+ class Category(models.Model):
+ class Meta:
+ verbose_name_plural = 'Categories'
- title = models.CharField(max_length=50)
- ...
+ title = models.CharField(max_length=50)
+ ...
- class Project(SortableMixin):
- class Meta:
- ordering = ['project_order']
+ class Project(SortableMixin):
+ class Meta:
+ ordering = ['project_order']
- category = SortableForeignKey(Category)
- title = models.CharField(max_length=50)
+ category = SortableForeignKey(Category)
+ title = models.CharField(max_length=50)
- # ordering field
- project_order = models.PositiveIntegerField(default=0, editable=False, db_index=True)
+ # ordering field
+ project_order = models.PositiveIntegerField(default=0, editable=False, db_index=True)
- def __unicode__(self):
- return self.title
+ def __unicode__(self):
+ return self.title
- # admin
- from adminsortable.admin import NonSortableParentAdmin, SortableStackedInline
+ # admin
+ from adminsortable.admin import NonSortableParentAdmin, SortableStackedInline
- from your_app.models import Category, Project
+ from your_app.models import Category, Project
- class ProjectInline(SortableStackedInline):
- model = Project
- extra = 1
+ class ProjectInline(SortableStackedInline):
+ model = Project
+ extra = 1
- class CategoryAdmin(NonSortableParentAdmin):
- inlines = [ProjectInline]
+ class CategoryAdmin(NonSortableParentAdmin):
+ inlines = [ProjectInline]
- admin.site.register(Category, CategoryAdmin)
+ admin.site.register(Category, CategoryAdmin)
The ``NonSortableParentAdmin`` class is necessary to wire up the
additional URL patterns and JavaScript that Django Admin Sortable needs
@@ -242,8 +243,8 @@ Backwards Compatibility
~~~~~~~~~~~~~~~~~~~~~~~
If you previously used Django Admin Sortable, **DON’T PANIC** -
-everything will still work exactly as before ***without any changes to
-your code***. Going forward, it is recommended that you use the new
+everything will still work exactly as before **without any changes to
+your code**. Going forward, it is recommended that you use the new
``SortableMixin`` on your models, as pre-2.0 compatibility might not be
a permanent thing.
@@ -252,17 +253,17 @@ hard-coded ``order`` field, and meta inheritance requirements:
.. code:: python
- # legacy model definition
+ # legacy model definition
- from adminsortable.models import Sortable
+ from adminsortable.models import Sortable
- class Project(Sortable):
- class Meta(Sortable.Meta):
- pass
- title = models.CharField(max_length=50)
+ class Project(Sortable):
+ class Meta(Sortable.Meta):
+ pass
+ title = models.CharField(max_length=50)
- def __unicode__(self):
- return self.title
+ def __unicode__(self):
+ return self.title
Model Instance Methods
^^^^^^^^^^^^^^^^^^^^^^
@@ -272,8 +273,8 @@ next or previous instance:
.. code:: python
- .get_next()
- .get_previous()
+ .get_next()
+ .get_previous()
By default, these methods will respect their order in relation to a
``SortableForeignKey`` field, if present. Meaning, that given the
@@ -281,13 +282,13 @@ following data:
::
- | Parent Model 1 | |
- | | Child Model 1 |
- | | Child Model 2 |
- | Parent Model 2 | |
- | | Child Model 3 |
- | | Child Model 4 |
- | | Child Model 5 |
+ | 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 return ``None`` “Child Model 3”
``get_previous`` would return ``None``
@@ -297,14 +298,14 @@ If you wish to override this behavior, pass in:
.. code:: python
- your_instance.get_next(filter_on_sortable_fk=False)
+ your_instance.get_next(filter_on_sortable_fk=False)
You may also pass in additional ORM “extra_filters” as a dictionary,
should you need to:
.. code:: python
- your_instance.get_next(extra_filters={'title__icontains': 'blue'})
+ your_instance.get_next(extra_filters={'title__icontains': 'blue'})
Adding Sorting to an existing model
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -322,10 +323,10 @@ Example assuming a model named “Category”:
.. code:: python
- def forwards(self, orm):
- for index, category in enumerate(orm.Category.objects.all()):
- category.order = index + 1
- category.save()
+ def forwards(self, orm):
+ for index, category in enumerate(orm.Category.objects.all()):
+ category.order = index + 1
+ category.save()
See: `this
link `__ for
@@ -348,56 +349,56 @@ To enable sorting in the admin, you need to inherit from
.. code:: python
- from django.contrib import admin
- from myapp.models import MySortableClass
- from adminsortable.admin import SortableAdmin
+ from django.contrib import admin
+ from myapp.models import MySortableClass
+ from adminsortable.admin import SortableAdmin
- class MySortableAdminClass(SortableAdmin):
- """Any admin options you need go here"""
+ class MySortableAdminClass(SortableAdmin):
+ """Any admin options you need go here"""
- admin.site.register(MySortableClass, MySortableAdminClass)
+ admin.site.register(MySortableClass, MySortableAdminClass)
To enable sorting on TabularInline models, you need to inherit from
SortableTabularInline:
.. code:: python
- from adminsortable.admin import SortableTabularInline
+ from adminsortable.admin import SortableTabularInline
- class MySortableTabularInline(SortableTabularInline):
- """Your inline options go here"""
+ class MySortableTabularInline(SortableTabularInline):
+ """Your inline options go here"""
To enable sorting on StackedInline models, you need to inherit from
SortableStackedInline:
.. code:: python
- from adminsortable.admin import SortableStackedInline
+ from adminsortable.admin import SortableStackedInline
- class MySortableStackedInline(SortableStackedInline):
- """Your inline options go here"""
+ class MySortableStackedInline(SortableStackedInline):
+ """Your inline options go here"""
There are also generic equivalents that you can inherit from:
.. code:: python
- from adminsortable.admin import (SortableGenericTabularInline,
- SortableGenericStackedInline)
- """Your generic inline options go here"""
+ from adminsortable.admin import (SortableGenericTabularInline,
+ SortableGenericStackedInline)
+ """Your generic inline options go here"""
If your parent model is *not* sortable, but has child inlines that are,
your parent model needs to inherit from ``NonSortableParentAdmin``:
.. code:: python
- from adminsortable.admin import (NonSortableParentAdmin,
- SortableTabularInline)
+ from adminsortable.admin import (NonSortableParentAdmin,
+ SortableTabularInline)
- class ChildTabularInline(SortableTabularInline):
- model = YourModel
+ class ChildTabularInline(SortableTabularInline):
+ model = YourModel
- class ParentAdmin(NonSortableParentAdmin):
- inlines = [ChildTabularInline]
+ class ParentAdmin(NonSortableParentAdmin):
+ inlines = [ChildTabularInline]
Overriding ``queryset()``
^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -420,25 +421,25 @@ properly determine the sortability of your model. Example:
.. code:: python
- # add this import to your admin.py
- from adminsortable.utils import get_is_sortable
+ # add this import to your admin.py
+ from adminsortable.utils import get_is_sortable
- class ComponentInline(SortableStackedInline):
- model = Component
+ class ComponentInline(SortableStackedInline):
+ model = Component
- def queryset(self, request):
- qs = super(ComponentInline, self).queryset(request).filter(
- title__icontains='foo')
+ def queryset(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.
+ # You'll need to add these lines to determine if your model
+ # is sortable once we hit the change_form() for the parent model.
- if get_is_sortable(qs):
- self.model.is_sortable = True
- else:
- self.model.is_sortable = False
- return qs
+ if get_is_sortable(qs):
+ self.model.is_sortable = True
+ else:
+ self.model.is_sortable = False
+ return qs
If you override the queryset of an inline, the number of objects present
may change, and adminsortable won’t be able to automatically determine
@@ -480,21 +481,21 @@ independently.
.. code:: python
- class Person(Sortable):
- class Meta(Sortable.Meta):
- verbose_name_plural = 'People'
+ class Person(Sortable):
+ class Meta(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)
+ 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}),
- )
+ 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)
+ def __unicode__(self):
+ return '{} {}'.format(self.first_name, self.last_name)
Extending custom templates
^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -508,15 +509,15 @@ SortableAdmin has two attributes you can override for this use case:
.. code:: python
- change_form_template_extends
- change_list_template_extends
+ change_form_template_extends
+ change_list_template_extends
These attributes have default values of:
.. code:: python
- change_form_template_extends = 'admin/change_form.html'
- change_list_template_extends = 'admin/change_list.html'
+ 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
@@ -524,15 +525,15 @@ 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.html
+ templates/adminsortable/edit_inline/stacked-1.10.x.html
+ templates/adminsortable/edit_inline/tabular-inline-1.10.x.html
otherwise, extend:
::
- templates/adminsortable/edit_inline/stacked.html
- templates/adminsortable/edit_inline/tabular.html
+ templates/adminsortable/edit_inline/stacked.html
+ templates/adminsortable/edit_inline/tabular.html
A Special Note About Stacked Inlines…
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -542,6 +543,77 @@ 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 complete
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you need to define a custom event or other callback to be executed
+after sorting is completed, you’ll need to:
+
+1. Create a custom template for to add your JavaScript
+2. Populate the ``after_sorting_js_callback_name`` on your model admin
+
+An example of this can be found in the “samples” application in the
+source. Here’s a model admin for a model called “Project”:
+
+.. code:: python
+
+ class ProjectAdmin(SortableAdmin):
+ inlines = [
+ CreditInline, NoteInline, GenericNoteInline,
+ NonSortableCreditInline, NonSortableNoteInline
+ ]
+ list_display = ['__str__', 'category']
+
+ after_sorting_js_callback_name = 'afterSortCallback' # do not include () - just function name
+ sortable_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:
+
+.. code:: html+django
+
+ {% extends 'adminsortable/change_list.html' %}
+
+ {% block extrahead %}
+ {{ block.super }}
+
+
+ {% endblock %}
+
+and the custom change form, for the inline models:
+
+.. code:: html+django
+
+ {% extends "adminsortable/change_form.html" %}
+
+ {% block extrahead %}
+ {{ block.super }}
+
+
+ {% endblock %}
+
+Ideally, you’d pull in a shared piece of code for your callback to keep
+your code DRY.
+
Django-CMS integration
~~~~~~~~~~~~~~~~~~~~~~
@@ -552,43 +624,43 @@ class allows a change form template to be specified:
.. code:: python
- # example plugin
- from cms.plugin_base import CMSPluginBase
+ # example plugin
+ from cms.plugin_base import CMSPluginBase
- class CMSCarouselPlugin(CMSPluginBase):
- admin_preview = False
- change_form_template = 'cms/sortable-stacked-inline-change-form.html'
- inlines = [SlideInline]
- model = Carousel
- name = _('Carousel')
- render_template = 'carousels/carousel.html'
+ class CMSCarouselPlugin(CMSPluginBase):
+ admin_preview = False
+ change_form_template = 'cms/sortable-stacked-inline-change-form.html'
+ inlines = [SlideInline]
+ model = Carousel
+ name = _('Carousel')
+ render_template = 'carousels/carousel.html'
- def render(self, context, instance, placeholder):
- context.update({
- 'carousel': instance,
- 'placeholder': placeholder
- })
- return context
+ def render(self, context, instance, placeholder):
+ context.update({
+ 'carousel': instance,
+ 'placeholder': placeholder
+ })
+ return context
- plugin_pool.register_plugin(CMSCarouselPlugin)
+ plugin_pool.register_plugin(CMSCarouselPlugin)
The contents of ``sortable-stacked-inline-change-form.html`` at a
minimum need to extend the extrahead block with:
.. code:: html+django
- {% extends "admin/cms/page/plugin_change_form.html" %}
- {% load static from staticfiles %}
+ {% extends "admin/cms/page/plugin_change_form.html" %}
+ {% load static from staticfiles %}
- {% block extrahead %}
- {{ block.super }}
-
-
-
-
+ {% block extrahead %}
+ {{ block.super }}
+
+
+
+
-
- {% endblock extrahead %}
+
+ {% endblock extrahead %}
Sorting within Django-CMS is really only feasible for inline models of a
plugin as Django-CMS already includes sorting for plugin instances. For
@@ -596,13 +668,13 @@ tabular inlines, just substitute:
.. code:: html+django
-
+
with:
.. code:: html+django
-
+
Notes
~~~~~
@@ -612,13 +684,13 @@ Replace the follwing line:
.. code:: html+django
- {% extends "admin/cms/page/plugin_change_form.html" %}
+ {% extends "admin/cms/page/plugin_change_form.html" %}
with
.. code:: html+django
- {% extends "admin/cms/page/plugin/change_form.html" %}
+ {% extends "admin/cms/page/plugin/change_form.html" %}
From ``django-admin-sortable 2.0.13`` the ``jquery.django-csrf.js`` was
removed and you have to include the snippet-template. Change the
@@ -626,13 +698,13 @@ following line:
.. code:: html+django
-
+
to
.. code:: html+django
- {% include 'adminsortable/csrf/jquery.django-csrf.html' with csrf_cookie_name='csrftoken' %}
+ {% include 'adminsortable/csrf/jquery.django-csrf.html' with csrf_cookie_name='csrftoken' %}
Please note, if you change the ``CSRF_COOKIE_NAME`` you have to adjust
``csrf_cookie_name='YOUR_CSRF_COOKIE_NAME'``
@@ -674,10 +746,3 @@ License
~~~~~~~
django-admin-sortable is released under the Apache Public License v2.
-
-.. |PyPI version| image:: https://img.shields.io/pypi/v/django-admin-sortable.svg
- :target: https://pypi.python.org/pypi/django-admin-sortable
-.. |Python versions| image:: https://img.shields.io/pypi/pyversions/django-admin-sortable.svg
- :target: https://pypi.python.org/pypi/django-admin-sortable
-.. |Build Status| image:: https://travis-ci.org/alsoicode/django-admin-sortable.svg?branch=master
- :target: https://travis-ci.org/alsoicode/django-admin-sortable
diff --git a/adminsortable/__init__.py b/adminsortable/__init__.py
index 7db3448..7785520 100644
--- a/adminsortable/__init__.py
+++ b/adminsortable/__init__.py
@@ -1,4 +1,4 @@
-VERSION = (2, 1, 5)
+VERSION = (2, 1, 6)
DEV_N = None