0001-general-remove-vote-extension-and-its-support-code-3.patch
extra/pyvotecore/vote_field.py | ||
---|---|---|
1 |
# w.c.s. - web application for online forms |
|
2 |
# Copyright (C) 2005-2010 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software; you can redistribute it and/or modify |
|
5 |
# it under the terms of the GNU General Public License as published by |
|
6 |
# the Free Software Foundation; either version 2 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU General Public License |
|
15 |
# along with this program; if not, write to the Free Software |
|
16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
17 |
# 02110-1301 USA |
|
18 | ||
19 |
import random |
|
20 |
from quixote.html import htmltext, TemplateIO |
|
21 |
from wcs.qommon.form import CompositeWidget, IntWidget, WidgetList, StringWidget, \ |
|
22 |
CheckboxWidget, SingleSelectWidget |
|
23 |
from wcs.fields import WidgetField, register_field_class |
|
24 |
from pyvotecore import schulze_method, irv, ranked_pairs, schulze_pr, \ |
|
25 |
schulze_stv, schulze_npr |
|
26 | ||
27 |
class VoteWidget(CompositeWidget): |
|
28 |
readonly = False |
|
29 | ||
30 |
def __init__(self, name, value = None, elements = None, **kwargs): |
|
31 |
CompositeWidget.__init__(self, name, value, **kwargs) |
|
32 |
self.element_names = {} |
|
33 | ||
34 |
if kwargs.has_key('title'): |
|
35 |
del kwargs['title'] |
|
36 |
if kwargs.has_key('readonly'): |
|
37 |
if kwargs['readonly']: |
|
38 |
self.readonly = True |
|
39 |
del kwargs['readonly'] |
|
40 |
if kwargs.has_key('required'): |
|
41 |
if kwargs['required']: |
|
42 |
self.required = True |
|
43 |
del kwargs['required'] |
|
44 | ||
45 |
self.randomize_items = False |
|
46 |
if kwargs.has_key('randomize_items'): |
|
47 |
if kwargs['randomize_items']: |
|
48 |
self.randomize_items = True |
|
49 |
del kwargs['randomize_items'] |
|
50 | ||
51 |
for v in elements: |
|
52 |
if type(v) is tuple: |
|
53 |
title = v[1] |
|
54 |
key = v[0] |
|
55 |
if type(key) is int: |
|
56 |
name = 'element%d' % v[0] |
|
57 |
elif type(key) in (str, htmltext): |
|
58 |
name = str('element%s' % v[0]) |
|
59 |
key = str(key) |
|
60 |
else: |
|
61 |
raise NotImplementedError() |
|
62 |
else: |
|
63 |
title = v |
|
64 |
key = v |
|
65 |
name = 'element%d' % len(self.element_names.keys()) |
|
66 | ||
67 |
if value: |
|
68 |
position = value.get(key) |
|
69 |
else: |
|
70 |
position = None |
|
71 |
self.add(IntWidget, name, title = title, value = position, size = 5, **kwargs) |
|
72 |
self.element_names[name] = key |
|
73 | ||
74 |
if self.randomize_items: |
|
75 |
random.shuffle(self.widgets) |
|
76 | ||
77 |
if self.readonly: |
|
78 |
def cmp_w(x, y): |
|
79 |
if x.value is None and y.value is None: |
|
80 |
return 0 |
|
81 |
if x.value is None: |
|
82 |
return 1 |
|
83 |
if y.value is None: |
|
84 |
return -1 |
|
85 |
return cmp(x.value, y.value) |
|
86 |
self.widgets.sort(cmp_w) |
|
87 | ||
88 | ||
89 |
def _parse(self, request): |
|
90 |
values = {} |
|
91 |
for name in self.element_names: |
|
92 |
value = self.get(name) |
|
93 |
values[self.element_names[name]] = value |
|
94 |
if type(value) is not int: |
|
95 |
self.get_widget(name).set_error(IntWidget.TYPE_ERROR) |
|
96 |
self.value = values or None |
|
97 | ||
98 |
def parse(self, request=None): |
|
99 |
value = CompositeWidget.parse(self, request=request) |
|
100 |
for widget in self.widgets: |
|
101 |
if widget.has_error(): |
|
102 |
self.set_error(_('Some fields were not filled properly.')) |
|
103 |
break |
|
104 |
return value |
|
105 | ||
106 |
def render_content(self): |
|
107 |
r = TemplateIO(html=True) |
|
108 |
r += htmltext('<ul>') |
|
109 |
for widget in self.get_widgets(): |
|
110 |
if widget.has_error(): |
|
111 |
r += htmltext('<li class="error"><label>') |
|
112 |
else: |
|
113 |
r += htmltext('<li><label>') |
|
114 |
if self.readonly: |
|
115 |
widget.attrs['disabled'] = 'disabled' |
|
116 |
if widget.value: |
|
117 |
r += htmltext('<input type="hidden" name="%s" value="%s" >') % ( |
|
118 |
widget.name, widget.value) |
|
119 |
widget.name = widget.name + 'xx' |
|
120 |
r += widget.render_content() |
|
121 |
r += htmltext('</label>') |
|
122 |
r += widget.title |
|
123 |
r += htmltext('</li>') |
|
124 |
r += htmltext('</ul>') |
|
125 |
return r.getvalue() |
|
126 | ||
127 |
METHODS = { |
|
128 |
'condorcet-schulze': N_('Condorcet-Shulze'), |
|
129 |
'irv': N_('Instant Run-off'), |
|
130 |
'ranked_pairs': N_('Ranked pairs'), |
|
131 |
'schulze-pr': N_('Schulze Proportional Ranking'), |
|
132 |
'schulze-stv': N_('Schulze STV'), |
|
133 |
'schulze-npr': N_('Schulze NPR'), |
|
134 |
} |
|
135 | ||
136 |
class VoteField(WidgetField): |
|
137 |
key = 'vote-field' |
|
138 |
description = N_('Ranked choice vote') |
|
139 | ||
140 |
items = None |
|
141 |
randomize_items = True |
|
142 |
widget_class = VoteWidget |
|
143 |
required_winners = None |
|
144 |
winner_threshold = None |
|
145 |
tallying_method = 'condorcet-schulze' |
|
146 | ||
147 |
def perform_more_widget_changes(self, form, kwargs, edit = True): |
|
148 |
kwargs['elements'] = self.items or [] |
|
149 |
kwargs['randomize_items'] = self.randomize_items |
|
150 | ||
151 |
def fill_admin_form(self, form): |
|
152 |
WidgetField.fill_admin_form(self, form) |
|
153 |
form.add(WidgetList, 'items', title = _('Items'), element_type = StringWidget, |
|
154 |
value = self.items, required = True, |
|
155 |
element_kwargs = {'render_br': False, 'size': 50}, |
|
156 |
add_element_label = _('Add item')) |
|
157 |
form.add(CheckboxWidget, 'randomize_items', title = _('Randomize Items'), |
|
158 |
value = self.randomize_items) |
|
159 |
form.add(SingleSelectWidget, 'tallying_method', title=_('Tallying method'), |
|
160 |
value=self.tallying_method, |
|
161 |
options=METHODS.items()) |
|
162 |
form.add(IntWidget, 'required_winners', title=_('Required winners'), |
|
163 |
value=self.required_winners), |
|
164 |
form.add(IntWidget, 'winner_threshold', title=_('Winner threshold'), |
|
165 |
value=self.winner_threshold), |
|
166 | ||
167 |
def get_admin_attributes(self): |
|
168 |
return WidgetField.get_admin_attributes(self) + ['items', |
|
169 |
'randomize_items', 'tallying_method', 'required_winners', |
|
170 |
'winner_threshold'] |
|
171 | ||
172 |
def get_view_value(self, value): |
|
173 |
r = TemplateIO(html=True) |
|
174 |
r += htmltext('<ul>') |
|
175 |
items = value.items() |
|
176 |
items.sort(lambda x,y: cmp(x[1], y[1])) |
|
177 |
for it in items: |
|
178 |
if it[1]: |
|
179 |
r += htmltext('<li>%s: %s</li>') % (it[1], it[0]) |
|
180 |
r += htmltext('</ul>') |
|
181 |
return r.getvalue() |
|
182 | ||
183 |
def stats(self, values): |
|
184 |
''' |
|
185 |
Compute vote result using the pyvotecore library. |
|
186 |
''' |
|
187 |
r = TemplateIO(html = True) |
|
188 | ||
189 |
votes = [x.data.get(self.id) for x in values] |
|
190 |
# build ballots |
|
191 |
kwargs = { 'winner_threshold': self.winner_threshold, |
|
192 |
'required_winners': self.required_winners } |
|
193 |
if self.tallying_method == 'condorcet-schulze': |
|
194 |
votes = [{ 'count': 1, 'ballot': x } for x in votes if x] |
|
195 |
method = schulze_method.SchulzeMethod |
|
196 |
elif self.tallying_method == 'irv': |
|
197 |
votes = [ x.items() for x in votes ] |
|
198 |
votes = [ sorted(x, cmp=lambda x,y: cmp(x[1],y[1])) for x in votes ] |
|
199 |
votes = [ {'count':1, 'ballot': [ a for a,b in x ] } for x in votes ] |
|
200 | ||
201 |
method = irv.IRV |
|
202 |
elif self.tallying_method == 'ranked_pairs': |
|
203 |
votes = [{ 'count': 1, 'ballot': x } for x in votes if x] |
|
204 |
method = ranked_pairs.RankedPairs |
|
205 |
elif self.tallying_method == 'schulze-pr': |
|
206 |
votes = [{ 'count': 1, 'ballot': x } for x in votes if x] |
|
207 |
method = schulze_pr.SchulzePR |
|
208 |
elif self.tallying_method == 'schulze-stv': |
|
209 |
votes = [{ 'count': 1, 'ballot': x } for x in votes if x] |
|
210 |
method = schulze_stv.SchulzeSTV |
|
211 |
elif self.tallying_method == 'schulze-npr': |
|
212 |
votes = [{ 'count': 1, 'ballot': x } for x in votes if x] |
|
213 |
method = schulze_npr.SchulzeNPR |
|
214 |
else: |
|
215 |
raise ValueError, 'unknown method', self.tallying_method |
|
216 |
# Restrain arguments |
|
217 |
accepted_args = method.__init__.im_func.func_code.co_varnames |
|
218 |
for key in kwargs.keys(): |
|
219 |
if key not in accepted_args or kwargs.get(key) is None: |
|
220 |
del kwargs[key] |
|
221 |
# Run vote |
|
222 |
result = method(votes, **kwargs).as_dict() |
|
223 | ||
224 |
r += htmltext('<h4>%s</h4>') % _('Method') |
|
225 |
r += htmltext('<p>%s</p>') % _(METHODS.get(self.tallying_method)) |
|
226 |
if 'candidates' in result: |
|
227 |
r += htmltext('<h4>%s</h4>') % _('Candidates') |
|
228 |
r += htmltext('<p>%s</p>') % (', '.join(result['candidates'])) |
|
229 |
if 'quota' in result: |
|
230 |
r += htmltext('<h4>%s</h4>') % _('Quota') |
|
231 |
r += htmltext('<p>%s</p>') % result.get('quota') |
|
232 |
if 'rounds' in result: |
|
233 |
for i, _round in enumerate(result['rounds']): |
|
234 |
r += htmltext('<h4>%s</h4><div class="round">') % (_('Round %s') % (i+1)) |
|
235 |
if 'loser' in _round: |
|
236 |
r += htmltext('<h5>%s</h5>') % _('Loser') |
|
237 |
r += htmltext('<p>%s</p>') % _round['loser'] |
|
238 |
if 'tallies' in _round: |
|
239 |
r += htmltext('<h5>%s</h5><ul>') % _('Tallies') |
|
240 |
for a, b in _round['tallies'].iteritems(): |
|
241 |
r += htmltext('<li>%s: %s</li>') % (a,b) |
|
242 |
r += htmltext('</ul>') |
|
243 |
if 'tied_losers' in _round: |
|
244 |
r += htmltext('<h5>%s</h5>') % _('Tied losers') |
|
245 |
r += htmltext('<p>%s</p>') % ', '.join(list(_round['tied_losers'])) |
|
246 |
if 'winner' in _round: |
|
247 |
r += htmltext('<h5>%s</h5>') % _('Winner') |
|
248 |
r += htmltext('<p>%s</p>') % _round['winner'] |
|
249 |
r += htmltext('</div>') |
|
250 |
if 'pairs' in result: |
|
251 |
r += htmltext('<h4>%s</h4><dl>') % _('Pairs') |
|
252 |
for a, b in result['pairs'].iteritems(): |
|
253 |
r += htmltext('<dt>%s</dt>') % ', '.join(a) |
|
254 |
r += htmltext('<dd>%s</dd>') % b |
|
255 |
r += htmltext('</dl>') |
|
256 |
if 'strong_pairs' in result: |
|
257 |
r += htmltext('<h4>%s</h4><dl>') % _('Strong pairs') |
|
258 |
for a, b in result['strong_pairs'].iteritems(): |
|
259 |
r += htmltext('<dt>%s</dt>') % ', '.join(a) |
|
260 |
r += htmltext('<dd>%s</dd>') % b |
|
261 |
r += htmltext('</dl>') |
|
262 | ||
263 |
if 'winner' in result: |
|
264 |
r += htmltext('<h4>%s</h4>') % _('Winner') |
|
265 |
r += htmltext('<p>%s</p>') % result['winner'] |
|
266 |
if 'winners' in result: |
|
267 |
r += htmltext('<h4>%s</h4>') % _('Winners') |
|
268 |
r += htmltext('<p>%s</p>') % ', '.join(result['winners']) |
|
269 |
if 'order' in result: |
|
270 |
r += htmltext('<h4>%s</h4>') % _('Order') |
|
271 |
r += htmltext('<p>%s</p>') % ', '.join(result['order']) |
|
272 |
# FIXME: show actions |
|
273 |
# import pprint |
|
274 |
# r += htmltext('<pre>%s</pre>') % pprint.pformat(result) |
|
275 |
return r.getvalue() |
|
276 | ||
277 |
register_field_class(VoteField) |
extra/vote/anonymity.py | ||
---|---|---|
1 |
# w.c.s. - web application for online forms |
|
2 |
# Copyright (C) 2005-2010 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software; you can redistribute it and/or modify |
|
5 |
# it under the terms of the GNU General Public License as published by |
|
6 |
# the Free Software Foundation; either version 2 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU General Public License |
|
15 |
# along with this program; if not, write to the Free Software |
|
16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
17 |
# 02110-1301 USA |
|
18 | ||
19 |
from quixote import get_publisher |
|
20 | ||
21 |
from wcs.fields import WidgetField, register_field_class |
|
22 |
from wcs.qommon.form import * |
|
23 | ||
24 |
class VoteAnonymityWidget(CheckboxWidget): |
|
25 |
vote_anonymity = 'anonymous' |
|
26 | ||
27 |
def __init__(self, name, value = None, elements = None, **kwargs): |
|
28 |
if kwargs and kwargs.get('vote_anonymity'): |
|
29 |
self.vote_anonymity = kwargs.get('vote_anonymity') |
|
30 |
if kwargs and 'required' in kwargs: |
|
31 |
del kwargs['required'] |
|
32 |
CheckboxWidget.__init__(self, name, value=value, elements=elements, **kwargs) |
|
33 | ||
34 |
def render(self): |
|
35 |
if self.vote_anonymity != 'choice': |
|
36 |
# XXX: include info for user ? |
|
37 |
return '' |
|
38 |
return CheckboxWidget.render(self) |
|
39 | ||
40 |
def render_title(self, title): |
|
41 |
return CheckboxWidget.render_title(self, _('Anonymous Voting')) |
|
42 | ||
43 |
def render_content(self): |
|
44 |
value = True |
|
45 |
if self.value: |
|
46 |
if self.value[0] == 'anonymous': |
|
47 |
value = True |
|
48 |
else: |
|
49 |
value = False |
|
50 |
return htmltag('input', xml_end=True, |
|
51 |
type='checkbox', |
|
52 |
name=self.name, |
|
53 |
value='yes', |
|
54 |
checked=value and 'checked' or None, |
|
55 |
**self.attrs) |
|
56 | ||
57 |
def _parse(self, request): |
|
58 |
if self.vote_anonymity == 'anonymous': |
|
59 |
self.value = ('anonymous', None) |
|
60 |
elif self.vote_anonymity == 'public': |
|
61 |
self.value = ('public', request.user.id) |
|
62 |
else: |
|
63 |
if type(request.form.get(self.name)) is tuple: |
|
64 |
self.value = request.form.get(self.name) |
|
65 |
else: |
|
66 |
CheckboxWidget._parse(self, request) |
|
67 |
if self.value is True: |
|
68 |
self.value = ('anonymous', None) |
|
69 |
else: |
|
70 |
self.value = ('public', request.user.id) |
|
71 | ||
72 | ||
73 |
class VoteAnonymityField(WidgetField): |
|
74 |
key = 'vote-anonymity' |
|
75 |
description = N_('Vote Anonymity') |
|
76 | ||
77 |
widget_class = VoteAnonymityWidget |
|
78 |
vote_anonymity = 'anonymous' |
|
79 |
# possible values: |
|
80 |
# 'anonymous': anonymous vote |
|
81 |
# 'public': public vote |
|
82 |
# 'choice': anonymous or public, choice of voter |
|
83 | ||
84 |
def fill_admin_form(self, form): |
|
85 |
form.add(StringWidget, 'label', title = _('Label'), value = self.label, |
|
86 |
required = True, size = 50) |
|
87 |
form.add(SingleSelectWidget, 'vote_anonymity', title = _('Vote Anonymity'), |
|
88 |
value = self.vote_anonymity, |
|
89 |
options = [('anonymous', _('Anonymous Vote')), |
|
90 |
('public', _('Public Vote')), |
|
91 |
('choice', _('Choice of Voter'))]) |
|
92 | ||
93 |
def get_admin_attributes(self): |
|
94 |
return ['label', 'vote_anonymity'] |
|
95 | ||
96 |
def perform_more_widget_changes(self, form, kwargs, edit = True): |
|
97 |
kwargs['vote_anonymity'] = self.vote_anonymity |
|
98 | ||
99 |
def get_view_value(self, value): |
|
100 |
public = False |
|
101 |
if self.vote_anonymity == 'choice': |
|
102 |
if value and value[0] == 'public': |
|
103 |
public = True |
|
104 |
if public: |
|
105 |
return get_publisher().user_class.get(value[1]).get_display_name() |
|
106 |
else: |
|
107 |
return _('Anonymous') # XXX: include token? |
|
108 | ||
109 |
register_field_class(VoteAnonymityField) |
extra/vote/ranked_items.py | ||
---|---|---|
1 |
# w.c.s. - web application for online forms |
|
2 |
# Copyright (C) 2005-2010 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software; you can redistribute it and/or modify |
|
5 |
# it under the terms of the GNU General Public License as published by |
|
6 |
# the Free Software Foundation; either version 2 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU General Public License |
|
15 |
# along with this program; if not, write to the Free Software |
|
16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
17 |
# 02110-1301 USA |
|
18 | ||
19 |
import random |
|
20 |
from quixote.html import htmltext, TemplateIO |
|
21 |
from wcs.qommon.form import * |
|
22 |
from wcs.fields import WidgetField, register_field_class |
|
23 | ||
24 |
class RankedItemsWidget(CompositeWidget): |
|
25 |
readonly = False |
|
26 | ||
27 |
def __init__(self, name, value = None, elements = None, **kwargs): |
|
28 |
CompositeWidget.__init__(self, name, value, **kwargs) |
|
29 |
self.element_names = {} |
|
30 | ||
31 |
if kwargs.has_key('title'): |
|
32 |
del kwargs['title'] |
|
33 |
if kwargs.has_key('readonly'): |
|
34 |
if kwargs['readonly']: |
|
35 |
self.readonly = True |
|
36 |
del kwargs['readonly'] |
|
37 |
if kwargs.has_key('required'): |
|
38 |
if kwargs['required']: |
|
39 |
self.required = True |
|
40 |
del kwargs['required'] |
|
41 | ||
42 |
self.randomize_items = False |
|
43 |
if kwargs.has_key('randomize_items'): |
|
44 |
if kwargs['randomize_items']: |
|
45 |
self.randomize_items = True |
|
46 |
del kwargs['randomize_items'] |
|
47 | ||
48 |
for v in elements: |
|
49 |
if type(v) is tuple: |
|
50 |
title = v[1] |
|
51 |
key = v[0] |
|
52 |
if type(key) is int: |
|
53 |
name = 'element%d' % v[0] |
|
54 |
elif type(key) in (str, htmltext): |
|
55 |
name = str('element%s' % v[0]) |
|
56 |
key = str(key) |
|
57 |
else: |
|
58 |
raise NotImplementedError() |
|
59 |
else: |
|
60 |
title = v |
|
61 |
key = v |
|
62 |
name = 'element%d' % len(self.element_names.keys()) |
|
63 | ||
64 |
if value: |
|
65 |
position = value.get(key) |
|
66 |
else: |
|
67 |
position = None |
|
68 |
self.add(IntWidget, name, title = title, value = position, size = 5, **kwargs) |
|
69 |
self.element_names[name] = key |
|
70 | ||
71 |
if self.randomize_items: |
|
72 |
random.shuffle(self.widgets) |
|
73 | ||
74 |
if self.readonly: |
|
75 |
def cmp_w(x, y): |
|
76 |
if x.value is None and y.value is None: |
|
77 |
return 0 |
|
78 |
if x.value is None: |
|
79 |
return 1 |
|
80 |
if y.value is None: |
|
81 |
return -1 |
|
82 |
return cmp(x.value, y.value) |
|
83 |
self.widgets.sort(cmp_w) |
|
84 | ||
85 | ||
86 |
def _parse(self, request): |
|
87 |
values = {} |
|
88 |
for name in self.element_names: |
|
89 |
value = self.get(name) |
|
90 |
values[self.element_names[name]] = value |
|
91 |
if type(value) is not int: |
|
92 |
self.get_widget(name).set_error(IntWidget.TYPE_ERROR) |
|
93 |
self.value = values or None |
|
94 | ||
95 |
def parse(self, request=None): |
|
96 |
value = CompositeWidget.parse(self, request=request) |
|
97 |
for widget in self.widgets: |
|
98 |
if widget.has_error(): |
|
99 |
self.set_error(_('Some fields were not filled properly.')) |
|
100 |
break |
|
101 |
return value |
|
102 | ||
103 |
def render_content(self): |
|
104 |
r = TemplateIO(html=True) |
|
105 |
r += htmltext('<ul>') |
|
106 |
for widget in self.get_widgets(): |
|
107 |
if widget.has_error(): |
|
108 |
r += htmltext('<li class="error"><label>') |
|
109 |
else: |
|
110 |
r += htmltext('<li><label>') |
|
111 |
if self.readonly: |
|
112 |
widget.attrs['disabled'] = 'disabled' |
|
113 |
if widget.value: |
|
114 |
r += htmltext('<input type="hidden" name="%s" value="%s" >') % ( |
|
115 |
widget.name, widget.value) |
|
116 |
widget.name = widget.name + 'xx' |
|
117 |
r += widget.render_content() |
|
118 |
r += htmltext('</label>') |
|
119 |
r += widget.title |
|
120 |
r += htmltext('</li>') |
|
121 |
r += htmltext('</ul>') |
|
122 |
return r.getvalue() |
|
123 | ||
124 | ||
125 |
class RankedItemsField(WidgetField): |
|
126 |
key = 'ranked-items' |
|
127 |
description = N_('Ranked Items') |
|
128 | ||
129 |
items = None |
|
130 |
randomize_items = True |
|
131 |
widget_class = RankedItemsWidget |
|
132 | ||
133 |
def perform_more_widget_changes(self, form, kwargs, edit = True): |
|
134 |
kwargs['elements'] = self.items or [] |
|
135 |
kwargs['randomize_items'] = self.randomize_items |
|
136 | ||
137 |
def fill_admin_form(self, form): |
|
138 |
WidgetField.fill_admin_form(self, form) |
|
139 |
form.add(WidgetList, 'items', title = _('Items'), element_type = StringWidget, |
|
140 |
value = self.items, required = True, |
|
141 |
element_kwargs = {'render_br': False, 'size': 50}, |
|
142 |
add_element_label = _('Add item')) |
|
143 |
form.add(CheckboxWidget, 'randomize_items', title = _('Randomize Items'), |
|
144 |
value = self.randomize_items) |
|
145 | ||
146 |
def get_admin_attributes(self): |
|
147 |
return WidgetField.get_admin_attributes(self) + ['items', 'randomize_items'] |
|
148 | ||
149 |
def get_view_value(self, value): |
|
150 |
r = TemplateIO(html=True) |
|
151 |
r += htmltext('<ul>') |
|
152 |
items = value.items() |
|
153 |
items.sort(lambda x,y: cmp(x[1], y[1])) |
|
154 |
for it in items: |
|
155 |
if it[1]: |
|
156 |
r += htmltext('<li>%s: %s</li>') % (it[1], it[0]) |
|
157 |
r += htmltext('</ul>') |
|
158 |
return r.getvalue() |
|
159 | ||
160 |
def stats(self, values): |
|
161 |
r = TemplateIO(html = True) |
|
162 | ||
163 |
# hardcoded to condorcet for the moment |
|
164 |
candidates = self.items |
|
165 |
# compute matrix |
|
166 |
matrix = {} |
|
167 |
for c1 in candidates: |
|
168 |
matrix[c1] = {} |
|
169 |
for c2 in candidates: |
|
170 |
matrix[c1][c2] = 0 |
|
171 |
votes = [x.data.get(self.id) for x in values] |
|
172 |
votes = [x for x in votes if x] |
|
173 |
for vote in votes: |
|
174 |
for c1 in candidates: |
|
175 |
for c2 in candidates: |
|
176 |
if c1 == c2: |
|
177 |
continue |
|
178 |
vote_a = vote.get(c1) |
|
179 |
vote_b = vote.get(c2) |
|
180 |
if vote_a is None: |
|
181 |
vote_a = 99999 # XXX MAX_INT |
|
182 |
if vote_b is None: |
|
183 |
vote_b = 99999 # idem |
|
184 |
if int(vote_a) == int(vote_b): |
|
185 |
matrix[c1][c2] += 0.5 |
|
186 |
elif int(vote_a) < int(vote_b): |
|
187 |
matrix[c1][c2] += 1 |
|
188 |
import pprint |
|
189 |
pprint.pprint(matrix) |
|
190 |
# compute ratings |
|
191 |
ratings = {} |
|
192 |
for c1 in candidates: |
|
193 |
ratings[c1] = {'win': [], 'loss': [], 'tie': [], 'worst': 0} |
|
194 |
for c2 in candidates: |
|
195 |
if c1 == c2: |
|
196 |
continue |
|
197 |
delta = matrix[c1][c2] - matrix[c2][c1] |
|
198 |
if delta > 0: |
|
199 |
ratings[c1]['win'].append(c2) |
|
200 |
elif delta < 0: |
|
201 |
ratings[c1]['loss'].append(c2) |
|
202 |
if delta < ratings[c1]['worst']: |
|
203 |
ratings[c1]['worst'] = -delta |
|
204 |
else: |
|
205 |
ratings[c1]['tie'].append(c2) |
|
206 | ||
207 |
pprint.pprint(ratings) |
|
208 | ||
209 |
# compute winner |
|
210 |
winners = [] |
|
211 |
remaining = candidates[:] |
|
212 |
for c1 in remaining: |
|
213 |
rating = ratings[c1] |
|
214 |
winner = True |
|
215 |
for loss in rating['loss']: |
|
216 |
if loss not in winners: |
|
217 |
winner = False |
|
218 |
break |
|
219 |
if not winner: |
|
220 |
continue |
|
221 |
for tie in rating['tie']: |
|
222 |
if tie not in winners: |
|
223 |
winner = False |
|
224 |
break |
|
225 |
if not winner: |
|
226 |
continue |
|
227 |
winners.append(c1) |
|
228 |
remaining.remove(c1) |
|
229 |
break |
|
230 |
else: |
|
231 |
narrowest = None |
|
232 |
winners = [] |
|
233 |
for c2 in remaining: |
|
234 |
rating = ratings[c2] |
|
235 |
if narrowest is None or rating['worst'] < narrowest: |
|
236 |
narrowest = rating['worst'] |
|
237 |
winners = [c2] |
|
238 |
elif rating['worst'] == narrowest: |
|
239 |
winners.append(c2) |
|
240 | ||
241 |
candidates.sort(lambda x,y: cmp(len(ratings[x]['win']), len(ratings[y]['win']))) |
|
242 | ||
243 |
r += htmltext('<table>' |
|
244 |
'<thead>' |
|
245 |
'<tr>' |
|
246 |
'<td></td>') |
|
247 |
for c in candidates: |
|
248 |
r += htmltext('<th>%s</th>' % c) |
|
249 |
r += htmltext('</tr>' |
|
250 |
'</thead>' |
|
251 |
'<tbody>') |
|
252 |
for c1 in candidates: |
|
253 |
r += htmltext('<tr>' |
|
254 |
'<th>%s</th>' % c1) |
|
255 |
for c2 in candidates: |
|
256 |
if c2 == c1: |
|
257 |
r += htmltext('<td></td>') |
|
258 |
else: |
|
259 |
if matrix[c1][c2] > matrix[c2][c1]: |
|
260 |
color = '#0f0' |
|
261 |
elif matrix[c1][c2] == matrix[c2][c1]: |
|
262 |
color = '#ff0' |
|
263 |
else: |
|
264 |
color = '#f00' |
|
265 |
r += htmltext('<td style="background: %s">' % color) |
|
266 |
r += '%.1f' % matrix[c1][c2] |
|
267 |
r += htmltext('</td>') |
|
268 |
r += htmltext('</tr>') |
|
269 |
r += htmltext('</tbody>' |
|
270 |
'</table>' |
|
271 |
'<p>') |
|
272 |
r += _('Winner:') |
|
273 |
r += ' ' |
|
274 |
r += ' '.join(winners) |
|
275 |
r += htmltext('</p>') |
|
276 |
return r.getvalue() |
|
277 | ||
278 |
register_field_class(RankedItemsField) |
|
279 | ||
280 | ||
281 |
wcs/formdef.py | ||
---|---|---|
1186 | 1186 |
def get_detailed_email_form(self, formdata, url): |
1187 | 1187 |
details = [] |
1188 | 1188 | |
1189 |
display_username = True |
|
1190 |
# this is custom code so it is possible to mark forms as anonyms, this |
|
1191 |
# is done through the VoteAnonymity field, this is very specific but |
|
1192 |
# isn't generalised yet into an useful extension mechanism, as it's not |
|
1193 |
# clear at the moment what could be useful. |
|
1194 |
for f in self.fields: |
|
1195 |
if f.key == 'vote-anonymity': |
|
1196 |
display_username = False |
|
1197 |
break |
|
1198 | ||
1199 |
if display_username and formdata.user_id and formdata.user: |
|
1189 |
if formdata.user_id and formdata.user: |
|
1200 | 1190 |
details.append(_('User name:')) |
1201 | 1191 |
details.append(' %s' % formdata.user.name) |
1202 | 1192 |
details.append('') |
wcs/forms/common.py | ||
---|---|---|
357 | 357 |
except KeyError: |
358 | 358 |
user = None |
359 | 359 | |
360 |
# this is custom code so it is possible to mark forms as anonyms, this |
|
361 |
# is done through the VoteAnonymity field, this is very specific but |
|
362 |
# isn't generalised yet into an useful extension mechanism, as it's not |
|
363 |
# clear at the moment what could be useful. |
|
364 |
for f in self.formdef.fields: |
|
365 |
if f.key == 'vote-anonymity': |
|
366 |
user = None |
|
367 |
break |
|
368 | ||
369 | 360 |
r = TemplateIO(html=True) |
370 | 361 |
klasses = 'foldable' |
371 | 362 |
if self.should_fold_summary(mine, request_user): |
372 |
- |