892 lines
35 KiB
Python
892 lines
35 KiB
Python
# -*- coding: iso-8859-15 -*-
|
|
|
|
|
|
# Glasnost
|
|
# By: Odile Bénassy <obenassy@entrouvert.com>
|
|
# Romain Chantereau <rchantereau@entrouvert.com>
|
|
# Nicolas Clapiès <nclapies@easter-eggs.org>
|
|
# Pierre-Antoine Dejace <padejace@entrouvert.be>
|
|
# Thierry Dulieu <tdulieu@easter-eggs.com>
|
|
# Florent Monnier <monnier@codelutin.com>
|
|
# Cédric Musso <cmusso@easter-eggs.org>
|
|
# Frédéric Péters <fpeters@entrouvert.be>
|
|
# Benjamin Poussin <poussin@codelutin.com>
|
|
# Emmanuel Raviart <eraviart@entrouvert.com>
|
|
# Sébastien Régnier <regnier@codelutin.com>
|
|
# Emmanuel Saracco <esaracco@easter-eggs.com>
|
|
#
|
|
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
|
|
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
|
|
# Emmanuel Saracco & Théridion
|
|
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
|
|
# Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
|
|
# Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
|
|
# Régnier, Emmanuel Saracco, Théridion & Vecam
|
|
#
|
|
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
|
|
__doc__ = """Glasnost Elections Web Proxy"""
|
|
|
|
__version__ = '$Revision$'[11:-2]
|
|
|
|
|
|
import glasnost.common.context as context
|
|
import glasnost.common.faults as faults
|
|
import glasnost.common.tools_new as commonTools
|
|
|
|
from glasnost.proxy.ElectionsProxy import *
|
|
|
|
from ObjectsWeb import register, AdminMixin, ObjectWebMixin, ObjectsWebMixin
|
|
from tools import *
|
|
import VotesWeb # Do not remove!
|
|
|
|
try:
|
|
import Graphs
|
|
except ImportError:
|
|
Graphs = None
|
|
|
|
class AdminElections(AdminMixin, AdminElections):
|
|
pass
|
|
register(AdminElections)
|
|
|
|
|
|
class ElectionMixin(ObjectWebMixin):
|
|
ballotKind_kind_defaultValue = 'voterChoice'
|
|
ballotKind_kind_widget_fieldLabel = N_('Kind of Ballots')
|
|
ballotKind_kind_widget_labels = {
|
|
'public': N_('Public Ballots'),
|
|
'secret': N_('Secret Ballots'),
|
|
'voterChoice': N_('Voters\' Choice'),
|
|
}
|
|
ballotKind_kind_widgetName = 'Select'
|
|
|
|
candidateIds_kind_stateInEditMode = 'hidden'
|
|
candidateIds_kind_stateInViewMode = 'hidden'
|
|
|
|
candidatesSet_kind_itemKind_value_widgetName = 'SelectId'
|
|
candidatesSet_kind_widget_fieldLabel = N_('Candidates')
|
|
candidatesSet_kind_widgetName = 'Multi'
|
|
|
|
endTime_kind_widget_fieldLabel = N_('End Time')
|
|
|
|
isAlwaysRunning_kind_defaultValue = 0
|
|
isAlwaysRunning_kind_widget_fieldLabel = N_('Election Duration')
|
|
isAlwaysRunning_kind_widget_labels = {
|
|
'0': N_('One-Time'),
|
|
'1': N_('Never Ending'),
|
|
}
|
|
isAlwaysRunning_kind_widgetName = 'InputCheckBox'
|
|
|
|
method = None
|
|
method_kind_defaultValue = 'condorcet'
|
|
#method_kind_hasToMakeFieldFromValue = 0
|
|
method_kind_isRequired = 1
|
|
method_kind_values = [
|
|
'average',
|
|
'condorcet',
|
|
]
|
|
method_kind_widget_apply = 1
|
|
method_kind_widget_fieldLabel = N_('Electoral Method')
|
|
method_kind_widget_labels = {
|
|
'average': N_('Best Average'),
|
|
'condorcet': N_('Condorcet Method'),
|
|
}
|
|
method_kindName = 'Choice'
|
|
|
|
state_kind_defaultValue = 'draft'
|
|
state_kind_widget_apply = 1
|
|
state_kind_widget_fieldLabel = N_('State')
|
|
state_kind_widget_labels = {
|
|
'closed': N_('Ended'),
|
|
'draft': N_('Being Written'),
|
|
'proposed': N_('Submitted for Evaluation'),
|
|
'running': N_('Running'),
|
|
}
|
|
state_kind_widgetName = 'Select'
|
|
|
|
subject_kind_widget_fieldLabel = N_('Subject')
|
|
subject_kind_widget_cols = 75
|
|
subject_kind_widget_colSpan = 2
|
|
subject_kind_widget_rows = 4
|
|
subject_kind_widgetName = 'TextArea'
|
|
|
|
title_kind_widget_fieldLabel = N_('Title')
|
|
title_kind_widget_size = 40
|
|
title_kind_widgetName = 'InputText'
|
|
|
|
voteKind_kind_defaultValue = 'VoteRanking'
|
|
voteKind_kind_widget_fieldLabel = N_('Kind of Vote')
|
|
voteKind_kind_widget_labels = {
|
|
'VoteApproval': N_('Approval'),
|
|
'VoteDistribution': N_('Percentage'),
|
|
'VoteRanking': N_('Ranking'),
|
|
'VoteRating': N_('Rating'),
|
|
}
|
|
voteKind_kind_widgetName = 'Select'
|
|
|
|
votersSet_kind_itemKind_value_widgetName = 'SelectId'
|
|
votersSet_kind_widget_fieldLabel = N_('Voters')
|
|
votersSet_kind_widgetName = 'Multi'
|
|
|
|
voteTokens_kind_stateInEditMode = 'hidden'
|
|
voteTokens_kind_stateInViewMode = 'hidden'
|
|
|
|
weightingsGradeId_kind_widget_fieldLabel = N_('Voters Weightings')
|
|
weightingsGradeId_kind_widgetName = 'SelectId'
|
|
|
|
winnerIds_kind_itemKind_value_widgetName = 'SelectId'
|
|
winnerIds_kind_widget_fieldLabel = N_('Winners')
|
|
winnerIds_kind_widgetName = 'Multi'
|
|
|
|
winnersCount_kind_defaultValue = 1
|
|
winnersCount_kind_min = 0
|
|
winnersCount_kind_textMaxLength = 3
|
|
winnersCount_kind_widget_fieldLabel = N_('Winners Count')
|
|
winnersCount_kind_widget_size = 3
|
|
winnersCount_kind_widgetName = 'InputText'
|
|
|
|
winnersGroupId_kind_widget_fieldLabel = N_('Winners Group')
|
|
winnersGroupId_kind_widgetName = 'SelectId'
|
|
|
|
def countVotes(self):
|
|
ballotsWeb = getWebForServerRole('ballots')
|
|
registeredVotersCount = 0
|
|
abstentionsCount = 0
|
|
blanksCount = 0
|
|
voterIds = self.getVoterIds()
|
|
registeredVotersCount = len(voterIds)
|
|
votes = ballotsWeb.getElectionVotes(self.voteTokens, voterIds)
|
|
nonAbstentionsCount = 0
|
|
if len(votes) > 0:
|
|
candidateIds = self.getSortedCandidateIds()
|
|
for vote in votes:
|
|
nonAbstentionsCount = nonAbstentionsCount + 1
|
|
if vote.isBlank(candidateIds):
|
|
blanksCount = blanksCount + 1
|
|
abstentionsCount = registeredVotersCount - nonAbstentionsCount
|
|
return registeredVotersCount, abstentionsCount, blanksCount
|
|
|
|
def getEditLayoutReadOnlySlotNames(self, fields, parentSlot = None):
|
|
readOnlySlotNames = ObjectWebMixin.getEditLayoutReadOnlySlotNames(
|
|
self, fields, parentSlot = parentSlot)
|
|
readOnlySlotNames = readOnlySlotNames[:]
|
|
if not self.id:
|
|
slotName = 'state'
|
|
if not slotName in readOnlySlotNames:
|
|
readOnlySlotNames.append(slotName)
|
|
if self.state in ['closed', 'running']:
|
|
for slotName in [
|
|
'ballotKind', 'candidatesSet', 'method', 'subject', 'title',
|
|
'voteKind', 'votersSet', 'weightingsGradeId']:
|
|
if not slotName in readOnlySlotNames:
|
|
readOnlySlotNames.append(slotName)
|
|
return readOnlySlotNames
|
|
|
|
def getEditLayoutSlotNames(self, fields, parentSlot = None):
|
|
slotNames = ObjectWebMixin.getEditLayoutSlotNames(
|
|
self, fields, parentSlot = parentSlot)
|
|
slotNames = slotNames[:]
|
|
for slotName in ['winnerIds']:
|
|
if slotName in slotNames:
|
|
slotNames.remove(slotName)
|
|
return slotNames
|
|
|
|
def getResultsLayout(
|
|
self, registeredVotersCount, abstentionsCount, blanksCount):
|
|
layout = X.array(
|
|
X.h3()(_('Analyze')),
|
|
X.table(_class = 'election-results')(
|
|
X.caption(_('Analyse of the election')),
|
|
X.tr(
|
|
X.th(_class = 'field-label')(_('Registered Voters:')),
|
|
X.td(_class = 'field-value')(registeredVotersCount),
|
|
),
|
|
X.tr(
|
|
X.th(_class = 'field-label')(_('Abstentions:')),
|
|
X.td(_class = 'field-value')(abstentionsCount),
|
|
),
|
|
X.tr(
|
|
X.th(_class = 'field-label')(_('Voters:')),
|
|
X.td(_class = 'field-value')(
|
|
registeredVotersCount - abstentionsCount),
|
|
),
|
|
X.tr(
|
|
X.th(_class = 'field-label')(_('Blank Ballots:')),
|
|
X.td(_class = 'field-value')(blanksCount),
|
|
),
|
|
X.tr(
|
|
X.th(_class = 'field-label')(_('Expressed Votes:')),
|
|
X.td(_class = 'field-value')(
|
|
registeredVotersCount - abstentionsCount - blanksCount),
|
|
),
|
|
))
|
|
return layout
|
|
|
|
def getViewLayoutSlotNames(self, fields, parentSlot = None):
|
|
slotNames = ObjectWebMixin.getViewLayoutSlotNames(
|
|
self, fields, parentSlot = parentSlot)
|
|
slotNames = slotNames[:]
|
|
userToken = context.getVar('userToken', default = '')
|
|
if not userToken or context.getVar('useCompactLayout', default = 0):
|
|
for slotName in ('authorsSet', 'creationTime', 'modificationTime',
|
|
'readersSet', 'writersSet'):
|
|
if slotName in slotNames:
|
|
slotNames.remove(slotName)
|
|
if self.weightingsGradeId is None:
|
|
slotName = 'weightingsGradeId'
|
|
if slotName in slotNames:
|
|
slotNames.remove(slotName)
|
|
if self.winnersCount == 0:
|
|
slotName = 'winnersCount'
|
|
if slotName in slotNames:
|
|
slotNames.remove(slotName)
|
|
if self.winnersGroupId is None:
|
|
slotName = 'winnersGroupId'
|
|
if slotName in slotNames:
|
|
slotNames.remove(slotName)
|
|
if self.winnersCount == 0 or not self.canCompute():
|
|
slotName = 'winnerIds'
|
|
if slotName in slotNames:
|
|
slotNames.remove(slotName)
|
|
return slotNames
|
|
|
|
def getVotesLayout(self):
|
|
ballotsWeb = getWebForServerRole('ballots')
|
|
gradesWeb = getWebForServerRole('grades')
|
|
peopleWeb = getWebForServerRole('people')
|
|
|
|
layout = X.array()
|
|
userVoteToken = None
|
|
userId = context.getVar('userId', default = '')
|
|
if userId:
|
|
user = peopleWeb.getObject(userId)
|
|
if user.voteTokens is not None \
|
|
and user.voteTokens.has_key(self.id):
|
|
userVoteToken = user.voteTokens[self.id]
|
|
voterIds = self.getVoterIds()
|
|
votes = ballotsWeb.getElectionVotes(self.voteTokens, voterIds)
|
|
if len(votes) > 0:
|
|
layout += X.h3()(_('Votes'))
|
|
table = X.table(_class = 'election-votes')
|
|
table += X.caption(_('Votes for the election'))
|
|
layout += table
|
|
table += X.colgroup()
|
|
thead = X.thead()
|
|
tr = X.tr()
|
|
thead += tr
|
|
tr += X.th()
|
|
if self.weightingsGradeId is not None:
|
|
weightingsGrade = gradesWeb.getObject(
|
|
self.weightingsGradeId)
|
|
weightings = weightingsGrade.repairMarks(
|
|
weightingsGrade.marks, voterIds)
|
|
else:
|
|
weightings = None
|
|
if weightings is not None:
|
|
table += X.colgroup()
|
|
tr += X.td()( _('Weightings') )
|
|
candidateIds = self.getSortedCandidateIds()
|
|
table += X.colgroup(span = len(candidateIds))
|
|
table += thead
|
|
tbody = X.tbody()
|
|
table += tbody
|
|
for candidateId in candidateIds:
|
|
tr += X.th(scope = 'col')(X.objectHypertextLabel(candidateId))
|
|
for vote in votes:
|
|
tr = X.tr()
|
|
tbody += tr
|
|
if vote.comment:
|
|
comment = X.array(
|
|
X.br(),
|
|
X.a(href = X.idUrl(vote.id))(_('comment')),
|
|
)
|
|
else:
|
|
comment = None
|
|
if self.ballotKind == 'secret' \
|
|
or self.ballotKind == 'voterChoice' \
|
|
and vote.ballotKind == 'secret':
|
|
tdAttributes = { 'scope': 'row' }
|
|
if vote.token == userVoteToken:
|
|
realToken = vote.token
|
|
token = X.a(href = X.roleUrl('votes').add(
|
|
'electionId', self.id).add(
|
|
'personId', userId))(vote.token)
|
|
tdAttributes['class'] = 'owned-vote'
|
|
else:
|
|
realToken = token = vote.token
|
|
shortToken = realToken
|
|
if shortToken.startswith(context.getVar('dispatcherId')):
|
|
shortToken = shortToken[len(context.getVar(
|
|
'dispatcherId'))+7:]
|
|
tr += X.th(**tdAttributes)(
|
|
X.array(_('Secret vote'),
|
|
X.br(),
|
|
_('(token: %s)') % shortToken, comment))
|
|
if weightings is not None:
|
|
if vote.token == userVoteToken:
|
|
voterId = ballotsWeb.getVoteVoterId(
|
|
vote.id)
|
|
tr += X.td()(
|
|
'%.2f' % (weightings[voterId] * 100),
|
|
X.nbsp,
|
|
'%',
|
|
)
|
|
else:
|
|
tr += X.td()( _('secret'))
|
|
else:
|
|
voterId = ballotsWeb.getVoteVoterId(vote.id)
|
|
tdAttributes = { 'scope': 'row'}
|
|
if vote.token == userVoteToken:
|
|
tdAttributes['class'] = 'owned-vote'
|
|
tr += X.th( **tdAttributes)(
|
|
X.objectHypertextLabel(voterId), comment)
|
|
if weightings is not None:
|
|
if vote.token == userVoteToken:
|
|
tr += X.td()(
|
|
'%.2f' % (weightings[voterId] * 100),
|
|
X.nbsp,
|
|
'%',
|
|
)
|
|
else:
|
|
tr += X.td()(_('secret'))
|
|
if vote.isBlank(candidateIds):
|
|
tr += X.td(colspan = len(candidateIds))(_('blank vote'))
|
|
else:
|
|
tr += vote.getMarksRowLayout(candidateIds)
|
|
return layout
|
|
|
|
def newVote(self):
|
|
vote = commonTools.newThing('object', 'votes.%s' % self.voteKind)
|
|
if self.ballotKind != 'voter-choice':
|
|
vote.ballotKind = self.ballotKind
|
|
return vote
|
|
|
|
|
|
class AbstractElection(ElectionMixin, AbstractElection):
|
|
pass
|
|
register(AbstractElection)
|
|
|
|
|
|
class ElectionAverage(ElectionMixin, ElectionAverage):
|
|
ratings_kind_stateInEditMode = 'hidden'
|
|
ratings_kind_stateInViewMode = 'hidden'
|
|
|
|
voteKind_kind_defaultValue = 'VoteRating'
|
|
|
|
winnersGradeId_kind_widget_fieldLabel = N_('Winners Grading')
|
|
winnersGradeId_kind_widgetName = 'SelectId'
|
|
|
|
def getAnalyzeLayout(self):
|
|
if self.winnerIds:
|
|
if len(self.winnerIds) == 1:
|
|
analyze = _(
|
|
'<p>The candidate "<i>%(candidate)s</i>" gets the '\
|
|
'best average rating.</p> <p>The candidate '\
|
|
'"<i>%(candidate)s</i>" wins the election.</p>') % {
|
|
'candidate': X.objectHypertextLabel(self.winnerIds[0]),
|
|
}
|
|
else:
|
|
candidates = '"<i>%s</i>"' % X.objectHypertextLabel(
|
|
self.winnerIds[0])
|
|
for winnerId in self.winnerIds[1:-1]:
|
|
candidates = candidates + ', "<i>%s</i>"' \
|
|
% X.objectHypertextLabel(winnerId)
|
|
candidates = candidates + ' & "<i>%s</i>"' \
|
|
% X.objectHypertextLabel(self.winnerIds[-1])
|
|
analyze = _(
|
|
'<p>The candidates <i>%(candidates)s</i> get the '\
|
|
'best average ratings.</p> <p>The candidates '\
|
|
'%(candidates)s win the election.</p>') % {
|
|
'candidates': candidates,
|
|
}
|
|
else:
|
|
analyze = _(
|
|
"""<p>No candidate gets an average rating above the other ones.</p>
|
|
<p>The election has no winner.</p>""")
|
|
return X.asIs(analyze)
|
|
|
|
def getDetailsLayout(
|
|
self, registeredVotersCount, abstentionsCount, blanksCount):
|
|
if not self.ratings:
|
|
return None
|
|
for k in self.ratings.keys():
|
|
if self.ratings[k] is None:
|
|
self.ratings[k] = 0
|
|
layout = X.array()
|
|
layout += X.h3()(_('Results'))
|
|
table = X.table(_class = 'election-details')
|
|
table += X.caption(_('Details of the election'))
|
|
layout += table
|
|
table += X.tr(
|
|
X.th(scope = 'col')(_('Candidates')),
|
|
X.th(scope = 'col')(_('Means')),
|
|
)
|
|
minRating = min(self.ratings.values())
|
|
maxRating = max(self.ratings.values())
|
|
if minRating < maxRating:
|
|
averageRating = (maxRating + minRating) / 2.0
|
|
coef = 255 / float(averageRating - minRating)
|
|
candidateIds = self.getSortedCandidateIds()
|
|
for candidateId in candidateIds:
|
|
rating = self.ratings[candidateId]
|
|
if rating is None:
|
|
rating = 0
|
|
tdAttributes = {}
|
|
if minRating < maxRating:
|
|
if rating <= averageRating:
|
|
red = 255
|
|
green = coef * (rating - minRating)
|
|
else:
|
|
red = coef * (maxRating - rating)
|
|
green = 255
|
|
tdAttributes['style'] = \
|
|
'background-color: #%(red)02x%(green)02x00' % {
|
|
'green': green,
|
|
'red': red,
|
|
}
|
|
table += X.tr(
|
|
X.th(scope = 'row')(
|
|
X.objectHypertextLabel(candidateId)),
|
|
X.td(**tdAttributes)(
|
|
'%.2f' % (rating * 100),
|
|
X.nbsp,
|
|
'%',
|
|
),
|
|
)
|
|
return layout
|
|
|
|
def getGraphPie(self):
|
|
if not Graphs:
|
|
raise Exception('Graphing module not available')
|
|
candidateIds = self.getSortedCandidateIds()
|
|
slices = []
|
|
for candidateId in candidateIds:
|
|
slices.append(Graphs.Slice(
|
|
getObjectLabelTranslated(
|
|
candidateId, context.getVar('readLanguages')),
|
|
self.ratings[candidateId]))
|
|
return Graphs.getPie(slices)
|
|
|
|
def getSortedCandidateIds(self):
|
|
candidateIds = self.candidateIds[:]
|
|
if self.ratings is not None and len(self.ratings) > 0:
|
|
candidateIds.sort(lambda id1, id2, ratings = self.ratings:
|
|
cmp(ratings[id2], ratings[id1]))
|
|
return candidateIds
|
|
|
|
def getViewLayoutSlotNames(self, fields, parentSlot = None):
|
|
slotNames = ElectionMixin.getViewLayoutSlotNames(
|
|
self, fields, parentSlot = parentSlot)
|
|
slotNames = slotNames[:]
|
|
if self.winnersGradeId is None:
|
|
slotName = 'winnersGradeId'
|
|
if slotName in slotNames:
|
|
slotNames.remove(slotName)
|
|
return slotNames
|
|
register(ElectionAverage)
|
|
|
|
|
|
class ElectionCondorcet(ElectionMixin, ElectionCondorcet):
|
|
pairwiseMatrix_kind_stateInEditMode = 'hidden'
|
|
pairwiseMatrix_kind_stateInViewMode = 'hidden'
|
|
|
|
ratings_kind_stateInEditMode = 'hidden'
|
|
ratings_kind_stateInViewMode = 'hidden'
|
|
|
|
voteKind_kind_defaultValue = 'VoteRanking'
|
|
|
|
def getAnalyzeLayout(self):
|
|
if self.winnerIds:
|
|
if len(self.winnerIds) == 1:
|
|
analyze = _(
|
|
'<p>The candidate "<i>%(candidate)s</i>" beats each '\
|
|
'of the other candidates.</p><p>The candidate "<i> '\
|
|
'%(candidate)s</i>" wins the election.</p>') % {
|
|
'candidate': X.objectHypertextLabel(self.winnerIds[0]),
|
|
}
|
|
else:
|
|
candidates = '"<i>%s</i>"' % X.objectHypertextLabel(
|
|
self.winnerIds[0])
|
|
for winnerId in self.winnerIds[1:-1]:
|
|
candidates = candidates + ', "<i>%s</i>"' \
|
|
% X.objectHypertextLabel(winnerId)
|
|
candidates = candidates + ' & "<i>%s</i>"' \
|
|
% X.objectHypertextLabel(self.winnerIds[-1])
|
|
analyze = _(
|
|
'<p>The candidates <i>%(candidates)s</i> beat each '\
|
|
'of the other candidates.</p><p>The candidates '\
|
|
'%(candidates)s win the election.</p>') % {
|
|
'candidates': candidates,
|
|
}
|
|
else:
|
|
analyze = _("""\
|
|
<p>No candidate beats every other candidates.</p><p>The election has no winner.</p>\
|
|
""")
|
|
return X.asIs(analyze)
|
|
|
|
def getDetailsLayout(
|
|
self, registeredVotersCount, abstentionsCount, blanksCount):
|
|
candidateIds = self.getSortedCandidateIds()
|
|
layout = X.array()
|
|
|
|
# Write the pairwise matrix.
|
|
if self.pairwiseMatrix is not None and len(self.pairwiseMatrix) > 0:
|
|
layout += X.h3()(
|
|
_('Candidates One-on-One Comparison'))
|
|
table = X.table(_class = 'election-pairwise-matrix')
|
|
table += X.caption(
|
|
_('Table with One-on-One comparisons of the candidates'))
|
|
layout += table
|
|
table += X.colgroup()
|
|
tr = X.tr(X.th())
|
|
thead = X.thead()
|
|
thead += tr
|
|
for candidateId in candidateIds:
|
|
tr += X.th(scope = 'col')(
|
|
X.objectHypertextLabel(candidateId))
|
|
table += X.colgroup(span = len(candidateIds))
|
|
table += thead
|
|
tbody = X.tbody()
|
|
table += tbody
|
|
for candidateId in candidateIds:
|
|
pairwiseRow = self.pairwiseMatrix[candidateId]
|
|
tr = X.tr(X.th(scope = 'row')(
|
|
X.objectHypertextLabel(candidateId)))
|
|
tbody += tr
|
|
for candidateId2 in candidateIds:
|
|
tdAttributes = {}
|
|
if candidateId2 != candidateId:
|
|
value = pairwiseRow[candidateId2]
|
|
peer = self.pairwiseMatrix[candidateId2][candidateId]
|
|
cell = '%.2f' % value
|
|
if value > peer:
|
|
bgcolor = '#00ff00'
|
|
elif value == peer:
|
|
bgcolor = '#ffff00'
|
|
else:
|
|
bgcolor = '#ff0000'
|
|
tdAttributes['style'] = 'background-color: %s' \
|
|
% bgcolor
|
|
else:
|
|
cell = X.nbsp
|
|
tr += X.td(**tdAttributes)(cell)
|
|
|
|
# Write the ratings.
|
|
if self.ratings is not None and len(self.ratings) > 0:
|
|
layout += X.h3()(_('Results'))
|
|
table = X.table(_class = 'election-ratings')
|
|
table += X.caption(_('Ratings for the election'))
|
|
layout += table
|
|
table += X.colgroup()
|
|
table += X.colgroup(span = 3)
|
|
table += X.thead(X.tr(
|
|
X.th()(_('Candidates')),
|
|
X.th(scope = 'col')(_('Wins')),
|
|
X.th(scope = 'col')(_('Losses')),
|
|
X.th(scope = 'col')(_('Ties')),
|
|
))
|
|
tbody = X.tbody()
|
|
table += tbody
|
|
scores = {}
|
|
for candidateId, rating in self.ratings.items():
|
|
scores[candidateId] = len(rating['wins']) \
|
|
+ len(rating['ties']) / 2.0
|
|
minScore = 0
|
|
maxScore = len(candidateIds) - 1
|
|
if minScore < maxScore:
|
|
averageScore = (maxScore + minScore) / 2.0
|
|
coef = 255 / float(averageScore - minScore)
|
|
for candidateId in candidateIds:
|
|
rating = self.ratings[candidateId]
|
|
tdAttributes = {}
|
|
if minScore < maxScore:
|
|
score = scores[candidateId]
|
|
if score <= averageScore:
|
|
red = 255
|
|
green = coef * (score - minScore)
|
|
else:
|
|
red = coef * (maxScore - score)
|
|
green = 255
|
|
tdAttributes['style'] = \
|
|
'background-color: #%(red)02x%(green)02x00' % {
|
|
'green': green,
|
|
'red': red,
|
|
}
|
|
tbody += X.tr(
|
|
X.th(scope = 'row')(
|
|
X.objectHypertextLabel(candidateId)),
|
|
X.td(**tdAttributes)(
|
|
len(rating['wins'])),
|
|
X.td(**tdAttributes)(
|
|
len(rating['losses'])),
|
|
X.td(**tdAttributes)(
|
|
len(rating['ties'])),
|
|
)
|
|
return layout
|
|
|
|
def getSortedCandidateIds(self):
|
|
candidateIds = self.candidateIds[:]
|
|
if self.ratings is not None and len(self.ratings) > 0:
|
|
scores = {}
|
|
for candidateId, rating in self.ratings.items():
|
|
scores[candidateId] = len(rating['wins']) \
|
|
+ len(rating['ties']) / 2.0
|
|
candidateIds.sort(lambda id1, id2, scores = scores:
|
|
cmp(scores[id2], scores[id1]))
|
|
return candidateIds
|
|
register(ElectionCondorcet)
|
|
|
|
|
|
class ElectionsWeb(ObjectsWebMixin, ElectionsProxy):
|
|
def clone(self, id):
|
|
if not id or not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canCloneObject(id):
|
|
return accessForbidden()
|
|
object = self.getObject(id)
|
|
object.id = None
|
|
object.version = 0
|
|
object.state = 'draft' # The difference is here.
|
|
return self.editObject(object)
|
|
clone.isPublicForWeb = 1
|
|
|
|
def confirmPesterAbstentionnists(self, id):
|
|
if not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canGetObject(id):
|
|
return accessForbidden()
|
|
election = self.getObject(id)
|
|
layout = X.array()
|
|
layout += X.div(_class = 'alert')(
|
|
_('Are you sure you want to pester by email '\
|
|
'the abstentionnists of election "%s"?') % \
|
|
election.getLabelTranslated(context.getVar('readLanguages')))
|
|
layout += X.br()
|
|
layout += X.buttonStandalone(
|
|
'pester-abstentionnists', X.idUrl(id, 'pesterAbstentionnists'))
|
|
return writePageLayout(layout,
|
|
_('Confirm Pestering of Abstentionnists by Email'),
|
|
canCache = 0)
|
|
confirmPesterAbstentionnists.isPublicForWeb = 1
|
|
|
|
def getObject_handleResult(self, lazyObject):
|
|
object = ElectionsProxy.getObject_handleResult(self,
|
|
lazyObject)
|
|
if self.__class__ is ElectionAverage:
|
|
object.getSlot('method').setValue('average')
|
|
else:
|
|
object.getSlot('method').setValue('condorcet')
|
|
return object
|
|
|
|
def getViewBelowButtonsBarLayout(self, object, fields):
|
|
layout = X.div(_class = 'election-results')
|
|
layout += ObjectsWebMixin.getViewBelowButtonsBarLayout(
|
|
self, object, fields)
|
|
if object.canCompute():
|
|
layout += X.hr()
|
|
layout += X.h2()(_('Counting of the Votes'))
|
|
|
|
layout += object.getVotesLayout()
|
|
registeredVotersCount, abstentionsCount, blanksCount \
|
|
= object.countVotes()
|
|
layout += object.getDetailsLayout(
|
|
registeredVotersCount, abstentionsCount, blanksCount)
|
|
layout += object.getResultsLayout(
|
|
registeredVotersCount, abstentionsCount, blanksCount)
|
|
if object.winnersCount > 0:
|
|
layout += object.getAnalyzeLayout()
|
|
elif object.state == 'running':
|
|
layout += X.hr()
|
|
layout += X.h2()(_('Election in progress'))
|
|
|
|
registeredVotersCount, abstentionsCount, blanksCount \
|
|
= object.countVotes()
|
|
layout += object.getResultsLayout(
|
|
registeredVotersCount, abstentionsCount, blanksCount)
|
|
return layout
|
|
|
|
def getViewOtherActionButtonsBarLayout(self, object, fields):
|
|
isAdmin = self.isAdmin()
|
|
layout = X.array()
|
|
layout += ObjectsWebMixin.getViewOtherActionButtonsBarLayout(
|
|
self, object, fields)
|
|
if object.state == 'running' \
|
|
and getProxyForServerRole('authentication').setContainsUser(
|
|
object.votersSet):
|
|
layout += X.buttonStandalone(
|
|
'vote',
|
|
X.roleUrl('votes', 'edit').add('electionId', object.id))
|
|
authenticationProxy = getProxyForServerRole('authentication')
|
|
if object.state == 'running' and \
|
|
(isAdmin or \
|
|
authenticationProxy.setContainsUser(object.writersSet)):
|
|
layout += X.buttonStandalone(
|
|
'pester-abstentionnists',
|
|
X.idUrl(object.id, 'confirmPesterAbstentionnists'))
|
|
return layout
|
|
|
|
def getVotesProxy(self):
|
|
return getWebForServerRole('votes')
|
|
|
|
def graphPie(self, id):
|
|
if not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canGetObject(id):
|
|
return accessForbidden()
|
|
election = self.getObject(id)
|
|
try:
|
|
png = election.getGraphPie()
|
|
except: # TODO: tighter check
|
|
if context.getVar('debug'):
|
|
raise
|
|
return pageNotFound()
|
|
req = context.getVar('req')
|
|
req.headers_out['Content-Length'] = str(len(png))
|
|
req.content_type = 'image/png'
|
|
setHttpCookie()
|
|
req.send_http_header()
|
|
if req.method == 'HEAD':
|
|
return OK
|
|
if req.caching:
|
|
req.openCachePage()
|
|
req.write(png)
|
|
if req.caching:
|
|
req.closeCachePage()
|
|
return OK
|
|
graphPie.isPublicForWeb = 1
|
|
|
|
def newObject(self, keywords = None):
|
|
if keywords and keywords.has_key('method') and \
|
|
keywords['method'] == 'average':
|
|
object = ElectionAverage()
|
|
else:
|
|
object = ElectionCondorcet()
|
|
return object
|
|
|
|
def pesterAbstentionnists(self, id):
|
|
if not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canGetObject(id):
|
|
return accessForbidden()
|
|
election = self.getObject(id)
|
|
try:
|
|
ElectionsProxy.pesterAbstentionnists(self, id)
|
|
except faults.Fault:
|
|
return failure(
|
|
_('The pestering of abstentionnists by email has failed!'),
|
|
X.idUrl(id))
|
|
return success(
|
|
_('The pestering of abstentionnists by email has succeeded!'),
|
|
X.idUrl(id))
|
|
pesterAbstentionnists.isPublicForWeb = 1
|
|
|
|
def submitAddObject(self, object):
|
|
try:
|
|
return ObjectsWebMixin.submitAddObject(self, object)
|
|
except faults.WinnersGroupNotEmpty, f:
|
|
context.setVar('error', 1)
|
|
object.setError('self.winnersGroupId', f)
|
|
return self.editObject(object)
|
|
|
|
def view(self, id):
|
|
if not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canGetObject(id):
|
|
return accessForbidden()
|
|
if context.getVar('userToken', default = ''):
|
|
self.computeObject(id)
|
|
return ObjectsWebMixin.view(self, id)
|
|
view.isPublicForWeb = 1
|
|
|
|
def viewAll(self):
|
|
context.push(_level = 'viewAll',
|
|
defaultDispatcherId = context.getVar('dispatcherId'))
|
|
try:
|
|
if not self.canGetObjects():
|
|
return accessForbidden()
|
|
isAdmin = self.isAdmin()
|
|
userId = context.getVar('userId', default = '')
|
|
if userId:
|
|
userSet = [userId]
|
|
else:
|
|
userSet = None
|
|
|
|
layout = X.array()
|
|
requiredSlotNames = [
|
|
'endTime', 'isAlwaysRunning', 'state', 'title']
|
|
displayedSlotNames = ['state', 'isAlwaysRunning', 'endTime']
|
|
|
|
if userSet:
|
|
lastElections = self.getLastObjects(
|
|
10, None, None, userSet, ['draft'],
|
|
requiredSlotNames)
|
|
layout += self.getObjectsSectionLayout(
|
|
lastElections,
|
|
_("""Your elections being written"""),
|
|
displayedSlotNames)
|
|
|
|
if isAdmin:
|
|
lastElections = self.getLastObjects(
|
|
10, None, None, None, ['proposed'],
|
|
requiredSlotNames)
|
|
layout += self.getObjectsSectionLayout(
|
|
lastElections,
|
|
_("""The elections being submitted for evaluation"""),
|
|
displayedSlotNames)
|
|
|
|
if userSet:
|
|
lastElections = self.getLastObjects(
|
|
10, None, None, userSet, ['proposed'],
|
|
requiredSlotNames)
|
|
layout += self.getObjectsSectionLayout(
|
|
lastElections,
|
|
_("""Your elections waiting for validation"""),
|
|
displayedSlotNames)
|
|
|
|
lastElections = self.getLastObjects(
|
|
50, None, None, None, ['running'],
|
|
requiredSlotNames)
|
|
layout += self.getObjectsSectionLayout(
|
|
lastElections,
|
|
_("""The elections in progress"""),
|
|
displayedSlotNames)
|
|
|
|
if userSet:
|
|
lastElections = self.getLastObjects(
|
|
10, None, None, userSet, ['closed'],
|
|
requiredSlotNames)
|
|
layout += self.getObjectsSectionLayout(
|
|
lastElections,
|
|
_("""Your last closed elections"""),
|
|
displayedSlotNames)
|
|
|
|
lastElections = self.getLastObjects(
|
|
10, None, None, None, ['closed'],
|
|
requiredSlotNames)
|
|
layout += self.getObjectsSectionLayout(
|
|
lastElections,
|
|
_("""The last closed elections"""),
|
|
displayedSlotNames)
|
|
|
|
layout += self.getViewAllButtonsBarLayout()
|
|
finally:
|
|
context.pull(_level = 'viewAll')
|
|
return writePageLayout(layout, _('Elections'))
|
|
viewAll.isPublicForWeb = 1
|
|
|