~launchpad-pqm/launchpad/devel

12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
4983.1.2 by Curtis Hovey
Added pylint exceptions to database classes.
4
# pylint: disable-msg=E0611,W0212
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
5
6
__metaclass__ = type
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
7
__all__ = [
8
    'Poll',
9
    'PollOption',
10
    'PollOptionSet',
11
    'PollSet',
12
    'VoteCast',
13
    'Vote',
14
    'VoteSet',
15
    'VoteCastSet',
16
    ]
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
17
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
18
from datetime import datetime
19
import random
20
1716.1.90 by Christian Reis
Delintifying some of the database classes
21
import pytz
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
22
from sqlobject import (
23
    AND,
24
    BoolCol,
25
    ForeignKey,
26
    IntCol,
27
    OR,
28
    SQLObjectNotFound,
29
    StringCol,
30
    )
31
from storm.store import Store
32
from zope.component import getUtility
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
33
from zope.interface import implements
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
34
35
from lp.registry.interfaces.person import validate_public_person
7675.110.3 by Curtis Hovey
Ran the migration script to move registry code to lp.registry.
36
from lp.registry.interfaces.poll import (
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
37
    IPoll,
38
    IPollOption,
39
    IPollOptionSet,
40
    IPollSet,
41
    IVote,
42
    IVoteCast,
43
    IVoteCastSet,
44
    IVoteSet,
45
    OptionIsNotFromSimplePoll,
46
    PollAlgorithm,
47
    PollSecrecy,
48
    PollStatus,
49
    )
14606.3.1 by William Grant
Merge canonical.database into lp.services.database.
50
from lp.services.database.datetimecol import UtcDateTimeCol
51
from lp.services.database.enumcol import EnumCol
52
from lp.services.database.sqlbase import (
53
    SQLBase,
54
    sqlvalues,
55
    )
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
56
57
58
class Poll(SQLBase):
59
    """See IPoll."""
60
61
    implements(IPoll)
62
    _table = 'Poll'
2701 by Canonical.com Patch Queue Manager
[trivial] Do not access Foo._defaultOrder from other classes; use a public name instead
63
    sortingColumns = ['title', 'id']
64
    _defaultOrder = sortingColumns
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
65
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
66
    team = ForeignKey(
67
        dbName='team', foreignKey='Person',
68
        storm_validator=validate_public_person, notNull=True)
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
69
70
    name = StringCol(dbName='name', notNull=True)
71
72
    title = StringCol(dbName='title', notNull=True, unique=True)
73
74
    dateopens = UtcDateTimeCol(dbName='dateopens', notNull=True)
75
76
    datecloses = UtcDateTimeCol(dbName='datecloses', notNull=True)
77
78
    proposition = StringCol(dbName='proposition',  notNull=True)
79
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
80
    type = EnumCol(dbName='type', enum=PollAlgorithm,
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
81
                   default=PollAlgorithm.SIMPLE)
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
82
83
    allowspoilt = BoolCol(dbName='allowspoilt', default=True, notNull=True)
84
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
85
    secrecy = EnumCol(dbName='secrecy', enum=PollSecrecy,
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
86
                      default=PollSecrecy.SECRET)
87
2736.1.11 by Mark Shuttleworth
poll system fixes
88
    def newOption(self, name, title, active=True):
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
89
        """See IPoll."""
2736.1.11 by Mark Shuttleworth
poll system fixes
90
        return getUtility(IPollOptionSet).new(self, name, title, active)
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
91
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
92
    def isOpen(self, when=None):
93
        """See IPoll."""
94
        if when is None:
95
            when = datetime.now(pytz.timezone('UTC'))
96
        return (self.datecloses >= when and self.dateopens <= when)
97
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
98
    @property
99
    def closesIn(self):
100
        """See IPoll."""
101
        return self.datecloses - datetime.now(pytz.timezone('UTC'))
102
103
    @property
104
    def opensIn(self):
105
        """See IPoll."""
106
        return self.dateopens - datetime.now(pytz.timezone('UTC'))
107
2219 by Canonical.com Patch Queue Manager
Some fixes as per Steve review. Do not use Subsets to traverse; instead do the traversal by consuming items from the traversal stack. r=SteveA
108
    def isClosed(self, when=None):
109
        """See IPoll."""
110
        if when is None:
111
            when = datetime.now(pytz.timezone('UTC'))
112
        return self.datecloses <= when
113
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
114
    def isNotYetOpened(self, when=None):
115
        """See IPoll."""
116
        if when is None:
117
            when = datetime.now(pytz.timezone('UTC'))
