wcs/extra/vote/ranked_items.py

251 lines
8.3 KiB
Python

import random
from quixote.html import htmltext, TemplateIO
from qommon.form import *
from wcs.fields import WidgetField, register_field_class
class RankedItemsWidget(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
self.value = values or None
def render_content(self):
r = TemplateIO(html=True)
r += htmltext('<ul>')
for widget in self.get_widgets():
r += htmltext('<li><label>')
if self.readonly:
widget.attrs['disabled'] = 'disabled'
if widget.value:
r += htmltext('<input type="hidden" name="%s" value="%s" >') % (
widget.name, widget.value)
widget.name = widget.name + 'xx'
r += widget.render_content()
r += htmltext('</label>')
r += widget.title
r += htmltext('</li>')
r += htmltext('</ul>')
return r.getvalue()
class RankedItemsField(WidgetField):
key = 'ranked-items'
description = N_('Ranked Items')
items = None
randomize_items = True
widget_class = RankedItemsWidget
def perform_more_widget_changes(self, form, kwargs, edit = True):
kwargs['elements'] = self.items or []
kwargs['randomize_items'] = self.randomize_items
def fill_admin_form(self, form):
WidgetField.fill_admin_form(self, form)
form.add(WidgetList, 'items', title = _('Items'), element_type = StringWidget,
value = self.items, required = True,
element_kwargs = {'render_br': False, 'size': 50},
add_element_label = _('Add item'))
form.add(CheckboxWidget, 'randomize_items', title = _('Randomize Items'),
value = self.randomize_items)
def get_admin_attributes(self):
return WidgetField.get_admin_attributes(self) + ['items', 'randomize_items']
def get_view_value(self, value):
r = TemplateIO(html=True)
r += htmltext('<ul>')
items = value.items()
items.sort(lambda x,y: cmp(x[1], y[1]))
for it in items:
if it[1]:
r += htmltext('<li>%s: %s</li>') % (it[1], it[0])
r += htmltext('</ul>')
return r.getvalue()
def stats(self, values):
r = TemplateIO(html = True)
# hardcoded to condorcet for the moment
candidates = self.items
# compute matrix
matrix = {}
for c1 in candidates:
matrix[c1] = {}
for c2 in candidates:
matrix[c1][c2] = 0
votes = [x.data.get(self.id) for x in values]
votes = [x for x in votes if x]
for vote in votes:
for c1 in candidates:
for c2 in candidates:
if c1 == c2:
continue
vote_a = vote.get(c1)
vote_b = vote.get(c2)
if vote_a is None:
vote_a = 99999 # XXX MAX_INT
if vote_b is None:
vote_b = 99999 # idem
if int(vote_a) == int(vote_b):
matrix[c1][c2] += 0.5
elif int(vote_a) < int(vote_b):
matrix[c1][c2] += 1
import pprint
pprint.pprint(matrix)
# compute ratings
ratings = {}
for c1 in candidates:
ratings[c1] = {'win': [], 'loss': [], 'tie': [], 'worst': 0}
for c2 in candidates:
if c1 == c2:
continue
delta = matrix[c1][c2] - matrix[c2][c1]
if delta > 0:
ratings[c1]['win'].append(c2)
elif delta < 0:
ratings[c1]['loss'].append(c2)
if delta < ratings[c1]['worst']:
ratings[c1]['worst'] = -delta
else:
ratings[c1]['tie'].append(c2)
pprint.pprint(ratings)
# compute winner
winners = []
remaining = candidates[:]
for c1 in remaining:
rating = ratings[c1]
winner = True
for loss in rating['loss']:
if loss not in winners:
winner = False
break
if not winner:
continue
for tie in rating['tie']:
if tie not in winners:
winner = False
break
if not winner:
continue
winners.append(c1)
remaining.remove(c1)
break
else:
narrowest = None
winners = []
for c2 in remaining:
rating = ratings[c2]
if narrowest is None or rating['worst'] < narrowest:
narrowest = rating['worst']
winners = [c2]
elif rating['worst'] == narrowest:
winners.append(c2)
candidates.sort(lambda x,y: cmp(len(ratings[x]['win']), len(ratings[y]['win'])))
r += htmltext('<table>'
'<thead>'
'<tr>'
'<td></td>')
for c in candidates:
r += htmltext('<th>%s</th>' % c)
r += htmltext('</tr>'
'</thead>'
'<tbody>')
for c1 in candidates:
r += htmltext('<tr>'
'<th>%s</th>' % c1)
for c2 in candidates:
if c2 == c1:
r += htmltext('<td></td>')
else:
if matrix[c1][c2] > matrix[c2][c1]:
color = '#0f0'
elif matrix[c1][c2] == matrix[c2][c1]:
color = '#ff0'
else:
color = '#f00'
r += htmltext('<td style="background: %s">' % color)
r += '%.1f' % matrix[c1][c2]
r += htmltext('</td>')
r += htmltext('</tr>')
r += htmltext('</tbody>'
'</table>'
'<p>')
r += _('Winner:')
r += ' '
r += ' '.join(winners)
r += htmltext('</p>')
return r.getvalue()
register_field_class(RankedItemsField)