Merge pull request #77 from iambrandontaylor/multi-sorting-groups
Refactored sorting_filters into a tuple and moved logic for retrieving s...master
commit
5a60408076
38
README.md
38
README.md
|
|
@ -1,6 +1,6 @@
|
||||||
# Django Admin Sortable
|
# Django Admin Sortable
|
||||||
|
|
||||||
Current version: 1.6.5
|
Current version: 1.6.6
|
||||||
|
|
||||||
This project makes it easy to add drag-and-drop ordering to any model in
|
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,
|
Django admin. Inlines for a sortable model may also be made sortable,
|
||||||
|
|
@ -12,6 +12,8 @@ For Django 1.5.x or higher, use the latest version of django-admin-sortable.
|
||||||
|
|
||||||
django-admin-sortable 1.5.2 introduced backward-incompatible changes for Django 1.4.x
|
django-admin-sortable 1.5.2 introduced backward-incompatible changes for Django 1.4.x
|
||||||
|
|
||||||
|
django-admin-sortable 1.6.6 introduced a backward-incompatible change for the `sorting_filters` attribute. Please convert your attributes to the new tuple-based format.
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
1. pip install django-admin-sortable
|
1. pip install django-admin-sortable
|
||||||
|
|
@ -31,7 +33,7 @@ Download django-admin-sortable from [source](https://github.com/iambrandontaylor
|
||||||
|
|
||||||
### Static Media
|
### Static Media
|
||||||
Preferred:
|
Preferred:
|
||||||
Use the [staticfiles app](https://docs.djangoproject.com/en/1.4/ref/contrib/staticfiles/)
|
Use the [staticfiles app](https://docs.djangoproject.com/en/1.6/ref/contrib/staticfiles/)
|
||||||
|
|
||||||
Alternate:
|
Alternate:
|
||||||
Copy the `adminsortable` folder from the `static` folder to the
|
Copy the `adminsortable` folder from the `static` folder to the
|
||||||
|
|
@ -184,23 +186,29 @@ 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 the
|
if the inline model is sortable from here, which is why we have to set the
|
||||||
`is_sortable` property of the model in this method.
|
`is_sortable` property of the model in this method.
|
||||||
|
|
||||||
#### Sorting a subset of objects
|
#### Sorting subsets of objects
|
||||||
It is also possible to sort a subset of objects in your model by adding a `sorting_filters` dictionary. This dictionary works exactly the same as `.filter()` on a QuerySet, and is applied *after* `get_queryset()` on the admin class, allowing you to override the queryset as you would normally in admin but apply additional filters for sorting.
|
It is also possible to sort a subset of objects in your model by adding a `sorting_filters` tuple. This works exactly the same as `.filter()` on a QuerySet, and is applied *after* `get_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 no `sorting_filters` are specified, the text "Change Order" will be displayed for the link.
|
||||||
|
|
||||||
This is useful when you need to have some objects orderable via drag-and-drop, and others not. An example would be a "Board of Directors". In this use case, you have a list of "People". Some of these people are on the Board of Directors, and you need to sort them at will. Other people need to be sorted alphabetically.
|
#####Important!
|
||||||
|
django-admin-sortable 1.6.6 introduces a backwards-incompatible change for `sorting_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.
|
||||||
|
|
||||||
class Person(Sortable):
|
class Person(Sortable):
|
||||||
class Meta(Sortable.Meta):
|
class Meta(Sortable.Meta):
|
||||||
verbose_name_plural = 'People'
|
verbose_name_plural = 'People'
|
||||||
|
|
||||||
first_name = models.CharField(max_length=50)
|
first_name = models.CharField(max_length=50)
|
||||||
last_name = models.CharField(max_length=50)
|
last_name = models.CharField(max_length=50)
|
||||||
is_board_member = models.BooleanField(default=False)
|
is_board_member = models.BooleanField('Board Member', default=False)
|
||||||
|
|
||||||
sorting_filters = {'is_board_member': True}
|
sorting_filters = (
|
||||||
|
('Board Members', {'is_board_member': True}),
|
||||||
|
('Non-Board Members', {'is_board_member': False}),
|
||||||
|
)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return '{} {}'.format(self.first_name, self.last_name)
|
return '{} {}'.format(self.first_name, self.last_name)
|
||||||
|
|
||||||
#### Extending custom templates
|
#### Extending custom templates
|
||||||
By default, adminsortable's change form and change list views inherit from
|
By default, adminsortable's change form and change list views inherit from
|
||||||
|
|
@ -208,12 +216,12 @@ 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
|
form or change list, but also need adminsortable's CSS and JavaScript for
|
||||||
inline models that are sortable for example.
|
inline models that are sortable for example.
|
||||||
|
|
||||||
SortableAdmin has two properties you can override for this use case:
|
SortableAdmin has two attributes you can override for this use case:
|
||||||
|
|
||||||
change_form_template_extends
|
change_form_template_extends
|
||||||
change_list_template_extends
|
change_list_template_extends
|
||||||
|
|
||||||
These properties have default values of:
|
These attributes have default values of:
|
||||||
|
|
||||||
change_form_template_extends = 'admin/change_form.html'
|
change_form_template_extends = 'admin/change_form.html'
|
||||||
change_list_template_extends = 'admin/change_list.html'
|
change_list_template_extends = 'admin/change_list.html'
|
||||||
|
|
@ -308,8 +316,8 @@ ordering on top of that just seemed a little much in my opinion.
|
||||||
django-admin-sortable is currently used in production.
|
django-admin-sortable is currently used in production.
|
||||||
|
|
||||||
|
|
||||||
### What's new in 1.6.5?
|
### What's new in 1.6.6?
|
||||||
- Namespace fixes for jQuery in Django admin
|
- Multiple sorting_filters
|
||||||
|
|
||||||
|
|
||||||
### Future
|
### Future
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
VERSION = (1, 6, 5) # following PEP 386
|
VERSION = (1, 6, 6) # following PEP 386
|
||||||
DEV_N = None
|
DEV_N = None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@ STATIC_URL = settings.STATIC_URL
|
||||||
|
|
||||||
|
|
||||||
class SortableAdminBase(object):
|
class SortableAdminBase(object):
|
||||||
filtered_objects = []
|
|
||||||
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
def changelist_view(self, request, extra_context=None):
|
||||||
"""
|
"""
|
||||||
If the model that inherits Sortable has more than one object,
|
If the model that inherits Sortable has more than one object,
|
||||||
|
|
@ -37,11 +35,10 @@ class SortableAdminBase(object):
|
||||||
object_tools block to take people to the view to change the sorting.
|
object_tools block to take people to the view to change the sorting.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Apply any additional filters to create a subset of sortable objects
|
# get sort group index from querystring
|
||||||
self.filtered_objects = self.queryset(request).filter(
|
sort_filter_index = request.GET.get('sort_filter')
|
||||||
**self.model.sorting_filters)
|
|
||||||
|
|
||||||
if get_is_sortable(self.filtered_objects):
|
if get_is_sortable(self.queryset(request)):
|
||||||
self.change_list_template = \
|
self.change_list_template = \
|
||||||
self.sortable_change_list_with_sort_link_template
|
self.sortable_change_list_with_sort_link_template
|
||||||
self.is_sortable = True
|
self.is_sortable = True
|
||||||
|
|
@ -50,7 +47,8 @@ class SortableAdminBase(object):
|
||||||
extra_context = {}
|
extra_context = {}
|
||||||
|
|
||||||
extra_context.update({
|
extra_context.update({
|
||||||
'change_list_template_extends': self.change_list_template_extends
|
'change_list_template_extends': self.change_list_template_extends,
|
||||||
|
'sorting_filters': [sort_filter[0] for sort_filter in self.model.sorting_filters]
|
||||||
})
|
})
|
||||||
return super(SortableAdminBase, self).changelist_view(request,
|
return super(SortableAdminBase, self).changelist_view(request,
|
||||||
extra_context=extra_context)
|
extra_context=extra_context)
|
||||||
|
|
@ -106,7 +104,18 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
|
||||||
has_perm = request.user.has_perm('{0}.{1}'.format(opts.app_label,
|
has_perm = request.user.has_perm('{0}.{1}'.format(opts.app_label,
|
||||||
opts.get_change_permission()))
|
opts.get_change_permission()))
|
||||||
|
|
||||||
objects = self.filtered_objects
|
# get sort group index from querystring if present
|
||||||
|
sort_filter_index = request.GET.get('sort_filter')
|
||||||
|
|
||||||
|
filters = {}
|
||||||
|
if sort_filter_index:
|
||||||
|
try:
|
||||||
|
filters = self.model.sorting_filters[int(sort_filter_index)][1]
|
||||||
|
except IndexError, ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Apply any sort filters to create a subset of sortable objects
|
||||||
|
objects = self.queryset(request).filter(**filters)
|
||||||
|
|
||||||
# Determine if we need to regroup objects relative to a
|
# Determine if we need to regroup objects relative to a
|
||||||
# foreign key specified on the model class that is extending Sortable.
|
# foreign key specified on the model class that is extending Sortable.
|
||||||
|
|
@ -191,9 +200,11 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
|
||||||
})
|
})
|
||||||
|
|
||||||
for klass in self.inlines:
|
for klass in self.inlines:
|
||||||
if issubclass(klass, SortableTabularInline) or issubclass(klass, SortableGenericTabularInline):
|
if issubclass(klass, SortableTabularInline) or issubclass(klass,
|
||||||
|
SortableGenericTabularInline):
|
||||||
self.has_sortable_tabular_inlines = True
|
self.has_sortable_tabular_inlines = True
|
||||||
if issubclass(klass, SortableStackedInline) or issubclass(klass, SortableGenericStackedInline):
|
if issubclass(klass, SortableStackedInline) or issubclass(klass,
|
||||||
|
SortableGenericStackedInline):
|
||||||
self.has_sortable_stacked_inlines = True
|
self.has_sortable_stacked_inlines = True
|
||||||
|
|
||||||
if self.has_sortable_tabular_inlines or \
|
if self.has_sortable_tabular_inlines or \
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,6 @@ class MultipleSortableForeignKeyException(Exception):
|
||||||
|
|
||||||
class Sortable(models.Model):
|
class Sortable(models.Model):
|
||||||
"""
|
"""
|
||||||
Unfortunately, Django doesn't support using more than one AutoField
|
|
||||||
in a model or this class could be simplified.
|
|
||||||
|
|
||||||
`is_sortable` determines whether or not the Model is sortable by
|
`is_sortable` determines whether or not the Model is sortable by
|
||||||
determining if the last value of `order` is greater than the default
|
determining if the last value of `order` is greater than the default
|
||||||
of 1, which should be present if there is only one object.
|
of 1, which should be present if there is only one object.
|
||||||
|
|
@ -31,7 +28,7 @@ class Sortable(models.Model):
|
||||||
order = models.PositiveIntegerField(editable=False, default=1,
|
order = models.PositiveIntegerField(editable=False, default=1,
|
||||||
db_index=True)
|
db_index=True)
|
||||||
is_sortable = False
|
is_sortable = False
|
||||||
sorting_filters = {}
|
sorting_filters = ()
|
||||||
|
|
||||||
# legacy support
|
# legacy support
|
||||||
sortable_by = None
|
sortable_by = None
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,14 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block object-tools-items %}
|
{% block object-tools-items %}
|
||||||
<li>
|
{% for sorting_filter in sorting_filters %}
|
||||||
<a href="./sort/">{% trans 'Change Order' %}</a>
|
<li>
|
||||||
</li>
|
<a href="./sort/?sort_filter={{ forloop.counter0 }}">{% trans 'Change Order of' %} {{ sorting_filter }}</a>
|
||||||
{{ block.super }}
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li>
|
||||||
|
<a href="./sort/">{% trans 'Change Order' %}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{{ block.super }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@ admin.site.register(Project, ProjectAdmin)
|
||||||
|
|
||||||
|
|
||||||
class PersonAdmin(SortableAdmin):
|
class PersonAdmin(SortableAdmin):
|
||||||
sortable_change_list_with_sort_link_template = 'app/person/admin/change_list_with_sort_link.html'
|
list_display = ['__unicode__', 'is_board_member']
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Person, PersonAdmin)
|
admin.site.register(Person, PersonAdmin)
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class GenericNote(SimpleModel, Sortable):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u'{0} : {1}'.format(self.title, self.content_object)
|
return u'{}: {}'.format(self.title, self.content_object)
|
||||||
|
|
||||||
|
|
||||||
# An model registered as an inline that has a custom queryset
|
# An model registered as an inline that has a custom queryset
|
||||||
|
|
@ -103,9 +103,16 @@ class Person(Sortable):
|
||||||
|
|
||||||
first_name = models.CharField(max_length=50)
|
first_name = models.CharField(max_length=50)
|
||||||
last_name = models.CharField(max_length=50)
|
last_name = models.CharField(max_length=50)
|
||||||
is_board_member = models.BooleanField(default=False)
|
is_board_member = models.BooleanField('Board Member', default=False)
|
||||||
|
|
||||||
sorting_filters = {'is_board_member': True}
|
# Sorting Filters allow you to set up sub-sets of objects that need
|
||||||
|
# to have independent sorting. They are listed in order, from left
|
||||||
|
# to right in the sorting change list. You can use any standard
|
||||||
|
# Django ORM filter method.
|
||||||
|
sorting_filters = (
|
||||||
|
('Board Members', {'is_board_member': True}),
|
||||||
|
('Non-Board Members', {'is_board_member': False}),
|
||||||
|
)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return '{} {}'.format(self.first_name, self.last_name)
|
return '{} {}'.format(self.first_name, self.last_name)
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,9 +0,0 @@
|
||||||
{% extends change_list_template_extends %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block object-tools-items %}
|
|
||||||
<li>
|
|
||||||
<a href="./sort/">{% trans 'Change Order of Board Members' %}</a>
|
|
||||||
</li>
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock %}
|
|
||||||
Loading…
Reference in New Issue