374 lines
12 KiB
Python
374 lines
12 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 Votes Common Models"""
|
|
|
|
__version__ = '$Revision$'[11:-2]
|
|
|
|
|
|
from ObjectsCommon import AdminWithoutWritersCommon, ObjectCommon, ObjectsCommonMixin
|
|
|
|
|
|
class AdminVotesCommon(AdminWithoutWritersCommon):
|
|
serverRole = 'votes'
|
|
|
|
|
|
class AbstractVoteCommon(ObjectCommon):
|
|
ballotKind = None
|
|
ballotKind_kind_isRequired = 1
|
|
ballotKind_kind_values = [
|
|
'public',
|
|
'secret',
|
|
]
|
|
ballotKind_kindName = 'Choice'
|
|
|
|
comment = None
|
|
comment_kindName = 'String'
|
|
|
|
electionToken = None
|
|
electionToken_kindName = 'Token'
|
|
|
|
marks = None
|
|
marks_kind_isRequired = 1
|
|
marks_kindName = 'Marks'
|
|
|
|
serverRole = 'votes'
|
|
|
|
token = None
|
|
token_kindName = 'Token'
|
|
|
|
voterId_kind_importExport = 'private'
|
|
voterId_kind_serverRoles = ['identities']
|
|
voterId_kindName = 'Id'
|
|
|
|
voterToken = None
|
|
voterToken_kindName = 'Token'
|
|
|
|
def canCache(self):
|
|
return 0
|
|
|
|
def getDistribution(self, candidateIds):
|
|
rating = self.getRating(candidateIds)
|
|
if len(rating) == 0:
|
|
return {}
|
|
distribution = {}
|
|
ratesSum = 0
|
|
for rate in rating.values():
|
|
ratesSum += rate
|
|
if ratesSum == 0:
|
|
score = 1 / float(len(rating))
|
|
for candidateId in rating.keys():
|
|
distribution[candidateId] = score
|
|
else:
|
|
coef = 1 / float(ratesSum)
|
|
for candidateId, rate in rating.items():
|
|
distribution[candidateId] = rate * coef
|
|
return distribution
|
|
|
|
def getLabel(self):
|
|
label = self.getName()
|
|
if label is None:
|
|
return ''
|
|
return label
|
|
|
|
def getLabelLanguage(self):
|
|
return ''
|
|
|
|
def getMarks(self, candidateIds):
|
|
return self.repairMarks(self.marks, candidateIds)
|
|
|
|
def getOrderedLayoutSlotNames(self, parentSlot = None):
|
|
slotNames = ObjectCommon.getOrderedLayoutSlotNames(
|
|
self, parentSlot = parentSlot)
|
|
slotNames += [
|
|
'voterId', 'voterToken', 'electionId', 'subject', 'token',
|
|
'ballotKind', 'marks', 'comment']
|
|
return slotNames
|
|
|
|
def getRanking(self, candidateIds):
|
|
rating = self.getRating(candidateIds)
|
|
if len(rating) == 0:
|
|
return {}
|
|
candidateIds = rating.keys()[:]
|
|
def candidatesSorter(id1, id2, rating = rating):
|
|
return cmp(rating[id2], rating[id1])
|
|
candidateIds.sort(candidatesSorter)
|
|
ranking = {}
|
|
if len(candidateIds) > 0:
|
|
rank = 1
|
|
previousRate = rating[candidateIds[0]]
|
|
for candidateId in candidateIds:
|
|
rate = rating[candidateId]
|
|
if previousRate < rate:
|
|
rank += 1
|
|
previousRate = rate
|
|
ranking[candidateId] = rank
|
|
return ranking
|
|
|
|
def getRating(self, candidateIds):
|
|
distribution = self.getDistribution(candidateIds)
|
|
if len(distribution) == 0:
|
|
return {}
|
|
rating = {}
|
|
minValue = min(distribution.values())
|
|
maxValue = max(distribution.values())
|
|
if minValue == maxValue:
|
|
for candidateId in distribution.keys():
|
|
rating[candidateId] = 0
|
|
else:
|
|
coef = 1 / float(maxValue - minValue)
|
|
for candidateId, value in distribution.items():
|
|
rating[candidateId] = (value - minValue) * coef
|
|
return rating
|
|
|
|
def getName(self):
|
|
if self.token:
|
|
return 'Vote %s' % self.token
|
|
return 'Vote'
|
|
|
|
def isAbstention(self):
|
|
return self.marks is None
|
|
|
|
|
|
class VoteDistributionCommon(AbstractVoteCommon):
|
|
def getDistribution(self, candidateIds):
|
|
return self.repairMarks(self.marks, candidateIds)
|
|
|
|
def isBlank(self, candidateIds):
|
|
if self.isAbstention():
|
|
return 0
|
|
marks = self.repairMarks(self.marks, candidateIds)
|
|
minMark = min(marks.values())
|
|
maxMark = max(marks.values())
|
|
return minMark == maxMark == 0
|
|
|
|
def repairMarks(self, marks, candidateIds):
|
|
if marks is None:
|
|
return {}
|
|
distribution = {}
|
|
marksSum = 0
|
|
for candidateId, mark in marks.items():
|
|
if not candidateId in candidateIds:
|
|
continue
|
|
marksSum += mark
|
|
if marksSum == 0:
|
|
# Blank vote.
|
|
for candidateId in candidateIds:
|
|
distribution[candidateId] = 0
|
|
else:
|
|
coef = 1 / float(marksSum)
|
|
for candidateId in candidateIds:
|
|
if marks.has_key(candidateId):
|
|
value = marks[candidateId] * coef
|
|
else:
|
|
value = 0
|
|
distribution[candidateId] = value
|
|
return distribution
|
|
|
|
|
|
class VoteRankingCommon(AbstractVoteCommon):
|
|
def getRanking(self, candidateIds):
|
|
return self.repairMarks(self.marks, candidateIds)
|
|
|
|
def getRating(self, candidateIds):
|
|
ranking = self.getRanking(candidateIds)
|
|
if len(ranking) == 0:
|
|
return {}
|
|
rating = {}
|
|
lastRank = max(ranking.values())
|
|
newRank = 1
|
|
for rank in range(1, lastRank + 1):
|
|
tieIds = filter(lambda id, ranking = ranking, rank = rank:
|
|
ranking[id] == rank,
|
|
ranking.keys())
|
|
rate = - (2 * newRank + len(tieIds) - 1) / 2.0
|
|
for candidateId in tieIds:
|
|
rating[candidateId] = rate
|
|
newRank = newRank + len(tieIds)
|
|
minRate = min(rating.values())
|
|
maxRate = max(rating.values())
|
|
if minRate == maxRate:
|
|
for candidateId in rating.keys():
|
|
rating[candidateId] = 0
|
|
else:
|
|
coef = 1 / float(maxRate - minRate)
|
|
for candidateId, rate in rating.items():
|
|
rating[candidateId] = (rate - minRate) * coef
|
|
return rating
|
|
|
|
def getScores(self, candidateIds):
|
|
ranking = self.getRanking(candidateIds)
|
|
if len(ranking) == 0:
|
|
return {}
|
|
scores = {}
|
|
lastRank = max(ranking.values())
|
|
newRank = 1
|
|
for rank in range(1, lastRank + 1):
|
|
tieIds = filter(lambda id, ranking = ranking, rank = rank:
|
|
ranking[id] == rank,
|
|
ranking.keys())
|
|
score = - (2 * newRank + len(tieIds) - 1) / 2.0
|
|
for candidateId in tieIds:
|
|
scores[candidateId] = score
|
|
newRank = newRank + len(tieIds)
|
|
minScore = - len(candidateIds)
|
|
maxScore = -1
|
|
if minScore == maxScore:
|
|
score = 1 / float(len(candidateIds))
|
|
for candidateId in scores.keys():
|
|
scores[candidateId] = score
|
|
else:
|
|
coef = 1 / float(maxScore - minScore)
|
|
for candidateId, score in scores.items():
|
|
scores[candidateId] = (score - minScore) * coef
|
|
return scores
|
|
|
|
def isBlank(self, candidateIds):
|
|
if self.isAbstention():
|
|
return 0
|
|
marks = self.repairMarks(self.marks, candidateIds)
|
|
foundMarks = []
|
|
for mark in marks.values():
|
|
if not mark in foundMarks:
|
|
foundMarks.append(mark)
|
|
return len(foundMarks) == 1 and foundMarks[0] is None
|
|
|
|
def repairMarks(self, marks, candidateIds):
|
|
if marks is None:
|
|
return {}
|
|
marks = marks.copy()
|
|
for candidateId in marks.keys():
|
|
if not candidateId in candidateIds:
|
|
del marks[candidateId]
|
|
for candidateId in candidateIds:
|
|
if not marks.has_key(candidateId):
|
|
marks[candidateId] = None
|
|
def candidatesSorter(id1, id2, marks = marks):
|
|
mark1 = marks[id1]
|
|
mark2 = marks[id2]
|
|
if mark1 == mark2:
|
|
return 0
|
|
elif mark1 is None:
|
|
return 1
|
|
elif mark2 is None:
|
|
return -1
|
|
else:
|
|
return cmp(mark1, mark2)
|
|
candidateIds = candidateIds[:]
|
|
candidateIds.sort(candidatesSorter)
|
|
ranking = {}
|
|
if len(candidateIds) > 0:
|
|
previousMark = marks[candidateIds[0]]
|
|
if previousMark is None:
|
|
for candidateId in candidateIds:
|
|
ranking[candidateId] = None
|
|
else:
|
|
rank = 1
|
|
for candidateId in candidateIds:
|
|
mark = marks[candidateId]
|
|
if previousMark is not None \
|
|
and (mark is None or previousMark < mark):
|
|
rank += 1
|
|
previousMark = mark
|
|
ranking[candidateId] = rank
|
|
return ranking
|
|
|
|
|
|
class VoteRatingCommon(AbstractVoteCommon):
|
|
def getRating(self, candidateIds):
|
|
return self.repairMarks(self.marks, candidateIds)
|
|
|
|
def isBlank(self, candidateIds):
|
|
if self.isAbstention():
|
|
return 0
|
|
marks = self.repairMarks(self.marks, candidateIds)
|
|
minMark = min(marks.values())
|
|
maxMark = max(marks.values())
|
|
return minMark == maxMark == 0
|
|
|
|
def repairMarks(self, marks, candidateIds):
|
|
if marks is None:
|
|
return {}
|
|
rating = {}
|
|
minMark = min(marks.values())
|
|
maxMark = max(marks.values())
|
|
if minMark == maxMark == 0:
|
|
for candidateId in candidateIds:
|
|
rating[candidateId] = 0
|
|
elif minMark == maxMark:
|
|
for candidateId in candidateIds:
|
|
rating[candidateId] = 1
|
|
else:
|
|
coef = 1 / float(maxMark - minMark)
|
|
for candidateId in candidateIds:
|
|
if marks.has_key(candidateId):
|
|
rate = (marks[candidateId] - minMark) * coef
|
|
else:
|
|
rate = 0
|
|
rating[candidateId] = rate
|
|
return rating
|
|
|
|
|
|
class VoteApprovalCommon(VoteRatingCommon):
|
|
def repairMarks(self, marks, candidateIds):
|
|
if marks is None:
|
|
return {}
|
|
rating = {}
|
|
for candidateId in candidateIds:
|
|
if marks.has_key(candidateId):
|
|
rate = marks[candidateId]
|
|
else:
|
|
rate = 0
|
|
rating[candidateId] = rate
|
|
return rating
|
|
|
|
|
|
class VotesCommonMixin(ObjectsCommonMixin):
|
|
adminClassName = 'AdminVotes'
|
|
newObjectNameCapitalized = N_('New Vote')
|
|
objectClassName = 'AbstractVote'
|
|
objectName = N_('vote')
|
|
objectNameCapitalized = N_('Vote')
|
|
objectsName = N_('votes')
|
|
objectsNameCapitalized = N_('Votes')
|
|
serverRole = 'votes'
|