251 lines
8.3 KiB
Python
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)
|
|
|
|
|
|
|