diff --git a/extra/pyvotecore/vote_field.py b/extra/pyvotecore/vote_field.py
new file mode 100644
index 000000000..d4abd0fe8
--- /dev/null
+++ b/extra/pyvotecore/vote_field.py
@@ -0,0 +1,277 @@
+# w.c.s. - web application for online forms
+# Copyright (C) 2005-2010 Entr'ouvert
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+import random
+from quixote.html import htmltext, TemplateIO
+from qommon.form import CompositeWidget, IntWidget, WidgetList, StringWidget, \
+ CheckboxWidget, SingleSelectWidget
+from wcs.fields import WidgetField, register_field_class
+from pyvotecore import schulze_method, irv, ranked_pairs, schulze_pr, \
+ schulze_stv, schulze_npr
+
+class VoteWidget(CompositeWidget):
+ readonly = False
+
+ def __init__(self, name, value = None, elements = None, **kwargs):
+ CompositeWidget.__init__(self, name, value, **kwargs)
+ self.element_names = {}
+
+ if kwargs.has_key('title'):
+ del kwargs['title']
+ if kwargs.has_key('readonly'):
+ if kwargs['readonly']:
+ self.readonly = True
+ del kwargs['readonly']
+ if kwargs.has_key('required'):
+ if kwargs['required']:
+ self.required = True
+ del kwargs['required']
+
+ self.randomize_items = False
+ if kwargs.has_key('randomize_items'):
+ if kwargs['randomize_items']:
+ self.randomize_items = True
+ del kwargs['randomize_items']
+
+ for v in elements:
+ if type(v) is tuple:
+ title = v[1]
+ key = v[0]
+ if type(key) is int:
+ name = 'element%d' % v[0]
+ elif type(key) in (str, htmltext):
+ name = str('element%s' % v[0])
+ key = str(key)
+ else:
+ raise NotImplementedError()
+ else:
+ title = v
+ key = v
+ name = 'element%d' % len(self.element_names.keys())
+
+ if value:
+ position = value.get(key)
+ else:
+ position = None
+ self.add(IntWidget, name, title = title, value = position, size = 5, **kwargs)
+ self.element_names[name] = key
+
+ if self.randomize_items:
+ random.shuffle(self.widgets)
+
+ if self.readonly:
+ def cmp_w(x, y):
+ if x.value is None and y.value is None:
+ return 0
+ if x.value is None:
+ return 1
+ if y.value is None:
+ return -1
+ return cmp(x.value, y.value)
+ self.widgets.sort(cmp_w)
+
+
+ def _parse(self, request):
+ values = {}
+ for name in self.element_names:
+ value = self.get(name)
+ values[self.element_names[name]] = value
+ if type(value) is not int:
+ self.get_widget(name).set_error(IntWidget.TYPE_ERROR)
+ self.value = values or None
+
+ def parse(self, request=None):
+ value = CompositeWidget.parse(self, request=request)
+ for widget in self.widgets:
+ if widget.has_error():
+ self.set_error(_('Some fields were not filled properly.'))
+ break
+ return value
+
+ def render_content(self):
+ r = TemplateIO(html=True)
+ r += htmltext('
')
+ for widget in self.get_widgets():
+ if widget.has_error():
+ r += htmltext('
')
+ items = value.items()
+ items.sort(lambda x,y: cmp(x[1], y[1]))
+ for it in items:
+ if it[1]:
+ r += htmltext('
%s: %s
') % (it[1], it[0])
+ r += htmltext('
')
+ return r.getvalue()
+
+ def stats(self, values):
+ '''
+ Compute vote result using the pyvotecore library.
+ '''
+ r = TemplateIO(html = True)
+
+ votes = [x.data.get(self.id) for x in values]
+ # build ballots
+ kwargs = { 'winner_threshold': self.winner_threshold,
+ 'required_winners': self.required_winners }
+ if self.tallying_method == 'condorcet-schulze':
+ votes = [{ 'count': 1, 'ballot': x } for x in votes if x]
+ method = schulze_method.SchulzeMethod
+ elif self.tallying_method == 'irv':
+ votes = [ x.items() for x in votes ]
+ votes = [ sorted(x, cmp=lambda x,y: cmp(x[1],y[1])) for x in votes ]
+ votes = [ {'count':1, 'ballot': [ a for a,b in x ] } for x in votes ]
+
+ method = irv.IRV
+ elif self.tallying_method == 'ranked_pairs':
+ votes = [{ 'count': 1, 'ballot': x } for x in votes if x]
+ method = ranked_pairs.RankedPairs
+ elif self.tallying_method == 'schulze-pr':
+ votes = [{ 'count': 1, 'ballot': x } for x in votes if x]
+ method = schulze_pr.SchulzePR
+ elif self.tallying_method == 'schulze-stv':
+ votes = [{ 'count': 1, 'ballot': x } for x in votes if x]
+ method = schulze_stv.SchulzeSTV
+ elif self.tallying_method == 'schulze-npr':
+ votes = [{ 'count': 1, 'ballot': x } for x in votes if x]
+ method = schulze_npr.SchulzeNPR
+ else:
+ raise ValueError, 'unknown method', self.tallying_method
+ # Restrain arguments
+ accepted_args = method.__init__.im_func.func_code.co_varnames
+ for key in kwargs.keys():
+ if key not in accepted_args or kwargs.get(key) is None:
+ del kwargs[key]
+ # Run vote
+ result = method(votes, **kwargs).as_dict()
+
+ r += htmltext('
%s
') % _('Method')
+ r += htmltext('
%s
') % _(METHODS.get(self.tallying_method))
+ if 'candidates' in result:
+ r += htmltext('
%s
') % _('Candidates')
+ r += htmltext('
%s
') % (', '.join(result['candidates']))
+ if 'quota' in result:
+ r += htmltext('
%s
') % _('Quota')
+ r += htmltext('
%s
') % result.get('quota')
+ if 'rounds' in result:
+ for i, _round in enumerate(result['rounds']):
+ r += htmltext('
%s
') % (_('Round %s') % (i+1))
+ if 'loser' in _round:
+ r += htmltext('
%s
') % _('Loser')
+ r += htmltext('
%s
') % _round['loser']
+ if 'tallies' in _round:
+ r += htmltext('
%s
') % _('Tallies')
+ for a, b in _round['tallies'].iteritems():
+ r += htmltext('
%s: %s
') % (a,b)
+ r += htmltext('
')
+ if 'tied_losers' in _round:
+ r += htmltext('
%s
') % _('Tied losers')
+ r += htmltext('
%s
') % ', '.join(list(_round['tied_losers']))
+ if 'winner' in _round:
+ r += htmltext('
%s
') % _('Winner')
+ r += htmltext('
%s
') % _round['winner']
+ r += htmltext('
')
+ if 'pairs' in result:
+ r += htmltext('
%s
') % _('Pairs')
+ for a, b in result['pairs'].iteritems():
+ r += htmltext('
%s
') % ', '.join(a)
+ r += htmltext('
%s
') % b
+ r += htmltext('
')
+ if 'strong_pairs' in result:
+ r += htmltext('
%s
') % _('Strong pairs')
+ for a, b in result['strong_pairs'].iteritems():
+ r += htmltext('
%s
') % ', '.join(a)
+ r += htmltext('
%s
') % b
+ r += htmltext('
')
+
+ if 'winner' in result:
+ r += htmltext('
%s
') % _('Winner')
+ r += htmltext('
%s
') % result['winner']
+ if 'winners' in result:
+ r += htmltext('
%s
') % _('Winners')
+ r += htmltext('
%s
') % ', '.join(result['winners'])
+ if 'order' in result:
+ r += htmltext('
%s
') % _('Order')
+ r += htmltext('
%s
') % ', '.join(result['order'])
+ # FIXME: show actions
+ # import pprint
+ # r += htmltext('