You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
104 lines
3.7 KiB
104 lines
3.7 KiB
# -*- coding: utf-8 -*-
|
|
import sys
|
|
from itertools import chain
|
|
from django import forms
|
|
from django.conf import settings
|
|
from django.db.models.query import QuerySet
|
|
from django.template.loader import render_to_string
|
|
from django.utils.encoding import force_text
|
|
from django.utils.html import conditional_escape, escape
|
|
from django.utils.safestring import mark_safe
|
|
|
|
|
|
if sys.version_info[0] < 3:
|
|
iteritems = lambda d: iter(d.iteritems())
|
|
string_types = basestring,
|
|
str_ = unicode
|
|
else:
|
|
iteritems = lambda d: iter(d.items())
|
|
string_types = str,
|
|
str_ = str
|
|
|
|
|
|
STATIC_URL = getattr(settings, 'STATIC_URL', settings.MEDIA_URL)
|
|
|
|
|
|
class SortedCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
|
|
class Media:
|
|
js = (
|
|
STATIC_URL + 'sortedm2m/widget.js',
|
|
STATIC_URL + 'sortedm2m/jquery-ui.js',
|
|
)
|
|
css = {'screen': (
|
|
STATIC_URL + 'sortedm2m/widget.css',
|
|
)}
|
|
|
|
def build_attrs(self, attrs=None, **kwargs):
|
|
attrs = super(SortedCheckboxSelectMultiple, self).\
|
|
build_attrs(attrs, **kwargs)
|
|
classes = attrs.setdefault('class', '').split()
|
|
classes.append('sortedm2m')
|
|
attrs['class'] = ' '.join(classes)
|
|
return attrs
|
|
|
|
def render(self, name, value, attrs=None, choices=()):
|
|
if value is None: value = []
|
|
has_id = attrs and 'id' in attrs
|
|
final_attrs = self.build_attrs(attrs, name=name)
|
|
|
|
# Normalize to strings
|
|
str_values = [force_text(v) for v in value]
|
|
|
|
selected = []
|
|
unselected = []
|
|
|
|
for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
|
|
# If an ID attribute was given, add a numeric index as a suffix,
|
|
# so that the checkboxes don't all have the same ID attribute.
|
|
if has_id:
|
|
final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
|
|
label_for = ' for="%s"' % conditional_escape(final_attrs['id'])
|
|
else:
|
|
label_for = ''
|
|
|
|
cb = forms.CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
|
|
option_value = force_text(option_value)
|
|
rendered_cb = cb.render(name, option_value)
|
|
option_label = conditional_escape(force_text(option_label))
|
|
item = {'label_for': label_for, 'rendered_cb': rendered_cb, 'option_label': option_label, 'option_value': option_value}
|
|
if option_value in str_values:
|
|
selected.append(item)
|
|
else:
|
|
unselected.append(item)
|
|
|
|
# re-order `selected` array according str_values which is a set of `option_value`s in the order they should be shown on screen
|
|
ordered = []
|
|
for value in str_values:
|
|
for select in selected:
|
|
if value == select['option_value']:
|
|
ordered.append(select)
|
|
selected = ordered
|
|
|
|
html = render_to_string(
|
|
'sortedm2m/sorted_checkbox_select_multiple_widget.html',
|
|
{'selected': selected, 'unselected': unselected})
|
|
return mark_safe(html)
|
|
|
|
def value_from_datadict(self, data, files, name):
|
|
value = data.get(name, None)
|
|
if isinstance(value, string_types):
|
|
return [v for v in value.split(',') if v]
|
|
return value
|
|
|
|
|
|
class SortedMultipleChoiceField(forms.ModelMultipleChoiceField):
|
|
widget = SortedCheckboxSelectMultiple
|
|
|
|
def clean(self, value):
|
|
queryset = super(SortedMultipleChoiceField, self).clean(value)
|
|
if value is None or not isinstance(queryset, QuerySet):
|
|
return queryset
|
|
object_list = dict((
|
|
(str_(key), value)
|
|
for key, value in iteritems(queryset.in_bulk(value))))
|
|
return [object_list[str_(pk)] for pk in value]
|
|
|