118
        return self.dateopens > when
119
2219 by Canonical.com Patch Queue Manager
Some fixes as per Steve review. Do not use Subsets to traverse; instead do the traversal by consuming items from the traversal stack. r=SteveA
120
    def getAllOptions(self):
121
        """See IPoll."""
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
122
        return getUtility(IPollOptionSet).selectByPoll(self)
123
124
    def getActiveOptions(self):
125
        """See IPoll."""
126
        return getUtility(IPollOptionSet).selectByPoll(self, only_active=True)
127
128
    def getVotesByPerson(self, person):
129
        """See IPoll."""
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
130
        return Vote.selectBy(person=person, poll=self)
2219 by Canonical.com Patch Queue Manager
Some fixes as per Steve review. Do not use Subsets to traverse; instead do the traversal by consuming items from the traversal stack. r=SteveA
131
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
132
    def personVoted(self, person):
133
        """See IPoll."""
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
134
        results = VoteCast.selectBy(person=person, poll=self)
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
135
        return bool(results.count())
136
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
137
    def removeOption(self, option, when=None):
138
        """See IPoll."""
139
        assert self.isNotYetOpened(when=when)
140
        if option.poll != self:
141
            raise ValueError(
142
                "Can't remove an option that doesn't belong to this poll")
143
        option.destroySelf()
144
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
145
    def getOptionByName(self, name):
146
        """See IPoll."""
147
        return PollOption.selectOneBy(poll=self, name=name)
148
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
149
    def _assertEverythingOkAndGetVoter(self, person, when=None):
150
        """Use assertions to Make sure all pre-conditions for a person to vote
151
        are met.
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
152
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
153
        Return the person if this is not a secret poll or None if it's a
154
        secret one.
155
        """
156
        assert self.isOpen(when=when), "This poll is not open"
157
        assert not self.personVoted(person), "Can't vote twice in the same poll"
158
        assert person.inTeam(self.team), (
159
            "Person %r is not a member of this poll's team." % person)
160
161
        # We only associate the option with the person if the poll is not a
162
        # SECRET one.
163
        if self.secrecy == PollSecrecy.SECRET:
164
            voter = None
165
        else:
166
            voter = person
167
        return voter
168
169
    def storeCondorcetVote(self, person, options, when=None):
170
        """See IPoll."""
171
        voter = self._assertEverythingOkAndGetVoter(person, when=when)
172
        assert self.type == PollAlgorithm.CONDORCET
173
        voteset = getUtility(IVoteSet)
174
175
        token = voteset.newToken()
176
        votes = []
177
        activeoptions = self.getActiveOptions()
178
        for option, preference in options.items():
179
            assert option.poll == self, (
180
                "The option %r doesn't belong to this poll" % option)
181
            assert option.active, "Option %r is not active" % option
182
            votes.append(voteset.new(self, option, preference, token, voter))
183
184
        # Store a vote with preference = None for each active option of this
185
        # poll that wasn't in the options argument.
186
        for option in activeoptions:
187
            if option not in options:
188
                votes.append(voteset.new(self, option, None, token, voter))
189
190
        getUtility(IVoteCastSet).new(self, person)
191
        return votes
192
193
    def storeSimpleVote(self, person, option, when=None):
194
        """See IPoll."""
195
        voter = self._assertEverythingOkAndGetVoter(person, when=when)
196
        assert self.type == PollAlgorithm.SIMPLE
197
        voteset = getUtility(IVoteSet)
198
199
        if option is None and not self.allowspoilt:
200
            raise ValueError("This poll doesn't allow spoilt votes.")
201
        elif option is not None:
202
            assert option.poll == self, (
203
                "The option %r doesn't belong to this poll" % option)
204
            assert option.active, "Option %r is not active" % option
205
        token = voteset.newToken()
206
        # This is a simple-style poll, so you can vote only on a single option
207
        # and this option's preference must be 1
208
        preference = 1
209
        vote = voteset.new(self, option, preference, token, voter)
210
        getUtility(IVoteCastSet).new(self, person)
211
        return vote
212
213
    def getTotalVotes(self):
214
        """See IPoll."""
215
        assert self.isClosed()
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
216
        return Vote.selectBy(poll=self).count()
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
217
218
    def getWinners(self):
219
        """See IPoll."""
220
        assert self.isClosed()
4664.1.1 by Curtis Hovey
Normalized comments for bug 3732.
221
        # XXX: GuilhermeSalgado 2005-08-24:
222
        # For now, this method works only for SIMPLE-style polls. This is
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
223
        # not a problem as CONDORCET-style polls are disabled.
