django-admin-sortable/adminsortable/admin.py

189 lines
8.4 KiB
Python
Raw Normal View History

2011-09-01 00:51:02 +00:00
import json
from django.conf import settings
from django.conf.urls.defaults import patterns, url
from django.contrib.admin import ModelAdmin, TabularInline, StackedInline
from django.contrib.admin.options import InlineModelAdmin
2011-09-01 00:51:02 +00:00
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponse
from django.shortcuts import render
from django.template.defaultfilters import capfirst
from django.views.decorators.csrf import csrf_exempt
from adminsortable.fields import SortableForeignKey
2011-09-01 00:51:02 +00:00
from adminsortable.models import Sortable
STATIC_URL = settings.STATIC_URL
class SortableAdmin(ModelAdmin):
ordering = ('order', 'id')
class Meta:
abstract = True
def _get_sortable_foreign_key(self):
sortable_foreign_key = None
for field in self.model._meta.fields:
if isinstance(field, SortableForeignKey):
sortable_foreign_key = field
break
return sortable_foreign_key
2011-09-01 00:51:02 +00:00
def __init__(self, *args, **kwargs):
super(SortableAdmin, self).__init__(*args, **kwargs)
2011-09-01 00:51:02 +00:00
self.has_sortable_tabular_inlines = False
self.has_sortable_stacked_inlines = False
2011-09-01 00:51:02 +00:00
for klass in self.inlines:
if issubclass(klass, SortableTabularInline):
if klass.model.is_sortable():
self.has_sortable_tabular_inlines = True
if issubclass(klass, SortableStackedInline):
if klass.model.is_sortable():
self.has_sortable_stacked_inlines = True
2011-09-01 00:51:02 +00:00
def get_urls(self):
urls = super(SortableAdmin, self).get_urls()
admin_urls = patterns('',
url(r'^sorting/do-sorting/(?P<model_type_id>\d+)/$',
self.admin_site.admin_view(self.do_sorting_view),
name='admin_do_sorting'), #this view changes the order
url(r'^sort/$', self.admin_site.admin_view(self.sort_view),
name='admin_sort'), #this view shows a link to the drag-and-drop view
)
return admin_urls + urls
def sort_view(self, request):
"""
Custom admin view that displays the objects as a list whose sort order can be
changed via drag-and-drop.
"""
opts = self.model._meta
admin_site = self.admin_site
has_perm = request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
objects = self.model.objects.all()
#Determine if we need to regroup objects relative to a foreign key specified on the
# model class that is extending Sortable.
#Legacy support for 'sortable_by' defined as a model property
sortable_by_property = getattr(self.model, 'sortable_by', None)
#`sortable_by` defined as a SortableForeignKey
sortable_by_fk = self._get_sortable_foreign_key()
if sortable_by_property:
#backwards compatibility for < 1.1.1, where sortable_by was a classmethod instead of a property
try:
sortable_by_class, sortable_by_expression = sortable_by_property()
except TypeError, ValueError:
sortable_by_class = self.model.sortable_by
sortable_by_expression = sortable_by_class.__name__.lower()
2011-09-01 00:51:02 +00:00
sortable_by_class_display_name = sortable_by_class._meta.verbose_name_plural
sortable_by_class_is_sortable = sortable_by_class.is_sortable()
elif sortable_by_fk:
#get sortable by properties from the SortableForeignKey field - supported in 1.3+
sortable_by_class_display_name = sortable_by_fk.rel.to._meta.verbose_name_plural
sortable_by_class = sortable_by_fk.rel.to
sortable_by_expression = sortable_by_fk.name.lower()
sortable_by_class_is_sortable = sortable_by_class.is_sortable()
2011-09-01 00:51:02 +00:00
else:
#model is not sortable by another model
sortable_by_class = sortable_by_expression = sortable_by_class_display_name =\
sortable_by_class_is_sortable = None
2011-09-01 00:51:02 +00:00
if sortable_by_property or sortable_by_fk:
# Order the objects by the property they are sortable by, then by the order, otherwise the regroup
# template tag will not show the objects correctly as
# shown in https://docs.djangoproject.com/en/1.3/ref/templates/builtins/#regroup
objects = objects.order_by(sortable_by_expression, 'order')
2011-09-01 00:51:02 +00:00
try:
verbose_name_plural = opts.verbose_name_plural.__unicode__()
except AttributeError:
verbose_name_plural = opts.verbose_name_plural
context = {
'title' : 'Drag and drop %s to change display order' % capfirst(verbose_name_plural),
'opts' : opts,
'root_path' : '/%s' % admin_site.root_path,
'app_label' : opts.app_label,
'has_perm' : has_perm,
'objects' : objects,
'group_expression' : sortable_by_expression,
'sortable_by_class' : sortable_by_class,
'sortable_by_class_is_sortable' : sortable_by_class_is_sortable,
'sortable_by_class_display_name' : sortable_by_class_display_name
}
return render(request, 'adminsortable/change_list.html', context)
def changelist_view(self, request, extra_context=None):
"""
If the model that inherits Sortable has more than one object,
its sort order can be changed. This view adds a link to the object_tools
block to take people to the view to change the sorting.
"""
if self.model.is_sortable():
self.change_list_template = 'adminsortable/change_list_with_sort_link.html'
return super(SortableAdmin, self).changelist_view(request, extra_context=extra_context)
def change_view(self, request, object_id, extra_context=None):
if self.has_sortable_tabular_inlines or self.has_sortable_stacked_inlines:
2011-09-01 00:51:02 +00:00
self.change_form_template = 'adminsortable/change_form.html'
extra_context = {
'has_sortable_tabular_inlines' : self.has_sortable_tabular_inlines,
'has_sortable_stacked_inlines' : self.has_sortable_stacked_inlines
}
2011-09-01 00:51:02 +00:00
return super(SortableAdmin, self).change_view(request, object_id, extra_context=extra_context)
@csrf_exempt
def do_sorting_view(self, request, model_type_id=None):
"""
This view sets the ordering of the objects for the model type and primary keys
passed in. It must be an Ajax POST.
"""
if request.is_ajax() and request.method == 'POST':
try:
indexes = map(str, request.POST.get('indexes', []).split(','))
2011-09-01 00:51:02 +00:00
klass = ContentType.objects.get(id=model_type_id).model_class()
objects_dict = dict([(str(obj.pk), obj) for obj in klass.objects.filter(pk__in=indexes)])
lowest_ordered_object = min(objects_dict.values(), key=lambda x: getattr(x, 'order'))
min_index = getattr(lowest_ordered_object, 'order') or 0
2011-09-01 00:51:02 +00:00
for index in indexes:
obj = objects_dict.get(index)
2011-09-01 00:51:02 +00:00
setattr(obj, 'order', min_index)
obj.save()
min_index += 1
2011-09-01 00:51:02 +00:00
response = {'objects_sorted' : True}
except (Key, IndexError, klass.DoesNotExist, AttributeError):
2011-09-01 00:51:02 +00:00
pass
else:
response = {'objects_sorted' : False}
return HttpResponse(json.dumps(response, ensure_ascii=False),
mimetype='application/json')
2011-09-01 00:51:02 +00:00
class SortableInlineBase(InlineModelAdmin):
2011-09-01 00:51:02 +00:00
def __init__(self, *args, **kwargs):
super(SortableInlineBase, self).__init__(*args, **kwargs)
2011-09-01 00:51:02 +00:00
if not issubclass(self.model, Sortable):
raise Warning(u'Models that are specified in SortableTabluarInline and SortableStackedInline '
'must inherit from Sortable')
2011-09-01 00:51:02 +00:00
self.is_sortable = self.model.is_sortable()
class SortableTabularInline(SortableInlineBase, TabularInline):
"""Custom template that enables sorting for tabular inlines"""
2011-09-01 00:51:02 +00:00
template = 'adminsortable/edit_inline/tabular.html'
class SortableStackedInline(SortableInlineBase, StackedInline):
"""Custom template that enables sorting for stacked inlines"""
template = 'adminsortable/edit_inline/stacked.html'