wcs: allow setting up a manual order of forms of a category (#8914)

This commit is contained in:
Frédéric Péters 2016-03-22 09:42:53 +01:00
parent 12f9987f5e
commit a2299d423a
7 changed files with 192 additions and 12 deletions

View File

@ -15,6 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django import forms
from django.utils.datastructures import MultiValueDict, MergeDict
from django.utils.safestring import mark_safe
from .models import WcsFormCell, WcsCategoryCell, WcsFormsOfCategoryCell
from .utils import get_wcs_options
@ -41,12 +43,53 @@ class WcsCategoryCellForm(forms.ModelForm):
self.fields['category_reference'].widget = forms.Select(choices=references)
class MultiSortWidget(forms.SelectMultiple):
def render(self, name, value, attrs=None, choices=()):
# reorder choices to get them in the current value order
self_choices = self.choices[:]
choices_dict = dict(self_choices)
if value:
for option in reversed(value.get('data')):
if not option in choices_dict:
continue
option_tuple = (option, choices_dict[option])
self.choices.remove(option_tuple)
self.choices.insert(0, option_tuple)
# render the <select multiple>
rendered = super(MultiSortWidget, self).render(name, value,
attrs=attrs, choices=choices)
# include it in a <div> that will be turned into an appropriate widget
# in javascript
id_ = 'wid-%s' % name
return mark_safe('''<div class="multisort" id="%s">%s</div>
<script type="text/javascript">multisort($("#%s"));</script>
''' % (id_, rendered, id_))
def render_options(self, choices, value):
value = value.get('data') or []
return super(MultiSortWidget, self).render_options(choices, value)
def value_from_datadict(self, data, files, name):
if isinstance(data, MultiValueDict):
return {'data': data.getlist(name)}
return data.get(name, None)
class WcsFormsOfCategoryCellForm(forms.ModelForm):
class Meta:
model = WcsFormsOfCategoryCell
fields = ('category_reference', 'ordering', 'limit')
fields = ('category_reference', 'ordering', 'manual_order', 'limit')
def __init__(self, *args, **kwargs):
super(WcsFormsOfCategoryCellForm, self).__init__(*args, **kwargs)
references = get_wcs_options('categories')
self.fields['category_reference'].widget = forms.Select(choices=references)
formdef_references = get_wcs_options('json', include_category_slug=True)
self.fields['ordering'].widget = forms.Select(
choices=self.fields['ordering'].choices,
attrs={'class': 'ordering-select'})
self.fields['category_reference'].widget = forms.Select(choices=references,
attrs={'class': 'category-select'})
self.fields['manual_order'].widget = MultiSortWidget(choices=formdef_references)

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import jsonfield.fields
class Migration(migrations.Migration):
dependencies = [
('wcs', '0011_auto_20151215_1121'),
]
operations = [
migrations.AddField(
model_name='wcsformsofcategorycell',
name='manual_order',
field=jsonfield.fields.JSONField(default=dict, help_text='Use drag and drop to reorder forms', verbose_name='Manual Order', blank=True),
preserve_default=True,
),
migrations.AlterField(
model_name='wcsformsofcategorycell',
name='ordering',
field=models.CharField(default=b'alpha', max_length=20, verbose_name='Order', choices=[(b'alpha', 'Default (alphabetical)'), (b'popularity', 'Popularity'), (b'manual', 'Manual')]),
preserve_default=True,
),
]

View File

@ -296,10 +296,13 @@ class WcsCurrentDraftsCell(WcsUserDataBaseCell):
@register_cell_class
class WcsFormsOfCategoryCell(WcsCommonCategoryCell, WcsBlurpMixin):
ordering = models.CharField(_('Order'), max_length=20,
default='', blank=True,
choices=[('', _('Default')),
('alpha', _('Alphabetical')),
('popularity', _('Popularity'))])
default='alpha', blank=False,
choices=[('alpha', _('Default (alphabetical)')),
('popularity', _('Popularity')),
('manual', _('Manual'))])
manual_order = JSONField(blank=True,
verbose_name=_('Manual Order'),
help_text=_('Use drag and drop to reorder forms'))
limit = models.PositiveSmallIntegerField(_('Limit'),
null=True, blank=True)
@ -337,10 +340,23 @@ class WcsFormsOfCategoryCell(WcsCommonCategoryCell, WcsBlurpMixin):
except (KeyError, TypeError) as e:
# an error occured in the blurp
context['forms'] = []
if self.ordering == 'alpha':
context['forms'] = sorted(context['forms'], lambda x, y: cmp(x.get('title'), y.get('title')))
elif self.ordering == 'popularity':
context['forms'] = sorted(context['forms'], lambda x, y: -cmp(x.get('count'), y.get('count')))
# default sort is alphabetical, it's always done as this will serve as
# secondary sort key (thanks to Python stable sort)
context['forms'] = sorted(context['forms'], key=lambda x: x.get('title'))
if self.ordering == 'popularity':
context['forms'] = sorted(context['forms'], key=lambda x: x.get('count'), reverse=True)
elif self.ordering == 'manual':
if self.manual_order:
manual_order = self.manual_order.get('data')
for form in context['forms']:
form_reference = '%s:%s' % (self.category_reference, form['slug'])
try:
form['order'] = manual_order.index(form_reference)
except ValueError:
form['order'] = 9999
context['forms'] = sorted(context['forms'], key=lambda x: x.get('order'))
if self.limit:
if len(context['forms']) > self.limit:

View File

@ -45,7 +45,7 @@ def get_wcs_json(wcs_site, path):
cache.set(url, response_json)
return response_json
def get_wcs_options(url):
def get_wcs_options(url, include_category_slug=False):
references = []
for wcs_key, wcs_site in get_wcs_services().iteritems():
site_title = wcs_site.get('title')
@ -59,7 +59,10 @@ def get_wcs_options(url):
label = title
else:
label = '%s : %s' % (site_title, title)
reference = '%s:%s' % (wcs_key, slug)
if include_category_slug:
reference = '%s:%s:%s' % (wcs_key, element.get('category_slug') or '', slug)
else:
reference = '%s:%s' % (wcs_key, slug)
references.append((reference, label))
references.sort(lambda x, y: cmp(x[1], y[1]))
return references

View File

@ -270,3 +270,21 @@ div#asset-img img {
div#asset-cmds {
text-align: right;
}
ul.multisort {
list-style: none;
padding: 0;
margin: 0;
margin-bottom: 2ex;
max-height: 250px;
overflow-y: auto;
}
ul.multisort li {
border: 1px solid #eee;
position: relative;
margin: 0;
margin-top: -1px;
padding: 1ex;
background: white;
}

View File

@ -1,3 +1,53 @@
function multisort(element)
{
var $category_selector = $(element).parents('.cell').find('select.category-select');
var category_value = null;
if ($category_selector.length) {
category_value = $category_selector.val();
$category_selector.off('change').on('change', function() {
multisort(element);
});
}
var $ordering_selector = $(element).parents('.cell').find('select.ordering-select');
if ($ordering_selector.length) {
$ordering_selector.off('change').on('change', function() {
var val = $(this).val();
if (val == 'manual') {
$(element).prev().show();
$(element).show();
} else {
$(element).prev().hide();
$(element).hide();
}
}).trigger('change');
}
$(element).find('ul').remove();
$(element).find('select').hide();
var $ul = $('<ul class="multisort"></ul>');
$(element).find('option').each(function(i, x) {
if (category_value && $(x).val().indexOf(category_value + ':') != 0) {
return;
}
$('<li data-value="' + $(x).val() + '">' + $(x).text() + '</li>').appendTo($ul);
});
$ul.appendTo(element);
$ul.sortable({
update: function(event, ui) {
var options = Array();
var select = $(element).find('select');
$ul.find('li').each(function(i, x) {
options.push($(element).find("option[value='" + $(x).data('value') + "']").attr('selected', 'selected').detach());
});
while (options.length) {
select.prepend(options.pop());
}
}
});
}
function init_pages_list()
{
if ($('#pages-list').length == 0)
@ -132,11 +182,13 @@ $(function() {
$('div.cell').each(function(i, x) {
$(this).attr('id', 'div-cell-'+i);
var h = $(this).find('h3 + div').height() + 10;
h += $(this).find('.multisort').length * 250;
h += $(this).find('.django-ckeditor-widget').length * 200;
var style = '<style>div#div-cell-'+i+'.toggled h3 + div { max-height: '+h+'px; }</style>';
$(style).appendTo('head');
$(this).addClass('untoggled');
});
$('a.menu-opener').on('click', function() {
$('#appbar .menu').toggle();
});

View File

@ -330,6 +330,27 @@ def test_forms_of_category_cell_render():
assert 'form title' in result and 'a second form title' in result
assert result.index('form title') < result.index('a second form title')
cell.ordering = 'manual'
cell.save()
result = cell.render(Context({'synchronous': True}))
# by default all forms should be present, in alphabetical order
assert result.index('form title') > result.index('a second form title')
# set a manual order
cell.manual_order = {'data': ['default:test-9:a-second-form-title',
'default:test-9:form-title']}
cell.save()
result = cell.render(Context({'synchronous': True}))
assert result.index('form title') > result.index('a second form title')
# make sure all forms are displayed even if the manual order only specify
# some.
cell.manual_order = {'data': ['default:test-9:a-second-form-title']}
cell.save()
result = cell.render(Context({'synchronous': True}))
assert result.index('form title') > result.index('a second form title')
assert 'form title' in result and 'a second form title' in result
@wcsctl_present
def test_current_drafts_cell_render():
page = Page(title='xxx', slug='test_current_drafts_cell_render', template_name='standard')