This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
glasnost/shared/common/VotesCommon.py

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 = ['people']
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'