diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5611a57f..7277de5b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,16 @@ Changelog Only important changes are mentioned below. See `commit log `_ and `closed issues `_ for full changes. +v0.2.7 (2014-03-21) +------------------- + +* [Feature] `Sortables `_ for `StackedInline `_ and Generic inlines added. Closes `#137 `_ +* [Fix] Fixes `#209 `_ Wrap jQuery autosize in Suit jQuery scope +* [Fix] Fixes `#206 `_ Fixed exception when menu config is Unicode [Thanks to @kane-c] +* [Fix] Fixes `#90 `_ Django test sometimes crashes, because of Django Suit +* [Fix] Fixes login template for custom user model `#200 `_ [Thanks to @theskumar] + + v0.2.6 (2014-02-14) ------------------------------------------------------------- diff --git a/docs/_static/img/stacked_inline_sortable.png b/docs/_static/img/stacked_inline_sortable.png new file mode 100644 index 00000000..ded49a40 Binary files /dev/null and b/docs/_static/img/stacked_inline_sortable.png differ diff --git a/docs/sortables.rst b/docs/sortables.rst index e97aa28f..a2ea1b02 100644 --- a/docs/sortables.rst +++ b/docs/sortables.rst @@ -1,11 +1,11 @@ Sortables ========= -Currently Django Suit supports three types of sortables: +Currently Django Suit supports these types of sortables: 1. Sortable for change list 2. Sortable for ``django-mptt`` tree list -3. Sortable for tabular inlines +3. Sortable for Tabular, Stacked, GenericTabular, GenericStacked inlines Limitations ----------- @@ -127,8 +127,6 @@ Resources Tabular inlines sortable ------------------------ -Currently inline sortable supports only TabularInline. Feel free to make a feature request `on Github `_, if you think also StackedInline should be supported. - 1. In ``models.py`` your model for inlines, should have integer property for sortable, same way as described in all previous sortable examples:: from django.db import models @@ -165,3 +163,30 @@ Resources * `Live example #2 `_ * `Github source `_ + +Stacked and Generic inlines sortable +------------------------------------ + +Implementation of sortables for Stacked and Generic inlines is the same as mentioned above for Tabular inlines. You just have to use appropriate base class instead of ``SortableTabularInline``: + +:: + + # For Stacked inlines + from suit.admin import SortableStackedInline + + # For Generic inlines + from suit.admin import SortableTabularStackedInline + from suit.admin import SortableGenericStackedInline + + +Example +^^^^^^^ + + .. image:: _static/img/stacked_inline_sortable.png + :target: http://djangosuit.com/admin/examples/kitchensink/3/ + +Resources +^^^^^^^^^ + +* `Live example `_ +* `Github source `_ diff --git a/suit/admin.py b/suit/admin.py index 4dc5fc89..e9481f36 100644 --- a/suit/admin.py +++ b/suit/admin.py @@ -1,11 +1,12 @@ +import copy from django.conf import settings -import suit.config from django.contrib.admin import ModelAdmin from django.contrib.admin.views.main import ChangeList +from django.contrib.contenttypes import generic from django.forms import ModelForm from django.contrib import admin from django.db import models -from suit.widgets import NumberInput, SuitSplitDateTimeWidget, AutosizedTextarea +from suit.widgets import NumberInput, SuitSplitDateTimeWidget class SortableModelAdminBase(object): @@ -39,13 +40,13 @@ def get_ordering(self, request, queryset): return [self.model_admin.sortable, '-' + self.model._meta.pk.name] -class SortableTabularInline(SortableModelAdminBase, admin.TabularInline): +class SortableTabularInlineBase(SortableModelAdminBase): """ Sortable tabular inline """ def __init__(self, *args, **kwargs): - super(SortableTabularInline, self).__init__(*args, **kwargs) + super(SortableTabularInlineBase, self).__init__(*args, **kwargs) self.ordering = (self.sortable,) self.fields = self.fields or [] @@ -55,10 +56,70 @@ def __init__(self, *args, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs): if db_field.name == self.sortable: kwargs['widget'] = SortableListForm.Meta.widgets['order'] - return super(SortableTabularInline, self).formfield_for_dbfield( + return super(SortableTabularInlineBase, self).formfield_for_dbfield( db_field, **kwargs) +class SortableTabularInline(SortableTabularInlineBase, admin.TabularInline): + pass + + +class SortableGenericTabularInline(SortableTabularInlineBase, + generic.GenericTabularInline): + pass + + +class SortableStackedInlineBase(SortableModelAdminBase): + """ + Sortable stacked inline + """ + + def get_fieldsets(self, *args, **kwargs): + """ + Iterate all fieldsets and make sure sortable is in the first fieldset + Remove sortable from every other fieldset, if by some reason someone + has added it + """ + fieldsets = super(SortableStackedInlineBase, self).get_fieldsets( + *args, **kwargs) + + sortable_added = False + for fieldset in fieldsets: + for line in fieldset: + if not line or not isinstance(line, dict): + continue + + fields = line.get('fields') + if self.sortable in fields: + fields.remove(self.sortable) + + # Add sortable field always as first + if not sortable_added: + fields.insert(0, self.sortable) + sortable_added = True + break + + return fieldsets + + def formfield_for_dbfield(self, db_field, **kwargs): + if db_field.name == self.sortable: + kwargs['widget'] = copy.deepcopy( + SortableListForm.Meta.widgets['order']) + kwargs['widget'].attrs['class'] += ' suit-sortable-stacked' + kwargs['widget'].attrs['rowclass'] = ' suit-sortable-stacked-row' + return super(SortableStackedInlineBase, self).formfield_for_dbfield( + db_field, **kwargs) + + +class SortableStackedInline(SortableStackedInlineBase, admin.StackedInline): + pass + + +class SortableGenericStackedInline(SortableStackedInlineBase, + generic.GenericStackedInline): + pass + + class SortableModelAdmin(SortableModelAdminBase, ModelAdmin): """ Sortable tabular inline diff --git a/suit/static/suit/css/suit.css b/suit/static/suit/css/suit.css index 86ec6a16..9484e1bd 100644 --- a/suit/static/suit/css/suit.css +++ b/suit/static/suit/css/suit.css @@ -210,6 +210,9 @@ h3{font-weight:bold;font-size:16px;line-height:36px;text-shadow:0 1px rgba(255,2 .form-horizontal .suit-tab.collapsed.hide{display:none !important} .suit-tab>h2,.form-horizontal .suit-include>h2{margin-top:-10px;margin-bottom:-6px;line-height:36px;padding:0} .linked-select-link{font-size:11px;margin-right:5px} +.stacked-inline-sortable{float:right}.stacked-inline-sortable :first-child{padding-right:1px} +.stacked-inline-sortable:nth-last-child(2){margin-right:10px} +.inline-group>div:first-of-type .stacked-inline-sortable .sortable-up,.inline-group>div:nth-last-child(3) .stacked-inline-sortable .sortable-down{opacity:.15 !important;cursor:default} .box,.well{box-shadow:0 0 0 1px rgba(0,0,0,0.07);-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;background:#f6f7f8;padding:10px 12px;border:0} .menu-box{margin:0 0 10px 0;padding:5px 0;background-color:#fff}.menu-box li{list-style:none;line-height:18px}.menu-box li a{text-decoration:none;line-height:18px;display:block;padding:5px 10px 5px 13px;color:#666;border-bottom:1px solid #ededed}.menu-box li a:hover{background-color:#f6f7f8}.menu-box li a:hover:first-child{-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px} .menu-box li a:hover:last-child{-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px} diff --git a/suit/static/suit/js/sortables.js b/suit/static/suit/js/sortables.js index 3a713a94..e0090af8 100644 --- a/suit/static/suit/js/sortables.js +++ b/suit/static/suit/js/sortables.js @@ -60,23 +60,47 @@ } function on_arrow_click(e) { - perform_move($(this), $(this).closest('tr')); + var $sortable = $(this); + var $row = $sortable.closest( + $sortable.hasClass('sortable-stacked') ? 'div.inline-related' : 'tr' + ); + perform_move($sortable, $row); e.preventDefault(); } - function create_link(text, direction) { + function create_link(text, direction, on_click_func, is_stacked) { return $('').attr('href', '#') - .addClass('sortable sortable-' + direction) + .addClass('sortable sortable-' + direction + + (is_stacked ? ' sortable-stacked' : '')) .attr('data-dir', direction).html(text) - .click(on_arrow_click); + .click(on_click_func); } $inputs.each(function () { - var $inline_sortable = $('
'); - var icon = ''; - $(this).parent().append($inline_sortable); - $inline_sortable.append(create_link(icon, 'up')); - $inline_sortable.append(create_link(icon.replace('-up', '-down'), 'down')); + var $inline_sortable = $('
'), + icon = '', + $sortable = $(this), + is_stacked = $sortable.hasClass('suit-sortable-stacked'); + + var $up_link = create_link(icon, 'up', on_arrow_click, is_stacked), + $down_link = create_link(icon.replace('-up', '-down'), 'down', on_arrow_click, is_stacked); + + if (is_stacked) { + var $sortable_row = $sortable.closest('div.form-row'), + $stacked_block = $sortable.closest('div.inline-related'), + $links_span = $('').attr('class', 'stacked-inline-sortable'); + + // Add arrows to header h3, move order input and remove order field row + $links_span.append($up_link).append($down_link); + $stacked_block.find('h3').append($links_span); + $stacked_block.append($sortable); + $sortable_row.remove(); + } else { + $sortable.parent().append($inline_sortable); + $inline_sortable.append($up_link); + $inline_sortable.append($down_link); + } + }); // Filters out unchanged selects and sortable field itself diff --git a/suit/static/suit/less/ui/form.less b/suit/static/suit/less/ui/form.less index 885e7e99..9c0b077f 100644 --- a/suit/static/suit/less/ui/form.less +++ b/suit/static/suit/less/ui/form.less @@ -619,3 +619,20 @@ h3 { font-size: 11px; margin-right: 5px; } + +/* StackedInline sortables */ +.stacked-inline-sortable { + float: right; + :first-child { + padding-right: 1px; + } + &:nth-last-child(2) { + margin-right: 10px; + } +} +.inline-group > div:first-of-type .stacked-inline-sortable .sortable-up, +.inline-group > div:nth-last-child(3) .stacked-inline-sortable .sortable-down +{ + opacity: .15 !important; + cursor: default; +}