diff options
Diffstat (limited to 'ishtar_common')
| -rw-r--r-- | ishtar_common/templates/blocks/JQueryAutocomplete.js | 19 | ||||
| -rw-r--r-- | ishtar_common/templates/blocks/JQueryAutocompleteMultiple.js | 92 | ||||
| -rw-r--r-- | ishtar_common/templatetags/replace_underscore.py | 10 | ||||
| -rw-r--r-- | ishtar_common/widgets.py | 113 | ||||
| -rw-r--r-- | ishtar_common/wizards.py | 30 | 
5 files changed, 235 insertions, 29 deletions
diff --git a/ishtar_common/templates/blocks/JQueryAutocomplete.js b/ishtar_common/templates/blocks/JQueryAutocomplete.js new file mode 100644 index 000000000..eb365c38a --- /dev/null +++ b/ishtar_common/templates/blocks/JQueryAutocomplete.js @@ -0,0 +1,19 @@ +$("#id_select_{{field_id}}").autocomplete({ +    source: {{source}}, +    select: function( event, ui ) { +            if(ui.item){ +                $('#id_{{field_id}}').val(ui.item.id); +            } else { +                $('#id_{{field_id}}').val(null); +            } +        }, +    minLength: 2{% if options %}, +    {{options}} +    {% endif %} +}); + +$('#id_select_{{field_id}}').live('click', function(){ +    $('#id_{{field_id}}').val(null); +    $('#id_select_{{field_id}}').val(null); +}); + diff --git a/ishtar_common/templates/blocks/JQueryAutocompleteMultiple.js b/ishtar_common/templates/blocks/JQueryAutocompleteMultiple.js new file mode 100644 index 000000000..56133ef4e --- /dev/null +++ b/ishtar_common/templates/blocks/JQueryAutocompleteMultiple.js @@ -0,0 +1,92 @@ +{% load replace_underscore %} +function split( val ) { +    return val.split( /,\s*/ ); +} + +function extractLast( term ) { +    return split( term ).pop(); +} + +var {{field_id|replace_underscore}}_values = $('#id_{{field_id}}').val().split(','); +if(!{{field_id|replace_underscore}}_values){ +    {{field_id|replace_underscore}}_values = new Array(); +} +var {{field_id|replace_underscore}}_ctext = ""; + +$("#id_select_{{field_id}}") +    // don't navigate away from the field on tab when selecting an item +      .bind( "keydown", function( event ) { +        if ( event.keyCode === $.ui.keyCode.TAB && +           $( this ).data( "ui-autocomplete" ).menu.active ) { +            event.preventDefault(); +        } else if (event.keyCode === $.ui.keyCode.DELETE || +                   event.keyCode === $.ui.keyCode.BACKSPACE){ +            {{field_id|replace_underscore}}_ctext = +                                $("#id_select_{{field_id}}").val().split(','); +        } +      }) +      .bind( "keyup", function( event ) { +        if (event.keyCode === $.ui.keyCode.DELETE || +            event.keyCode === $.ui.keyCode.BACKSPACE){ +            var new_val = $("#id_select_{{field_id}}").val().split(','); +            var length = {{field_id|replace_underscore}}_ctext.length; +            for (idx=0;idx<length;idx++){ +                if(idx >= new_val.length || +                   {{field_id|replace_underscore}}_ctext[idx] != new_val[idx]){ +                    if (idx == (length - 1) && length > 1){ +                        if ({{field_id|replace_underscore}}_ctext[idx].trim() == ""){ +                            idx = idx - 1; +                        } else { +                            return; +                        } +                    } +                    {{field_id|replace_underscore}}_ctext.splice(idx, 1); +                    // remove value +                    if (idx < {{field_id|replace_underscore}}_values.length){ +                        {{field_id|replace_underscore}}_values.splice(idx, 1); +                        $('#id_{{field_id}}').val({{field_id|replace_underscore}}_values); +                    } +                    if ({{field_id|replace_underscore}}_ctext.length > 0 && +                        {{field_id|replace_underscore}}_ctext[0].length){ +                        // remove leading space +                        if ({{field_id|replace_underscore}}_ctext[0][0] == ' '){ +                            {{field_id|replace_underscore}}_ctext[0] = +                                    {{field_id|replace_underscore}}_ctext[0].trim(); +                        } +                        // remove trailing space +                        var last = {{field_id|replace_underscore}}_ctext.length -1; +                        {{field_id|replace_underscore}}_ctext[last] = +                            {{field_id|replace_underscore}}_ctext[last].trim(); +                    } +                    this.value = {{field_id|replace_underscore}}_ctext.join(", "); +                    return +                } +            } +        } +    }).autocomplete({ +     source:  function( request, response ) { +        $.getJSON({{source}}, { +            term: extractLast( request.term ) +        }, response ); +     }, +     focus: function() { +        // prevent value inserted on focus +        return false; +     }, +     select: function( event, ui ) { +        var terms = split( this.value ); +        // remove the current input +        terms.pop(); +        // add the selected item +        terms.push( ui.item.value ); +        {{field_id|replace_underscore}}_values.push(ui.item.id); +        // add placeholder to get the comma-and-space at the end +        $('#id_{{field_id}}').val({{field_id|replace_underscore}}_values); +        terms.push( "" ); +        this.value = terms.join( ", " ); +        return false; +     }, +     minLength: 2{% if options %}, +     {{options}} +     {% endif %} +}); diff --git a/ishtar_common/templatetags/replace_underscore.py b/ishtar_common/templatetags/replace_underscore.py new file mode 100644 index 000000000..66931e6fe --- /dev/null +++ b/ishtar_common/templatetags/replace_underscore.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from django.template import Library + +register = Library() + +@register.filter +def replace_underscore(value): +    return value.replace('-', '_') diff --git a/ishtar_common/widgets.py b/ishtar_common/widgets.py index a97cfe70b..fc3ada283 100644 --- a/ishtar_common/widgets.py +++ b/ishtar_common/widgets.py @@ -27,6 +27,7 @@ from django.forms import ClearableFileInput  from django.forms.widgets import flatatt
  from django.template import Context, loader
  from django.utils.encoding import smart_unicode
 +from django.utils.functional import lazy
  from django.utils.html import escape
  from django.utils.safestring import mark_safe
  from django.utils.simplejson import JSONEncoder
 @@ -34,6 +35,44 @@ from django.utils.translation import ugettext_lazy as _  import models
 +reverse_lazy = lazy(reverse, unicode)
 +
 +class MultipleAutocompleteField(forms.MultipleChoiceField):
 +    def __init__(self, *args, **kwargs):
 +        model = None
 +        if 'model' in kwargs:
 +            model = kwargs.pop('model')
 +        if 'choices' not in kwargs and model:
 +            kwargs['choices'] = [(i.pk, unicode(i))for i in model.objects.all()]
 +        new = kwargs.pop('new') if 'new' in kwargs else None
 +        if 'widget' not in kwargs and model:
 +            kwargs['widget'] = JQueryAutoComplete(reverse_lazy(
 +                    'autocomplete-'+model.__name__.lower()),
 +                    associated_model=model, new=new,
 +                    multiple=True)
 +        super(MultipleAutocompleteField, self).__init__(*args, **kwargs)
 +
 +    def clean(self, value):
 +        if value:
 +            # clean JS messup with values
 +            try:
 +                if type(value) not in (list, tuple):
 +                    value = [value]
 +                else:
 +                    val = value
 +                    value = []
 +                    for v in val:
 +                        v = unicode(v).strip('[').strip(']'
 +                                     ).strip('u').strip("'").strip('"')
 +                        value += [int(v.strip())
 +                                            for v in list(set(v.split(',')))
 +                                                                if v.strip()]
 +            except (TypeError, ValueError):
 +                value = []
 +        else:
 +            value = []
 +        return super(MultipleAutocompleteField, self).clean(value)
 +
  class DeleteWidget(forms.CheckboxInput):
      def render(self, name, value, attrs=None):
          final_attrs = flatatt(self.build_attrs(attrs, name=name,
 @@ -95,7 +134,7 @@ class JQueryDate(forms.TextInput):  class JQueryAutoComplete(forms.TextInput):
      def __init__(self, source, associated_model=None, options={}, attrs={},
 -                 new=False):
 +                 new=False, multiple=False):
          """
          Source can be a list containing the autocomplete values or a
          string containing the url used for the request.
 @@ -108,6 +147,13 @@ class JQueryAutoComplete(forms.TextInput):              self.options = JSONEncoder().encode(options)
          self.attrs.update(attrs)
          self.new = new
 +        self.multiple = multiple
 +
 +    def value_from_datadict(self, data, files, name):
 +        if self.multiple:
 +            return data.getlist(name, None)
 +        else:
 +            return data.get(name, None)
      def render_js(self, field_id):
          if isinstance(self.source, list):
 @@ -119,39 +165,54 @@ class JQueryAutoComplete(forms.TextInput):                  source = "'" + unicode(self.source) + "'"
              except:
                  raise ValueError('source type is not valid')
 -        options = 'source : ' + source
 -        options += ''', select: function( event, ui ) {
 -            if(ui.item){
 -                $('#id_%s').val(ui.item.id);
 -            } else {
 -                $('#id_%s').val(null);
 -            }
 -        }, minLength: 2
 -        ''' % (field_id, field_id)
 +        dct = {'source':mark_safe(source),
 +               'field_id':field_id}
          if self.options:
 -            options += ',%s' % self.options
 +            dct['options'] = mark_safe('%s' % self.options)
 -        js = u'$(\'#id_select_%s\').autocomplete({%s});\n' % (field_id, options)
 -        js += u'''$(\'#id_select_%s\').live('click', function(){
 -                $('#id_%s').val(null);
 -                $('#id_select_%s').val(null);
 -});''' % (field_id, field_id, field_id)
 +        js = ""
 +        tpl = 'blocks/JQueryAutocomplete.js'
 +        if self.multiple:
 +            tpl = 'blocks/JQueryAutocompleteMultiple.js'
 +        t = loader.get_template(tpl)
 +        js = t.render(Context(dct))
          return js
      def render(self, name, value=None, attrs=None):
          attrs_hidden = self.build_attrs(attrs, name=name)
          attrs_select = self.build_attrs(attrs)
 -
          if value:
 -            val =  escape(smart_unicode(value))
 -            attrs_hidden['value'] = val
 -            attrs_select['value'] = val
 -            if self.associated_model:
 -                try:
 -                    attrs_select['value'] = unicode(
 -                        self.associated_model.objects.get(pk=value))
 -                except:
 -                    attrs_select['value'] = ""
 +            hiddens = []
 +            selects = []
 +            values = value
 +            if type(value) not in (list, tuple):
 +                values = unicode(escape(smart_unicode(value)))
 +                values = values.replace('[', '').replace(']', '')
 +                values = values.split(',')
 +            else:
 +                values = []
 +                for v in value:
 +                    values += v.split(',')
 +            for v in values:
 +                if not v:
 +                    continue
 +                hiddens.append(v)
 +                selects.append(v)
 +                if self.associated_model:
 +                    try:
 +                        selects[-1] = unicode(
 +                            self.associated_model.objects.get(pk=v))
 +                    except (self.associated_model.DoesNotExist, ValueError):
 +                        selects.pop()
 +                        hiddens.pop()
 +            if self.multiple:
 +                attrs_hidden['value'] = ", ".join(hiddens)
 +                if selects:
 +                    selects.append("")
 +                attrs_select['value'] = ", ".join(selects)
 +            else:
 +                attrs_hidden['value'] = hiddens[0]
 +                attrs_select['value'] = selects[0]
          if not self.attrs.has_key('id'):
              attrs_hidden['id'] = 'id_%s' % name
              attrs_select['id'] = 'id_select_%s' % name
 diff --git a/ishtar_common/wizards.py b/ishtar_common/wizards.py index 55c9d0d9d..a237fb327 100644 --- a/ishtar_common/wizards.py +++ b/ishtar_common/wizards.py @@ -24,12 +24,22 @@ from django.contrib.formtools.wizard.views import NamedUrlWizardView  from django.core.exceptions import ObjectDoesNotExist  from django.core.files.images import ImageFile  from django.db.models.fields.files import FileField +from django.db.models.fields.related import ManyToManyField  from django.shortcuts import render_to_response  from django.template import RequestContext -from django.utils.datastructures import MultiValueDict +from django.utils.datastructures import MultiValueDict as BaseMultiValueDict  from django.utils.translation import ugettext_lazy as _  import models +class MultiValueDict(BaseMultiValueDict): +    def get(self, *args, **kwargs): +        v = super(MultiValueDict, self).getlist(*args, **kwargs) +        if len(v) > 1: +            v = ",".join(v) +        else: +            v = super(MultiValueDict, self).get(*args, **kwargs) +        return v +  class Wizard(NamedUrlWizardView):      model = None      label = '' @@ -211,7 +221,8 @@ class Wizard(NamedUrlWizardView):                          if type(value) in (tuple, list):                              values = value                          elif "," in unicode(value): -                            values = unicode(value).split(",") +                            values = unicode(value +                                            ).strip('[').strip(']').split(",")                          else:                              values = [value]                          rendered_values = [] @@ -344,6 +355,12 @@ class Wizard(NamedUrlWizardView):                     isinstance(obj.__class__._meta.get_field(k), ImageFile)):                      if not dct[k]:                          dct[k] = None +                if isinstance(obj.__class__._meta.get_field(k), +                              ManyToManyField): +                    if not dct[k]: +                        dct[k] = [] +                    elif type(dct[k]) not in (list, tuple): +                        dct[k] = [dct[k]]                  setattr(obj, k, dct[k])              try:                  obj.full_clean() @@ -516,7 +533,8 @@ class Wizard(NamedUrlWizardView):          elif hasattr(form, 'extra_form') and hasattr(form.extra_form, 'fields')\             and form.extra_form.fields.keys():              frm = form.extra_form -        elif hasattr(form, 'forms') and form.forms and form.forms[0].fields.keys(): +        elif hasattr(form, 'forms') and form.forms \ +           and form.forms[0].fields.keys():              frm = form.forms[0]          if frm:              first_field = frm.fields[frm.fields.keyOrder[0]] @@ -675,6 +693,12 @@ class Wizard(NamedUrlWizardView):                              value = obj                              break                          value = getattr(value, field) +                if hasattr(value, 'all') and callable(value.all): +                    if not value.count(): +                        continue +                    initial.setlist(base_field, +                                    [unicode(v.pk) for v in value.all()]) +                    continue                  if value == obj:                      continue                  if hasattr(value, 'pk'):  | 