224
        assert self.type == PollAlgorithm.SIMPLE
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
225
        query = """
226
            SELECT option
227
            FROM Vote
228
            WHERE poll = %d AND option IS NOT NULL
229
            GROUP BY option
230
            HAVING COUNT(*) = (
231
                SELECT COUNT(*)
232
                FROM Vote
233
                WHERE poll = %d
234
                GROUP BY option
235
                ORDER BY COUNT(*) DESC LIMIT 1
236
                )
237
            """ % (self.id, self.id)
238
        results = Store.of(self).execute(query).get_all()
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
239
        if not results:
240
            return None
241
        return [PollOption.get(id) for (id,) in results]
242
243
    def getPairwiseMatrix(self):
244
        """See IPoll."""
245
        assert self.type == PollAlgorithm.CONDORCET
246
        options = list(self.getAllOptions())
247
        pairwise_matrix = []
248
        for option1 in options:
249
            pairwise_row = []
250
            for option2 in options:
251
                points_query = """
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
252
                    SELECT COUNT(*) FROM Vote as v1, Vote as v2 WHERE
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
253
                        v1.token = v2.token AND
254
                        v1.option = %s AND v2.option = %s AND
255
                        (
256
                         (
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
257
                          v1.preference IS NOT NULL AND
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
258
                          v2.preference IS NOT NULL AND
259
                          v1.preference < v2.preference
260
                         )
261
                          OR
262
                         (
263
                          v1.preference IS NOT NULL AND
264
                          v2.preference IS NULL
265
                         )
266
                        )
267
                    """ % sqlvalues(option1.id, option2.id)
268
                if option1 == option2:
269
                    pairwise_row.append(None)
270
                else:
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
271
                    points = Store.of(self).execute(points_query).get_one()[0]
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
272
                    pairwise_row.append(points)
273
            pairwise_matrix.append(pairwise_row)
274
        return pairwise_matrix
275
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
276
277
class PollSet:
278
    """See IPollSet."""
279
280
    implements(IPollSet)
281
282
    def new(self, team, name, title, proposition, dateopens, datecloses,
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
283
            secrecy, allowspoilt, poll_type=PollAlgorithm.SIMPLE):
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
284
        """See IPollSet."""
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
285
        return Poll(team=team, name=name, title=title,
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
286
                proposition=proposition, dateopens=dateopens,
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
287
                datecloses=datecloses, secrecy=secrecy,
288
                allowspoilt=allowspoilt, type=poll_type)
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
289
2219 by Canonical.com Patch Queue Manager
Some fixes as per Steve review. Do not use Subsets to traverse; instead do the traversal by consuming items from the traversal stack. r=SteveA
290
    def selectByTeam(self, team, status=PollStatus.ALL, orderBy=None, when=None):
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
291
        """See IPollSet."""
292
        if when is None:
293
            when = datetime.now(pytz.timezone('UTC'))
294
295
        if orderBy is None:
2701 by Canonical.com Patch Queue Manager
[trivial] Do not access Foo._defaultOrder from other classes; use a public name instead
296
            orderBy = Poll.sortingColumns
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
297
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
298
2219 by Canonical.com Patch Queue Manager
Some fixes as per Steve review. Do not use Subsets to traverse; instead do the traversal by consuming items from the traversal stack. r=SteveA
299
        status = set(status)
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
300
        status_clauses = []
301
        if PollStatus.OPEN in status:
302
            status_clauses.append(AND(Poll.q.dateopens <= when,
303
                                    Poll.q.datecloses > when))
304
        if PollStatus.CLOSED in status:
305
            status_clauses.append(Poll.q.datecloses <= when)
306
        if PollStatus.NOT_YET_OPENED in status:
307
            status_clauses.append(Poll.q.dateopens > when)
308
309
        assert len(status_clauses) > 0, "No poll statuses were selected"
310
311
        results = Poll.select(AND(Poll.q.teamID == team.id,
312
                                  OR(*status_clauses)))
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
313
314
        return results.orderBy(orderBy)
315
316
    def getByTeamAndName(self, team, name, default=None):
317
        """See IPollSet."""
1716.1.90 by Christian Reis
Delintifying some of the database classes
318
        query = AND(Poll.q.teamID == team.id, Poll.q.name == name)
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
319
        try:
320
            return Poll.selectOne(query)
321
        except SQLObjectNotFound:
322
            return default
323
324
325
class PollOption(SQLBase):
326
    """See IPollOption."""
327
328
    implements(IPollOption)
329
    _table = 'PollOption'
