diff --git a/adminsortable/admin.py b/adminsortable/admin.py index 031ac0b..2def272 100755 --- a/adminsortable/admin.py +++ b/adminsortable/admin.py @@ -27,8 +27,9 @@ from django.http import HttpResponse from django.shortcuts import render from django.template.defaultfilters import capfirst -from adminsortable.utils import get_is_sortable, check_model_is_sortable +from adminsortable.fields import SortableForeignKey from adminsortable.models import SortableMixin +from adminsortable.utils import get_is_sortable, check_model_is_sortable STATIC_URL = settings.STATIC_URL @@ -145,9 +146,17 @@ class SortableAdmin(SortableAdminBase, ModelAdmin): # 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.model.sortable_foreign_key - sortable_by_class_is_sortable = check_model_is_sortable(sortable_by_fk) + # see if our model is sortable by a SortableForeignKey field + # and that the number of objects available is >= 2 + sortable_by_fk = None + sortable_by_field_name = None + sortable_by_class_is_sortable = False + + for field in self.model._meta.fields: + if isinstance(field, SortableForeignKey): + sortable_by_fk = field.rel.to + sortable_by_field_name = field.name.lower() + sortable_by_class_is_sortable = sortable_by_fk.objects.count() >= 2 if sortable_by_property: # backwards compatibility for < 1.1.1, where sortable_by was a @@ -165,10 +174,9 @@ class SortableAdmin(SortableAdminBase, ModelAdmin): 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_display_name = sortable_by_fk._meta.verbose_name_plural + sortable_by_class = sortable_by_fk + sortable_by_expression = sortable_by_field_name else: # model is not sortable by another model diff --git a/adminsortable/static/adminsortable/css/admin.sortable.css b/adminsortable/static/adminsortable/css/admin.sortable.css index 7af1b1f..e03ef81 100644 --- a/adminsortable/static/adminsortable/css/admin.sortable.css +++ b/adminsortable/static/adminsortable/css/admin.sortable.css @@ -42,7 +42,16 @@ margin-bottom: 0; } +.sortable-help-text { + color: #999; + font-size: 13px; +} + .sortable a:hover { color: #003366; } + +.sortable .fa { + margin-right: 7px; +} diff --git a/adminsortable/static/adminsortable/css/admin.sortable.inline.css b/adminsortable/static/adminsortable/css/admin.sortable.inline.css index 44f7e70..cbde4e8 100644 --- a/adminsortable/static/adminsortable/css/admin.sortable.inline.css +++ b/adminsortable/static/adminsortable/css/admin.sortable.inline.css @@ -1,3 +1,17 @@ .sortable.has_original { cursor: move; } + +.sortable .inline-related .module.aligned .fa, +.sortable.inline-group .module .fa { + display: block; + float: left; +} + +.sortable .inline-related .module.aligned .fa { + margin: 9px 10px 0 0; +} + +.sortable.inline-group .module .fa { + margin: 34px -10px 0 10px; +} diff --git a/adminsortable/static/adminsortable/js/admin.sortable.js b/adminsortable/static/adminsortable/js/admin.sortable.js index b854f52..cca28cb 100644 --- a/adminsortable/static/adminsortable/js/admin.sortable.js +++ b/adminsortable/static/adminsortable/js/admin.sortable.js @@ -8,17 +8,36 @@ items : 'li', stop : function(event, ui) { - var indexes = []; - ui.item.parent().children('li').each(function(i) + var indexes = [], + lineItems = ui.item.parent().find('> li'); + + lineItems.each(function(i) { indexes.push($(this).find(':hidden[name="pk"]').val()); }); + $.ajax({ url: ui.item.find('a.admin_sorting_url').attr('href'), type: 'POST', data: { indexes: indexes.join(',') }, success: function() { + // set icons based on position + lineItems.each(function(index, element) { + var icon = $(element).find('> a .fa'); + icon.removeClass('fa-sort-desc fa-sort-asc fa-sort'); + + if (index === 0) { + icon.addClass('fa fa-sort-desc'); + } + else if (index == lineItems.length - 1) { + icon.addClass('fa fa-sort-asc'); + } + else { + icon.addClass('fa fa-sort'); + } + }); + ui.item.effect('highlight', {}, 1000); } }); diff --git a/adminsortable/static/adminsortable/js/admin.sortable.stacked.inlines.js b/adminsortable/static/adminsortable/js/admin.sortable.stacked.inlines.js index 3b32b7e..4afbea3 100644 --- a/adminsortable/static/adminsortable/js/admin.sortable.stacked.inlines.js +++ b/adminsortable/static/adminsortable/js/admin.sortable.stacked.inlines.js @@ -41,7 +41,24 @@ data: { indexes : indexes.join(',') }, success: function() { var fieldsets = ui.item.find('fieldset'), - highlightedSelector = fieldsets.filter('.collapsed').length === fieldsets.length ? 'h3' : '.form-row'; + highlightedSelector = fieldsets.filter('.collapsed').length === fieldsets.length ? 'h3' : '.form-row', + icons = ui.item.parent().find(highlightedSelector).find('.fa'); + + // set icons based on position + icons.removeClass('fa-sort-desc fa-sort-asc fa-sort'); + icons.each(function(index, element) { + var icon = $(element); + if (index === 0) { + icon.addClass('fa fa-sort-desc'); + } + else if (index == icons.length - 1) { + icon.addClass('fa fa-sort-asc'); + } + else { + icon.addClass('fa fa-sort'); + } + }); + ui.item.find(highlightedSelector).effect('highlight', {}, 1000); } }); diff --git a/adminsortable/static/adminsortable/js/admin.sortable.tabular.inlines.js b/adminsortable/static/adminsortable/js/admin.sortable.tabular.inlines.js index 44c9fe8..debd654 100644 --- a/adminsortable/static/adminsortable/js/admin.sortable.tabular.inlines.js +++ b/adminsortable/static/adminsortable/js/admin.sortable.tabular.inlines.js @@ -38,7 +38,23 @@ type: 'POST', data: { indexes : indexes.join(',') }, success: function() { - //highlight sorted row, then re-stripe table + // set icons based on position + var icons = ui.item.parent().find('.fa'); + icons.removeClass('fa-sort-desc fa-sort-asc fa-sort'); + icons.each(function(index, element) { + var icon = $(element); + if (index === 0) { + icon.addClass('fa fa-sort-desc'); + } + else if (index == icons.length - 1) { + icon.addClass('fa fa-sort-asc'); + } + else { + icon.addClass('fa fa-sort'); + } + }); + + // highlight sorted row, then re-stripe table ui.item.effect('highlight', {}, 1000); tabular_inline_rows.removeClass('row1 row2'); $('.tabular table tbody tr:odd').addClass('row2'); diff --git a/adminsortable/templates/adminsortable/change_form.html b/adminsortable/templates/adminsortable/change_form.html index 3a4e922..0f3fd76 100644 --- a/adminsortable/templates/adminsortable/change_form.html +++ b/adminsortable/templates/adminsortable/change_form.html @@ -24,6 +24,7 @@ {{ block.super }} {% if has_sortable_tabular_inlines or has_sortable_stacked_inlines %} + {% endif %} {% endblock %} diff --git a/adminsortable/templates/adminsortable/change_list.html b/adminsortable/templates/adminsortable/change_list.html index f8dfbc9..7b6e83e 100644 --- a/adminsortable/templates/adminsortable/change_list.html +++ b/adminsortable/templates/adminsortable/change_list.html @@ -4,6 +4,7 @@ {% block extrastyle %} {{ block.super }} + {% endblock %} @@ -40,8 +41,8 @@ {% blocktrans with opts.verbose_name_plural|capfirst as model %}Drag and drop {{ model }} to change their order.{% endblocktrans %} {% endif %} - {% if sortable_by_class.is_sortable %} -

