Added properties to allow sortable change list and change form view to extend a custom template or extend the default admin change form and change list templates.
Fixed a problem with the detection of sortability of inline models at the template level that was preventing the drag-and-drop messaging in the template to be displayed. Updated unit tests.master
parent
32c6f7c034
commit
847b471872
|
|
@ -40,6 +40,13 @@ class SortableAdminBase(object):
|
||||||
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
|
||||||
|
|
||||||
|
if extra_context is None:
|
||||||
|
extra_context = {}
|
||||||
|
|
||||||
|
extra_context.update({
|
||||||
|
'change_list_template_extends': self.change_list_template_extends
|
||||||
|
})
|
||||||
return super(SortableAdminBase, self).changelist_view(request,
|
return super(SortableAdminBase, self).changelist_view(request,
|
||||||
extra_context=extra_context)
|
extra_context=extra_context)
|
||||||
|
|
||||||
|
|
@ -58,6 +65,9 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
|
||||||
sortable_javascript_includes_template = \
|
sortable_javascript_includes_template = \
|
||||||
'adminsortable/shared/javascript_includes.html'
|
'adminsortable/shared/javascript_includes.html'
|
||||||
|
|
||||||
|
change_form_template_extends = 'admin/change_form.html'
|
||||||
|
change_list_template_extends = 'admin/change_list.html'
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
@ -162,6 +172,13 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
|
||||||
self.has_sortable_tabular_inlines = False
|
self.has_sortable_tabular_inlines = False
|
||||||
self.has_sortable_stacked_inlines = False
|
self.has_sortable_stacked_inlines = False
|
||||||
|
|
||||||
|
if extra_context is None:
|
||||||
|
extra_context = {}
|
||||||
|
|
||||||
|
extra_context.update({
|
||||||
|
'change_form_template_extends': self.change_form_template_extends
|
||||||
|
})
|
||||||
|
|
||||||
for klass in self.inlines:
|
for klass in self.inlines:
|
||||||
is_sortable = klass.model.is_sortable
|
is_sortable = klass.model.is_sortable
|
||||||
if issubclass(klass, SortableTabularInline) and is_sortable:
|
if issubclass(klass, SortableTabularInline) and is_sortable:
|
||||||
|
|
@ -171,15 +188,18 @@ class SortableAdmin(SortableAdminBase, ModelAdmin):
|
||||||
|
|
||||||
if self.has_sortable_tabular_inlines or \
|
if self.has_sortable_tabular_inlines or \
|
||||||
self.has_sortable_stacked_inlines:
|
self.has_sortable_stacked_inlines:
|
||||||
|
|
||||||
self.change_form_template = self.sortable_change_form_template
|
self.change_form_template = self.sortable_change_form_template
|
||||||
extra_context = {
|
|
||||||
|
extra_context.update({
|
||||||
'sortable_javascript_includes_template':
|
'sortable_javascript_includes_template':
|
||||||
self.sortable_javascript_includes_template,
|
self.sortable_javascript_includes_template,
|
||||||
'has_sortable_tabular_inlines':
|
'has_sortable_tabular_inlines':
|
||||||
self.has_sortable_tabular_inlines,
|
self.has_sortable_tabular_inlines,
|
||||||
'has_sortable_stacked_inlines':
|
'has_sortable_stacked_inlines':
|
||||||
self.has_sortable_stacked_inlines
|
self.has_sortable_stacked_inlines
|
||||||
}
|
})
|
||||||
|
|
||||||
return super(SortableAdmin, self).change_view(request, object_id,
|
return super(SortableAdmin, self).change_view(request, object_id,
|
||||||
extra_context=extra_context)
|
extra_context=extra_context)
|
||||||
|
|
||||||
|
|
@ -238,21 +258,21 @@ class SortableInlineBase(SortableAdminBase, InlineModelAdmin):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class SortableTabularInline(SortableInlineBase, TabularInline):
|
class SortableTabularInline(TabularInline, SortableInlineBase):
|
||||||
"""Custom template that enables sorting for tabular inlines"""
|
"""Custom template that enables sorting for tabular inlines"""
|
||||||
template = 'adminsortable/edit_inline/tabular.html'
|
template = 'adminsortable/edit_inline/tabular.html'
|
||||||
|
|
||||||
|
|
||||||
class SortableStackedInline(SortableInlineBase, StackedInline):
|
class SortableStackedInline(StackedInline, SortableInlineBase):
|
||||||
"""Custom template that enables sorting for stacked inlines"""
|
"""Custom template that enables sorting for stacked inlines"""
|
||||||
template = 'adminsortable/edit_inline/stacked.html'
|
template = 'adminsortable/edit_inline/stacked.html'
|
||||||
|
|
||||||
|
|
||||||
class SortableGenericTabularInline(SortableInlineBase, GenericTabularInline):
|
class SortableGenericTabularInline(GenericTabularInline, SortableInlineBase):
|
||||||
"""Custom template that enables sorting for tabular inlines"""
|
"""Custom template that enables sorting for tabular inlines"""
|
||||||
template = 'adminsortable/edit_inline/tabular.html'
|
template = 'adminsortable/edit_inline/tabular.html'
|
||||||
|
|
||||||
|
|
||||||
class SortableGenericStackedInline(SortableInlineBase, GenericStackedInline):
|
class SortableGenericStackedInline(GenericStackedInline, SortableInlineBase):
|
||||||
"""Custom template that enables sorting for stacked inlines"""
|
"""Custom template that enables sorting for stacked inlines"""
|
||||||
template = 'adminsortable/edit_inline/stacked.html'
|
template = 'adminsortable/edit_inline/stacked.html'
|
||||||
|
|
|
||||||
|
|
@ -39,19 +39,6 @@ class Sortable(models.Model):
|
||||||
abstract = True
|
abstract = True
|
||||||
ordering = ['order']
|
ordering = ['order']
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# def determine_if_sortable(cls):
|
|
||||||
# try:
|
|
||||||
# max_order = cls.objects.aggregate(
|
|
||||||
# models.Max('order'))['order__max']
|
|
||||||
# except (TypeError, IndexError):
|
|
||||||
# max_order = 0
|
|
||||||
|
|
||||||
# if max_order > 1:
|
|
||||||
# cls.is_sortable = True
|
|
||||||
# else:
|
|
||||||
# cls.is_sortable = False
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def model_type_id(cls):
|
def model_type_id(cls):
|
||||||
return ContentType.objects.get_for_model(cls).id
|
return ContentType.objects.get_for_model(cls).id
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "admin/change_form.html" %}
|
{% extends change_form_template_extends %}
|
||||||
{% load i18n admin_modify %}
|
{% load i18n admin_modify %}
|
||||||
{% load url from future %}
|
{% load url from future %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'admin/change_list.html' %}
|
{% extends change_list_template_extends %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block object-tools-items %}
|
{% block object-tools-items %}
|
||||||
|
|
@ -6,4 +6,4 @@
|
||||||
<a href="./sort/">{% trans 'Change Order' %}</a>
|
<a href="./sort/">{% trans 'Change Order' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{% load i18n admin_modify adminsortable_tags %}
|
{% load i18n admin_modify adminsortable_tags %}
|
||||||
<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
|
<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>
|
<h2>{{ inline_admin_formset.opts.verbose_name_plural|title }} {% if inline_admin_formset.formset.initial_form_count > 1 %} - drag and drop to change order{% endif %}</h2>
|
||||||
{{ inline_admin_formset.formset.management_form }}
|
{{ inline_admin_formset.formset.management_form }}
|
||||||
{{ inline_admin_formset.formset.non_form_errors }}
|
{{ inline_admin_formset.formset.non_form_errors }}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
|
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
|
||||||
{{ inline_admin_formset.formset.management_form }}
|
{{ inline_admin_formset.formset.management_form }}
|
||||||
<fieldset class="module">
|
<fieldset class="module">
|
||||||
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }} {% if inline_admin_formset.opts.is_sortable %} - drag and drop to change order{% endif %}</h2>
|
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }} {% if inline_admin_formset.formset.initial_form_count > 1 %} - drag and drop to change order{% endif %}</h2>
|
||||||
{{ inline_admin_formset.formset.non_form_errors }}
|
{{ inline_admin_formset.formset.non_form_errors }}
|
||||||
<table>
|
<table>
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ class ComponentInline(SortableStackedInline):
|
||||||
model = Component
|
model = Component
|
||||||
|
|
||||||
def queryset(self, request):
|
def queryset(self, request):
|
||||||
qs = super(ComponentInline, self).queryset(request).exclude(title__icontains='2')
|
qs = super(ComponentInline, self).queryset(
|
||||||
|
request).exclude(title__icontains='2')
|
||||||
if get_is_sortable(qs):
|
if get_is_sortable(qs):
|
||||||
self.model.is_sortable = True
|
self.model.is_sortable = True
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from django.test.client import Client, RequestFactory
|
||||||
|
|
||||||
from adminsortable.fields import SortableForeignKey
|
from adminsortable.fields import SortableForeignKey
|
||||||
from adminsortable.models import Sortable
|
from adminsortable.models import Sortable
|
||||||
|
from adminsortable.utils import get_is_sortable
|
||||||
from app.models import Category, Credit, Note
|
from app.models import Category, Credit, Note
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,13 +53,13 @@ class SortableTestCase(TestCase):
|
||||||
record to sort.
|
record to sort.
|
||||||
"""
|
"""
|
||||||
self.create_category()
|
self.create_category()
|
||||||
self.assertFalse(Category.is_sortable(),
|
self.assertFalse(get_is_sortable(Category.objects.all()),
|
||||||
'Category only has one record. It should not be sortable.')
|
'Category only has one record. It should not be sortable.')
|
||||||
|
|
||||||
def test_is_sortable(self):
|
def test_is_sortable(self):
|
||||||
self.create_category()
|
self.create_category()
|
||||||
self.create_category(title='Category 2')
|
self.create_category(title='Category 2')
|
||||||
self.assertTrue(Category.is_sortable(),
|
self.assertTrue(get_is_sortable(Category.objects.all()),
|
||||||
'Category has more than one record. It should be sortable.')
|
'Category has more than one record. It should be sortable.')
|
||||||
|
|
||||||
def test_save_order_incremented(self):
|
def test_save_order_incremented(self):
|
||||||
|
|
@ -82,8 +83,8 @@ class SortableTestCase(TestCase):
|
||||||
return category1, category2, category3
|
return category1, category2, category3
|
||||||
|
|
||||||
def get_sorting_url(self):
|
def get_sorting_url(self):
|
||||||
return reverse('admin:app_do_sorting', args=(),
|
return '/admin/app/category/sorting/do-sorting/{0}/'.format(
|
||||||
kwargs={'model_type_id': Category.model_type_id()})
|
Category.model_type_id())
|
||||||
|
|
||||||
def get_category_indexes(self, *categories):
|
def get_category_indexes(self, *categories):
|
||||||
return {'indexes': ','.join([str(c.id) for c in categories])}
|
return {'indexes': ','.join([str(c.id) for c in categories])}
|
||||||
|
|
@ -93,7 +94,7 @@ class SortableTestCase(TestCase):
|
||||||
password=self.user_raw_password)
|
password=self.user_raw_password)
|
||||||
self.assertTrue(logged_in, 'User is not logged in')
|
self.assertTrue(logged_in, 'User is not logged in')
|
||||||
|
|
||||||
response = self.client.get(reverse('admin:app_sort'))
|
response = self.client.get('/admin/app/category/sort/')
|
||||||
self.assertEqual(response.status_code, httplib.OK,
|
self.assertEqual(response.status_code, httplib.OK,
|
||||||
'Admin sort request failed.')
|
'Admin sort request failed.')
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue