diff options
author | Étienne Loks <etienne.loks@peacefrogs.net> | 2013-12-26 19:09:09 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@peacefrogs.net> | 2013-12-26 19:09:09 +0100 |
commit | 39e296ed421cce7696313dcafe75cd03d073054d (patch) | |
tree | d69858ce6408508fc2661a04e75d0a5b32d33f5a /ishtar_common | |
parent | bd64ebef333c1275923bf90dd69e8181e80092aa (diff) | |
download | Ishtar-39e296ed421cce7696313dcafe75cd03d073054d.tar.bz2 Ishtar-39e296ed421cce7696313dcafe75cd03d073054d.zip |
Manage archaeological sites into forms (refs #1586)
* create new widget: multiple autocomplete field
* move JS autocomplete to template
* archaeological site reference made unique
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'): |