Hi list,
I wrote a form field (newforms) that can "replicate" itself (source
attached). Is similar to the "add attachment" field on GMail; it works
with many django's newform fields (even ChoiceField, or IntegerField),
and has an almost-decent error handling (it there is a validation error,
the values of the previously filled/created fields are filled on the
form).
The idea is that you give to the field (I called it MultipleField) an
instance of another field (right now it can't receive classes), and it
will display the given field and will show a link to add more instances
of the same field to the displayed form --through javascript--. On the
server side, the data returned by the field is a list of the values on
each inner-field (the one passed to the MultipleField). This data is
also validated properly (e.g., if the inner field is a FileField, you'll
receive a list of "UploadedFile"s).
It still has some rough edges (as the display of errors, or the layout
of the label and the HTML fields), but I think you might find it useful.
A few notes: it requires jQuery to work properly. I have tested it with
IntegerField, FileField and CharField. I haven't tested it in IE, only
in Firefox and up to some extent in Konqueror.
A simple use case would be
class TestForm(forms.Form):
my_field = localforms.MultipleField(label=u'Pum!',
inner_field=forms.ChoiceField(choices=(("1", "a"), ("2", "b"
I would appreciate feedback/patches on this :D.
Greets,
--
Javier Rojas
GPG Key ID: 0xA1C57061
#!/usr/bin/python
# -*- coding: latin-1 -*-
from copy import copy
from django import newforms as forms
from django.newforms.util import flatatt
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
class MultipleWidget(forms.widgets.Widget):
def __init__(self, *args, **kwargs):
if kwargs.has_key('inner_widget'):
self.inner_widget = kwargs['inner_widget']
del kwargs['inner_widget']
else:
self.inner_widget = forms.widgets.TextInput()
super(MultipleWidget, self).__init__(*args, **kwargs)
def value_from_datadict(self, data, files, name):
#Given a dictionary of data (POST) and this widget's name, returns the
value
#of this widget. Returns None if it's not provided.
dummy_instance = self.inner_widget
all_data = copy(data)
all_data.update(files)
res = [dummy_instance.value_from_datadict(data, files, k)
for k in all_data.keys() if k.startswith(name)]
if res:
return res
return None
def render(self, name, value, *args, **kwargs):
widget_text = "%s" % self.inner_widget.render("%s_0"
% name, None) #value is none since the new fields must be empty
cnt = (value and len(value)) or 1
widget_text = widget_text.replace("\n", "\\n")
widget_text = widget_text.replace("'", "\\'")
js = '''
//<![CDATA[
cuenta_%(name)s = %(cnt)d;
function append_%(name)s() {
var widget_text = '%(widget_text)s';
widget_text = widget_text.replace(/name="%(name)s_[0-9]+"/g,
'name="%(name)s_'+ cuenta_%(name)s + '"');
widget_text = widget_text.replace(/id="%(name)s_[0-9]+_id"/g,
'id="%(name)s_'+ cuenta_%(name)s+'_id"');
$('#%(id)s').append('<tr><td>' + widget_text + '</td></tr>');
cuenta_%(name)s = cuenta_%(name)s + 1;
}
$('#%(id)s_link').click(append_%(name)s);
//]]>
''' % dict(name=name, id=name+'_id', widget_text=widget_text, cnt=cnt)
if value:
wdgs = [self.inner_widget.render("%(name)s_%(idx)d" %
dict(name=name, idx=i), val)
for i, val in enumerate(value)]
wdgs = ["%s" % s for s in wdgs]
else:
wdgs = [widget_text]
html = '''
%(widgets)s
Add
''' % dict(widgets="\n".join(wdgs), name=name, id=name+'_id')
return mark_safe(html+js)
class MultipleField(forms.Field):
def __init__(self, *args, **kwargs):
if kwargs.has_key('inner_field'):
self.inner_field = kwargs['inner_field']
del kwargs['inner_field']
else:
self.inner_field = forms.CharField()
super(MultipleField, self).__init__(*args, **kwargs)
self.widget = MultipleWidget(inner_widget=self.inner_field.widget)
def clean(self, value):
dummy_instance = self.inner_field
value = map(dummy_instance.clean, value)
return value
pgpdnkQdVqIso.pgp
Description: PGP signature