summaryrefslogtreecommitdiff
path: root/ishtar_common
diff options
context:
space:
mode:
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
commit39e296ed421cce7696313dcafe75cd03d073054d (patch)
treed69858ce6408508fc2661a04e75d0a5b32d33f5a /ishtar_common
parentbd64ebef333c1275923bf90dd69e8181e80092aa (diff)
downloadIshtar-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.js19
-rw-r--r--ishtar_common/templates/blocks/JQueryAutocompleteMultiple.js92
-rw-r--r--ishtar_common/templatetags/replace_underscore.py10
-rw-r--r--ishtar_common/widgets.py113
-rw-r--r--ishtar_common/wizards.py30
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'):