Minor modifications for Django 1.5.x and 1.4.x backward-compatibility.
Added new sample project. Improved documentation. Refactored CSS selector for inlines that are sortable.
This commit is contained in:
+78
-42
@@ -1,6 +1,15 @@
|
||||
import json
|
||||
|
||||
from django import VERSION as DJANGO_VERSION
|
||||
DJANGO_MINOR_VERSION = DJANGO_VERSION[1]
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
if DJANGO_MINOR_VERSION < 5:
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
else:
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from django.contrib.admin import ModelAdmin, TabularInline, StackedInline
|
||||
from django.contrib.admin.options import InlineModelAdmin
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@@ -17,15 +26,17 @@ STATIC_URL = settings.STATIC_URL
|
||||
|
||||
class SortableAdmin(ModelAdmin):
|
||||
"""
|
||||
Admin class to add template overrides and context objects to enable drag-and-drop
|
||||
ordering.
|
||||
Admin class to add template overrides and context objects to enable
|
||||
drag-and-drop ordering.
|
||||
"""
|
||||
ordering = ('order', 'id')
|
||||
|
||||
sortable_change_list_with_sort_link_template = 'adminsortable/change_list_with_sort_link.html'
|
||||
sortable_change_list_with_sort_link_template = \
|
||||
'adminsortable/change_list_with_sort_link.html'
|
||||
sortable_change_form_template = 'adminsortable/change_form.html'
|
||||
sortable_change_list_template = 'adminsortable/change_list.html'
|
||||
sortable_javascript_includes_template = 'adminsortable/shared/javascript_includes.html'
|
||||
sortable_javascript_includes_template = \
|
||||
'adminsortable/shared/javascript_includes.html'
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@@ -54,25 +65,30 @@ class SortableAdmin(ModelAdmin):
|
||||
def get_urls(self):
|
||||
urls = super(SortableAdmin, self).get_urls()
|
||||
admin_urls = patterns('',
|
||||
|
||||
# this view changes the order
|
||||
url(r'^sorting/do-sorting/(?P<model_type_id>\d+)/$',
|
||||
self.admin_site.admin_view(self.do_sorting_view),
|
||||
name='{0}_do_sorting'.format(self.model._meta.app_label)), # this view changes the order
|
||||
name='{0}_do_sorting'.format(self.model._meta.app_label)),
|
||||
|
||||
# this view shows a link to the drag-and-drop view
|
||||
url(r'^sort/$', self.admin_site.admin_view(self.sort_view),
|
||||
name='{0}_sort'.format(self.model._meta.app_label)), # this view shows a link to the drag-and-drop view
|
||||
name='{0}_sort'.format(self.model._meta.app_label)),
|
||||
)
|
||||
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.
|
||||
Custom admin view that displays the objects as a list whose sort
|
||||
order can be changed via drag-and-drop.
|
||||
"""
|
||||
opts = self.model._meta
|
||||
has_perm = request.user.has_perm('{0}.{1}'.format(opts.app_label, opts.get_change_permission()))
|
||||
has_perm = request.user.has_perm('{0}.{1}'.format(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.
|
||||
# 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)
|
||||
|
||||
@@ -80,19 +96,24 @@ class SortableAdmin(ModelAdmin):
|
||||
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
|
||||
# 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()
|
||||
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()
|
||||
|
||||
sortable_by_class_display_name = sortable_by_class._meta.verbose_name_plural
|
||||
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
|
||||
# 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()
|
||||
try:
|
||||
@@ -102,13 +123,14 @@ class SortableAdmin(ModelAdmin):
|
||||
|
||||
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
|
||||
sortable_by_class = sortable_by_expression = \
|
||||
sortable_by_class_display_name = \
|
||||
sortable_by_class_is_sortable = None
|
||||
|
||||
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
|
||||
# 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
|
||||
objects = objects.order_by(sortable_by_expression, 'order')
|
||||
|
||||
try:
|
||||
@@ -117,7 +139,8 @@ class SortableAdmin(ModelAdmin):
|
||||
verbose_name_plural = opts.verbose_name_plural
|
||||
|
||||
context = {
|
||||
'title': 'Drag and drop %s to change display order' % capfirst(verbose_name_plural),
|
||||
'title': 'Drag and drop {0} to change display order'.format(
|
||||
capfirst(verbose_name_plural)),
|
||||
'opts': opts,
|
||||
'app_label': opts.app_label,
|
||||
'has_perm': has_perm,
|
||||
@@ -126,47 +149,59 @@ class SortableAdmin(ModelAdmin):
|
||||
'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,
|
||||
'sortable_javascript_includes_template': self.sortable_javascript_includes_template
|
||||
'sortable_javascript_includes_template':
|
||||
self.sortable_javascript_includes_template
|
||||
}
|
||||
return render(request, self.sortable_change_list_template, 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.
|
||||
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 = self.sortable_change_list_with_sort_link_template
|
||||
return super(SortableAdmin, self).changelist_view(request, extra_context=extra_context)
|
||||
self.change_list_template = \
|
||||
self.sortable_change_list_with_sort_link_template
|
||||
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:
|
||||
if self.has_sortable_tabular_inlines or \
|
||||
self.has_sortable_stacked_inlines:
|
||||
self.change_form_template = self.sortable_change_form_template
|
||||
extra_context = {
|
||||
'sortable_javascript_includes_template': self.sortable_javascript_includes_template,
|
||||
'has_sortable_tabular_inlines': self.has_sortable_tabular_inlines,
|
||||
'has_sortable_stacked_inlines': self.has_sortable_stacked_inlines
|
||||
'sortable_javascript_includes_template':
|
||||
self.sortable_javascript_includes_template,
|
||||
'has_sortable_tabular_inlines':
|
||||
self.has_sortable_tabular_inlines,
|
||||
'has_sortable_stacked_inlines':
|
||||
self.has_sortable_stacked_inlines
|
||||
}
|
||||
return super(SortableAdmin, self).change_view(request, object_id, extra_context=extra_context)
|
||||
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.
|
||||
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(','))
|
||||
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)])
|
||||
objects_dict = dict([(str(obj.pk), obj) for obj in
|
||||
klass.objects.filter(pk__in=indexes)])
|
||||
if '-order' in klass._meta.ordering: # desc order
|
||||
start_object = max(objects_dict.values(), key=lambda x: getattr(x, 'order'))
|
||||
start_index = getattr(start_object, 'order') or len(indexes)
|
||||
start_object = max(objects_dict.values(),
|
||||
key=lambda x: getattr(x, 'order'))
|
||||
start_index = getattr(start_object, 'order') \
|
||||
or len(indexes)
|
||||
step = -1
|
||||
else: # 'order' is default, asc order
|
||||
start_object = min(objects_dict.values(), key=lambda x: getattr(x, 'order'))
|
||||
start_object = min(objects_dict.values(),
|
||||
key=lambda x: getattr(x, 'order'))
|
||||
start_index = getattr(start_object, 'order') or 0
|
||||
step = 1
|
||||
|
||||
@@ -180,7 +215,8 @@ class SortableAdmin(ModelAdmin):
|
||||
pass
|
||||
else:
|
||||
response = {'objects_sorted': False}
|
||||
return HttpResponse(json.dumps(response, ensure_ascii=False), mimetype='application/json')
|
||||
return HttpResponse(json.dumps(response, ensure_ascii=False),
|
||||
mimetype='application/json')
|
||||
|
||||
|
||||
class SortableInlineBase(InlineModelAdmin):
|
||||
@@ -188,8 +224,8 @@ class SortableInlineBase(InlineModelAdmin):
|
||||
super(SortableInlineBase, self).__init__(*args, **kwargs)
|
||||
|
||||
if not issubclass(self.model, Sortable):
|
||||
raise Warning(u'Models that are specified in SortableTabluarInline and SortableStackedInline '
|
||||
'must inherit from Sortable')
|
||||
raise Warning(u'Models that are specified in SortableTabluarInline'
|
||||
' and SortableStackedInline must inherit from Sortable')
|
||||
|
||||
self.is_sortable = self.model.is_sortable()
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.tabular .sortable:not(.add-row) {
|
||||
.sortable.has_original {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
|
||||
{% block content_title %}
|
||||
<h1>
|
||||
{% if sort_type %}
|
||||
{% blocktrans with opts.verbose_name_plural|capfirst as model %}Drag and drop {{ sort_type }} {{ model }} to change their order.{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with opts.verbose_name_plural|capfirst as model %}Drag and drop {{ model }} to change their order.{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% if sort_type %}
|
||||
{% blocktrans with opts.verbose_name_plural|capfirst as model %}Drag and drop {{ sort_type }} {{ model }} to change their order.{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with opts.verbose_name_plural|capfirst as model %}Drag and drop {{ model }} to change their order.{% endblocktrans %}
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% if sortable_by_class.is_sortable %}
|
||||
<p>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{% load i18n adminmedia adminsortable_tags %}
|
||||
{% load i18n admin_modify adminsortable_tags %}
|
||||
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
|
||||
<h2>{{ inline_admin_formset.opts.verbose_name_plural|title }} {% if inline_admin_formset.opts.is_sortable %} - drag and drop to change order{% endif %}</h2>
|
||||
{{ inline_admin_formset.formset.management_form }}
|
||||
{{ inline_admin_formset.formset.non_form_errors }}
|
||||
|
||||
{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if forloop.last %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
|
||||
{% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if inline_admin_form.original %} has_original{% endif %}{% if forloop.last %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
|
||||
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b> <span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span>
|
||||
{% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
|
||||
{% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
|
||||
@@ -16,7 +16,7 @@
|
||||
{% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
|
||||
{{ inline_admin_form.fk_field.field }}
|
||||
{% if inline_admin_form.original %}
|
||||
<input type="hidden" name="admin_sorting_url" value="{% url admin:admin_do_sorting inline_admin_form.original.model_type_id %}" />
|
||||
<input type="hidden" name="admin_sorting_url" value="{% get_do_sorting_url inline_admin_form.original %}" />
|
||||
{% endif %}
|
||||
</div>{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<tr class="{% cycle "row1" "row2" %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last %} empty-form{% endif %}"
|
||||
id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
|
||||
<td class="original">
|
||||
|
||||
|
||||
{% if inline_admin_form.original or inline_admin_form.show_url %}<p>
|
||||
{% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}
|
||||
{% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
|
||||
@@ -40,7 +40,7 @@
|
||||
{% endfor %}
|
||||
{% endspaceless %}
|
||||
{% if inline_admin_form.original %}
|
||||
<input type="hidden" name="admin_sorting_url" value="{% url admin:admin_do_sorting inline_admin_form.original.model_type_id %}" />
|
||||
<input type="hidden" name="admin_sorting_url" value="{% get_do_sorting_url inline_admin_form.original %}" />
|
||||
{% endif %}
|
||||
</td>
|
||||
{% for fieldset in inline_admin_form %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django import template
|
||||
from django import VERSION as DJANGO_VERSION
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
register = template.Library()
|
||||
@@ -38,5 +39,5 @@ def render_object_rep(context, obj,
|
||||
|
||||
@register.simple_tag(takes_context=False)
|
||||
def get_do_sorting_url(obj):
|
||||
return reverse('admin:%s_do_sorting' % obj._meta.app_label,
|
||||
kwargs={'model_type_id': obj.model_type_id()})
|
||||
return reverse('admin:{0}_do_sorting'.format(obj._meta.app_label),
|
||||
kwargs={'model_type_id': obj.model_type_id()})
|
||||
|
||||
Reference in New Issue
Block a user