+ {% if sortable_by_class_is_sortable %} +

{% blocktrans %}You may also drag and drop {{ sortable_by_class_display_name }} to change their order.{% endblocktrans %}

{% endif %} diff --git a/adminsortable/templates/adminsortable/edit_inline/stacked-1.5.x.html b/adminsortable/templates/adminsortable/edit_inline/stacked-1.5.x.html index bc7ce69..c133fe6 100644 --- a/adminsortable/templates/adminsortable/edit_inline/stacked-1.5.x.html +++ b/adminsortable/templates/adminsortable/edit_inline/stacked-1.5.x.html @@ -12,7 +12,7 @@ {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} {% for fieldset in inline_admin_form %} - {% include "admin/includes/fieldset.html" %} + {% include "admin/includes/fieldset.html" with inline_admin_form_forloop=forloop.parentloop %} {% endfor %} {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %} {{ inline_admin_form.fk_field.field }} diff --git a/adminsortable/templates/adminsortable/edit_inline/stacked.html b/adminsortable/templates/adminsortable/edit_inline/stacked.html index 0bf7d7b..ff45712 100644 --- a/adminsortable/templates/adminsortable/edit_inline/stacked.html +++ b/adminsortable/templates/adminsortable/edit_inline/stacked.html @@ -12,13 +12,13 @@ {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} {% for fieldset in inline_admin_form %} - {% include "admin/includes/fieldset.html" %} + {% include "adminsortable/shared/fieldset.html" with inline_admin_form_forloop=forloop.parentloop initial_forms_count=inline_admin_formset.formset.management_form.initial.INITIAL_FORMS %} {% endfor %} - {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %} - {{ inline_admin_form.fk_field.field }} {% if inline_admin_form.original %} {% endif %} + {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %} + {{ inline_admin_form.fk_field.field }} {% endfor %} diff --git a/adminsortable/templates/adminsortable/edit_inline/tabular-1.5.x.html b/adminsortable/templates/adminsortable/edit_inline/tabular-1.5.x.html index 34555ee..30f1772 100644 --- a/adminsortable/templates/adminsortable/edit_inline/tabular-1.5.x.html +++ b/adminsortable/templates/adminsortable/edit_inline/tabular-1.5.x.html @@ -48,6 +48,7 @@ {% for line in fieldset %} {% for field in line %} + {% if inline_admin_form.original and forloop.parentloop.counter == 1 %}{% endif %} {% if field.is_readonly %}