2736.1.11 by Mark Shuttleworth
poll system fixes
330
    _defaultOrder = ['title', 'id']
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
331
332
    poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
333
2736.1.11 by Mark Shuttleworth
poll system fixes
334
    name = StringCol(notNull=True)
335
336
    title = StringCol(notNull=True)
337
338
    active = BoolCol(notNull=True, default=False)
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
339
340
341
class PollOptionSet:
342
    """See IPollOptionSet."""
343
344
    implements(IPollOptionSet)
345
2736.1.11 by Mark Shuttleworth
poll system fixes
346
    def new(self, poll, name, title, active=True):
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
347
        """See IPollOptionSet."""
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
348
        return PollOption(poll=poll, name=name, title=title, active=active)
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
349
350
    def selectByPoll(self, poll, only_active=False):
351
        """See IPollOptionSet."""
1716.1.90 by Christian Reis
Delintifying some of the database classes
352
        query = PollOption.q.pollID == poll.id
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
353
        if only_active:
1716.1.90 by Christian Reis
Delintifying some of the database classes
354
            query = AND(query, PollOption.q.active == True)
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
355
        return PollOption.select(query)
356
1716.1.90 by Christian Reis
Delintifying some of the database classes
357
    def getByPollAndId(self, poll, option_id, default=None):
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
358
        """See IPollOptionSet."""
1716.1.90 by Christian Reis
Delintifying some of the database classes
359
        query = AND(PollOption.q.pollID == poll.id,
360
                    PollOption.q.id == option_id)
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
361
        try:
362
            return PollOption.selectOne(query)
363
        except SQLObjectNotFound:
364
            return default
365
366
367
class VoteCast(SQLBase):
368
    """See IVoteCast."""
369
370
    implements(IVoteCast)
371
    _table = 'VoteCast'
2643 by Canonical.com Patch Queue Manager
[trivial] Fix https://launchpad.net/products/launchpad/+bug/2717 (The Vote class misses a default ordering column)
372
    _defaultOrder = 'id'
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
373
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
374
    person = ForeignKey(
375
        dbName='person', foreignKey='Person',
376
        storm_validator=validate_public_person, notNull=True)
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
377
378
    poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
379
380
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
381
class VoteCastSet:
382
    """See IVoteCastSet."""
383
384
    implements(IVoteCastSet)
385
386
    def new(self, poll, person):
387
        """See IVoteCastSet."""
388
        return VoteCast(poll=poll, person=person)
389
390
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
391
class Vote(SQLBase):
392
    """See IVote."""
393
394
    implements(IVote)
395
    _table = 'Vote'
2643 by Canonical.com Patch Queue Manager
[trivial] Fix https://launchpad.net/products/launchpad/+bug/2717 (The Vote class misses a default ordering column)
396
    _defaultOrder = ['preference', 'id']
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
397
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
398
    person = ForeignKey(
399
        dbName='person', foreignKey='Person',
400
        storm_validator=validate_public_person)
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
401
402
    poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
403
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
404
    option = ForeignKey(dbName='option', foreignKey='PollOption')
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
405
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
406
    preference = IntCol(dbName='preference')
2162 by Canonical.com Patch Queue Manager
cleanup and portlet love [r=stevea]
407
408
    token = StringCol(dbName='token', notNull=True, unique=True)
409
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
410
411
class VoteSet:
412
    """See IVoteSet."""
413
414
    implements(IVoteSet)
415
416
    def newToken(self):
417
        """See IVoteSet."""
418
        chars = '23456789bcdfghjkmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ'
419
        length = 10
420
        token = ''.join([random.choice(chars) for c in range(length)])
421
        while self.getByToken(token):
422
            token = ''.join([random.choice(chars) for c in range(length)])
423
        return token
424
425
    def new(self, poll, option, preference, token, person):
426
        """See IVoteSet."""
427
        return Vote(poll=poll, option=option, preference=preference,
428
                    token=token, person=person)
429
430
    def getByToken(self, token):
431
        """See IVoteSet."""
432
        return Vote.selectBy(token=token)
433
434
    def getVotesByOption(self, option):
435
        """See IVoteSet."""
436
        if option.poll.type != PollAlgorithm.SIMPLE:
437
            raise OptionIsNotFromSimplePoll(
438
                '%r is not an option of a simple-style poll.' % option)
12156.3.1 by Curtis Hovey
Restored polls so that they can be used by a handful of important teams once a year.
439
        return Vote.selectBy(option=option).count()
2491 by Canonical.com Patch Queue Manager
Simple and Condorcet polls (although the latter is disabled for now) for teams. r=BjornT
440