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
Brandon Taylor 2013-05-03 08:35:17 -04:00
parent 32c6f7c034
commit 847b471872
9 changed files with 39 additions and 30 deletions

View File

@ -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'

View File

@ -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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 }}

View File

@ -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>

View File

@ -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:

View File

@ -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.')