{{ field.contents }}

{% else %} diff --git a/adminsortable/templates/adminsortable/edit_inline/tabular.html b/adminsortable/templates/adminsortable/edit_inline/tabular.html index 247818e..9554f0d 100644 --- a/adminsortable/templates/adminsortable/edit_inline/tabular.html +++ b/adminsortable/templates/adminsortable/edit_inline/tabular.html @@ -25,6 +25,11 @@ + {% with initial_forms=inline_admin_form.formset.management_form.initial.INITIAL_FORMS %} + {% if forloop.counter <= initial_forms %} + + {% endif %} + {% endwith %} {% if inline_admin_form.original or inline_admin_form.show_url %}

{% if inline_admin_form.original %} {{ inline_admin_form.original }} diff --git a/adminsortable/templates/adminsortable/shared/fieldset.html b/adminsortable/templates/adminsortable/shared/fieldset.html new file mode 100644 index 0000000..89659ae --- /dev/null +++ b/adminsortable/templates/adminsortable/shared/fieldset.html @@ -0,0 +1,35 @@ +{# overrides admin/includes/fieldset.html: https://github.com/django/django/blob/master/django/contrib/admin/templates/admin/includes/fieldset.html #} +

+ {% if fieldset.name %}

{{ fieldset.name }}

{% endif %} + {% if fieldset.description %} +
{{ fieldset.description|safe }}
+ {% endif %} + {% for line in fieldset %} +
+ {% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %} + {% for field in line %} + + {% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %} + {% if forloop.first %} + {% if inline_admin_form_forloop.counter <= initial_forms_count %} + + {% endif %} + {% endif %} + {% if field.is_checkbox %} + {{ field.field }}{{ field.label_tag }} + {% else %} + {{ field.label_tag }} + {% if field.is_readonly %} +

{{ field.contents }}

+ {% else %} + {{ field.field }} + {% endif %} + {% endif %} + {% if field.field.help_text %} +

{{ field.field.help_text|safe }}

+ {% endif %} +
+ {% endfor %} + + {% endfor %} +
diff --git a/adminsortable/templates/adminsortable/shared/list_items.html b/adminsortable/templates/adminsortable/shared/list_items.html index bfc21a6..29df030 100644 --- a/adminsortable/templates/adminsortable/shared/list_items.html +++ b/adminsortable/templates/adminsortable/shared/list_items.html @@ -3,7 +3,7 @@ {% for object in list_objects %}
  • {% if list_objects_length > 1 %} - {% render_object_rep object %} + {% render_object_rep object forloop %} {% else %} {{ object }} {% endif %} diff --git a/adminsortable/templates/adminsortable/shared/nested_objects.html b/adminsortable/templates/adminsortable/shared/nested_objects.html index 6774a60..8ef74d9 100644 --- a/adminsortable/templates/adminsortable/shared/nested_objects.html +++ b/adminsortable/templates/adminsortable/shared/nested_objects.html @@ -5,8 +5,8 @@ {% for regrouped_object in regrouped_objects %} {% with object=regrouped_object.grouper %} {% if object %} -
  • {% if sortable_by_class_is_sortable %} - {% render_object_rep object %} +
  • {% if sortable_by_class_is_sortable %} + {% render_object_rep object forloop %} {% else %} {{ object }} {% endif %} diff --git a/adminsortable/templates/adminsortable/shared/object_rep.html b/adminsortable/templates/adminsortable/shared/object_rep.html index 05dfb16..ffe9edf 100644 --- a/adminsortable/templates/adminsortable/shared/object_rep.html +++ b/adminsortable/templates/adminsortable/shared/object_rep.html @@ -3,4 +3,4 @@
    -{{ object }} + {{ object }} diff --git a/adminsortable/templatetags/adminsortable_tags.py b/adminsortable/templatetags/adminsortable_tags.py index aae2b6c..bbed9a0 100644 --- a/adminsortable/templatetags/adminsortable_tags.py +++ b/adminsortable/templatetags/adminsortable_tags.py @@ -28,8 +28,8 @@ def render_list_items(context, list_objects, @register.simple_tag(takes_context=True) -def render_object_rep(context, obj, +def render_object_rep(context, obj, forloop, sortable_object_rep_template='adminsortable/shared/object_rep.html'): - context.update({'object': obj}) + context.update({'object': obj, 'forloop': forloop}) tmpl = template.loader.get_template(sortable_object_rep_template) return tmpl.render(context) diff --git a/adminsortable/utils.py b/adminsortable/utils.py index 9848274..890e504 100644 --- a/adminsortable/utils.py +++ b/adminsortable/utils.py @@ -1,8 +1,9 @@ from .models import SortableMixin, SortableForeignKey -def check_inheritance(obj): - return issubclass(type(obj), SortableMixin) +def check_inheritance(cls): + print 'check_inheritance: {}'.format(issubclass(type(cls), SortableMixin)) + return issubclass(type(cls), SortableMixin) def get_is_sortable(objects): diff --git a/sample_project/app/admin.py b/sample_project/app/admin.py index a029d62..cba42f8 100644 --- a/sample_project/app/admin.py +++ b/sample_project/app/admin.py @@ -59,7 +59,7 @@ class CreditInline(SortableTabularInline): class NoteInline(SortableStackedInline): model = Note - extra = 0 + extra = 2 class GenericNoteInline(SortableGenericStackedInline): diff --git a/sample_project/database/test_project.sqlite b/sample_project/database/test_project.sqlite index 6f5d859..84ca616 100644 Binary files a/sample_project/database/test_project.sqlite and b/sample_project/database/test_project.sqlite differ