django-sortedm2m-0.7.0/0000775000175000017500000000000012300742665015475 5ustar gregorgregor00000000000000django-sortedm2m-0.7.0/sortedm2m/0000775000175000017500000000000012300742665017411 5ustar gregorgregor00000000000000django-sortedm2m-0.7.0/sortedm2m/forms.py0000664000175000017500000000737012266027344021121 0ustar gregorgregor00000000000000# -*- 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] django-sortedm2m-0.7.0/sortedm2m/templates/0000775000175000017500000000000012300742665021407 5ustar gregorgregor00000000000000django-sortedm2m-0.7.0/sortedm2m/templates/sortedm2m/0000775000175000017500000000000012300742665023323 5ustar gregorgregor00000000000000django-sortedm2m-0.7.0/sortedm2m/templates/sortedm2m/sorted_checkbox_select_multiple_widget.html0000664000175000017500000000132312266026175034175 0ustar gregorgregor00000000000000{% load i18n static %}

{% trans "Choose items and order by drag & drop." %}

django-sortedm2m-0.7.0/sortedm2m/__init__.py0000664000175000017500000000006012300740105021501 0ustar gregorgregor00000000000000# -*- coding: utf-8 -*- __version__ = '0.7.0' django-sortedm2m-0.7.0/sortedm2m/static/0000775000175000017500000000000012300742665020700 5ustar gregorgregor00000000000000django-sortedm2m-0.7.0/sortedm2m/static/sortedm2m/0000775000175000017500000000000012300742665022614 5ustar gregorgregor00000000000000django-sortedm2m-0.7.0/sortedm2m/static/sortedm2m/widget.css0000664000175000017500000000155112171044714024607 0ustar gregorgregor00000000000000.sortedm2m-container { margin-right: 10px; width: 570px; } .sortedm2m-container p.selector-filter { width: 570px; padding: 0; margin: 0; } .sortedm2m-container p.selector-filter input { width: 532px; margin: 5px 4px; } ul.sortedm2m { display: block; width: 554px; min-height: 200px; max-height: 400px; overflow-x: hidden; overflow-y: auto; margin: 0; padding: 6px 8px; list-style-type: none; text-align: left; } ul.sortedm2m li { list-style-type: none; text-align: left; width: 550px; overflow: hidden; text-overflow: ellipsis; white-space: pre; } ul.sortedm2m li, ul.sortedm2m label { cursor: move; } /* required to work properly in django admin */ body.change-form .sortedm2m-container { float: left; } .module ul.sortedm2m { margin: 0; padding: 6px 8px; } django-sortedm2m-0.7.0/sortedm2m/static/sortedm2m/widget.js0000664000175000017500000000725012171037064024435 0ustar gregorgregor00000000000000if (jQuery === undefined) { jQuery = django.jQuery; } (function ($) { $(function () { $('.sortedm2m').parents('ul').each(function () { $(this).addClass('sortedm2m'); var checkboxes = $(this).find('input[type=checkbox]'); var id = checkboxes.first().attr('id').match(/^(.*)_\d+$/)[1]; var name = checkboxes.first().attr('name'); checkboxes.removeAttr('name'); $(this).before(''); var that = this; var recalculate_value = function () { var values = []; $(that).find(':checked').each(function () { values.push($(this).val()); }); $('#' + id).val(values.join(',')); } recalculate_value(); checkboxes.change(recalculate_value); $(this).sortable({ axis: 'y', //containment: 'parent', update: recalculate_value }); }); $('.sortedm2m-container .selector-filter input').each(function () { $(this).bind('input', function() { var search = $(this).val().toLowerCase(); var $el = $(this).closest('.selector-filter'); var $container = $el.siblings('ul').each(function() { // walk over each child list el and do name comparisons $(this).children().each(function() { var curr = $(this).find('label').text().toLowerCase(); if (curr.indexOf(search) === -1) { $(this).css('display', 'none'); } else { $(this).css('display', 'inherit'); }; }); }); }); }); if (window.showAddAnotherPopup) { var django_dismissAddAnotherPopup = window.dismissAddAnotherPopup; window.dismissAddAnotherPopup = function (win, newId, newRepr) { // newId and newRepr are expected to have previously been escaped by // django.utils.html.escape. newId = html_unescape(newId); newRepr = html_unescape(newRepr); var name = windowname_to_id(win.name); var elem = $('#' + name); var sortedm2m = elem.siblings('ul.sortedm2m'); if (sortedm2m.length == 0) { // no sortedm2m widget, fall back to django's default // behaviour return django_dismissAddAnotherPopup.apply(this, arguments); } if (elem.val().length > 0) { elem.val(elem.val() + ','); } elem.val(elem.val() + newId); var id_template = ''; var maxid = 0; sortedm2m.find('li input').each(function () { var match = this.id.match(/^(.+)_(\d+)$/); id_template = match[1]; id = parseInt(match[2]); if (id > maxid) maxid = id; }); var id = id_template + '_' + (maxid + 1); var new_li = $('
  • ').append( $('