Did a complete rework of the poll plugin.

This commit is contained in:
Daniel DiPaolo 2003-11-26 20:27:42 +00:00
parent d5c9873231
commit ea23b47163
2 changed files with 152 additions and 170 deletions

View File

@ -72,93 +72,46 @@ class Poll(callbacks.Privmsg, plugins.ChannelDBHandler):
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""CREATE TABLE polls ( cursor.execute("""CREATE TABLE polls (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
question TEXT, question TEXT UNIQUE ON CONFLICT IGNORE,
started_by INTEGER, started_by INTEGER,
expires INTEGER)""") open INTEGER)""")
cursor.execute("""CREATE TABLE options ( cursor.execute("""CREATE TABLE options (
id INTEGER PRIMARY KEY,
poll_id INTEGER, poll_id INTEGER,
option_id INTEGER,
option TEXT, option TEXT,
votes INTEGER, UNIQUE (poll_id, id) ON CONFLICT IGNORE)""")
PRIMARY KEY (poll_id, option_id)
ON CONFLICT IGNORE)""")
cursor.execute("""CREATE TABLE votes ( cursor.execute("""CREATE TABLE votes (
user_id INTEGER, user_id INTEGER,
poll_id INTEGER, poll_id INTEGER,
option_id INTERER)""") option_id INTEGER,
UNIQUE (user_id, poll_id)
ON CONFLICT IGNORE)""")
db.commit() db.commit()
return db return db
def new(self, irc, msg, args): def open(self, irc, msg, args):
"""[<channel>] [<lifespan in seconds>] <question> """[<channel>] <question>
Creates a new poll with the given question and optional lifespan. Creates a new poll with the given question.
Without a lifespan the poll will never expire and accept voting
until it is closed.
""" """
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
(lifespan, question) = privmsgs.getArgs(args, optional=1) question = privmsgs.getArgs(args)
try: # Must be registered to create a poll
lifespan = int(lifespan)
except ValueError:
if question:
question = '%s %s' % (lifespan, question)
else:
question = lifespan
lifespan = 0
if lifespan:
lifespan += time.time()
if not question:
raise callbacks.ArgumentError
try: try:
userId = ircdb.users.getUserId(msg.prefix) userId = ircdb.users.getUserId(msg.prefix)
except KeyError: except KeyError:
irc.error(msg, conf.replyNotRegistered) irc.error(msg, conf.replyNotRegistered)
return return
db = self.getDb(channel) db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""INSERT INTO polls VALUES cursor.execute("""INSERT INTO polls
(NULL, %%s, %s, %s)""" % (userId, lifespan), VALUES (NULL, %s, %s, 1)""",
question) question, userId)
db.commit() db.commit()
cursor.execute("""SELECT id FROM polls WHERE question=%s""", question) cursor.execute("""SELECT id FROM polls WHERE question=%s""", question)
id = cursor.fetchone()[0] id = cursor.fetchone()[0]
irc.reply(msg, '%s (poll #%s)' % (conf.replySuccess, id)) irc.reply(msg, '%s (poll #%s)' % (conf.replySuccess, id))
def open(self, irc, msg, args):
"""[<channel>] [<lifespan in seconds>] <id>
Reopens a closed poll with the given <id> and optional lifespan.
Without a lifespan the poll will never expire and accept voting
until it is closed.
"""
channel = privmsgs.getChannel(msg, args)
(lifespan, id) = privmsgs.getArgs(args, optional=1)
if not id:
id = lifespan
lifespan = 0
else:
try:
lifespan = int(lifespan)
except ValueError:
irc.error(msg, 'The <lifespan> argument must be an integer.')
return
try:
id = int(id)
except ValueError:
irc.error(msg, 'The <id> argument must be an integer.')
return
if lifespan:
lifespan += time.time()
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""UPDATE polls SET expires=%s WHERE id=%s""" % \
(lifespan, id))
db.commit()
irc.reply(msg, conf.replySuccess)
def close(self, irc, msg, args): def close(self, irc, msg, args):
"""[<channel>] <id> """[<channel>] <id>
@ -169,67 +122,65 @@ class Poll(callbacks.Privmsg, plugins.ChannelDBHandler):
try: try:
id = int(id) id = int(id)
except ValueError: except ValueError:
irc.error(msg, 'The <id> argument must be an integer.') irc.error(msg, 'The id must be an integer.')
return return
db = self.getDb(channel) db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""UPDATE polls SET expires=%s WHERE id=%s""" % \ # Check to make sure that the poll exists
(int(time.time()), id)) cursor.execute("""SELECT id FROM polls WHERE id=%s""", id)
db.commit() if cursor.rowcount == 0:
irc.error(msg, 'Id #%s is not an existing poll.')
return
cursor.execute("""UPDATE polls SET open=0 WHERE id=%s""", id)
irc.reply(msg, conf.replySuccess) irc.reply(msg, conf.replySuccess)
def add(self, irc, msg, args): def add(self, irc, msg, args):
"""[<channel>] <id> <option> """[<channel>] <id> <option text>
Add an option to poll <id>. Add an option with the given text to the poll with the given id.
""" """
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
(id, option) = privmsgs.getArgs(args, required=2) (poll_id, option) = privmsgs.getArgs(args, required=2)
try: try:
id = int(id) poll_id = int(poll_id)
except ValueError: except ValueError:
irc.error(msg, 'The <id> argument must be an integer.') irc.error(msg, 'The id must be an integer.')
return return
try: try:
userId = ircdb.users.getUserId(msg.prefix) userId = ircdb.users.getUserId(msg.prefix)
except KeyError: except KeyError:
irc.error(msg, conf.replyNotRegistered) irc.error(msg, conf.replyNotRegistered)
return return
db = self.getDb(channel) db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""SELECT started_by FROM polls WHERE id=%s""" % id) cursor.execute("""SELECT started_by FROM polls
WHERE id=%s""",
poll_id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error(msg, 'There is no such poll.') irc.error(msg, 'There is no such poll.')
return return
elif userId != cursor.fetchone()[0]: if not ((userId == cursor.fetchone()[0]) or
irc.error(msg, 'That poll isn\'t yours.') (ircdb.checkCapability(userId, 'admin'))):
irc.error(msg, 'That poll isn\'t yours and you aren\'t an admin.')
return return
cursor.execute("""INSERT INTO options VALUES cursor.execute("""INSERT INTO options VALUES
(%s, NULL, %%s, 0)""" % id, option) (NULL, %s, %s)""",
db.commit() poll_id, option)
cursor.execute("""SELECT option_id FROM options irc.reply(msg, conf.replySuccess)
WHERE poll_id=%s
AND votes=0
AND option=%%s""" % id, option)
irc.reply(msg, '%s (option #%s)' % (conf.replySuccess, cursor.fetchone()[0]))
def vote(self, irc, msg, args): def vote(self, irc, msg, args):
"""[<channel>] <poll id> <option id> """[<channel>] <poll id> <option id>
Vote <option id> on an active poll with the given <poll id>. Vote for the option with the given id on the poll with the given poll
This command can also be used to override the previous vote. id. This command can also be used to override any previous vote.
""" """
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
(id, option) = privmsgs.getArgs(args, required=2) (poll_id, option_id) = privmsgs.getArgs(args, required=2)
try: try:
id = int(id) poll_id = int(poll_id)
option = int(option) option_id = int(option_id)
except ValueError: except ValueError:
irc.error(msg, 'The <poll id> and <option id> ' irc.error(msg, 'The poll id and option id '
'arguments must be an integers.') 'arguments must be an integers.')
return return
try: try:
@ -237,103 +188,115 @@ class Poll(callbacks.Privmsg, plugins.ChannelDBHandler):
except KeyError: except KeyError:
irc.error(msg, conf.replyNotRegistered) irc.error(msg, conf.replyNotRegistered)
return return
db = self.getDb(channel) db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""SELECT expires cursor.execute("""SELECT open
FROM polls WHERE id=%s""" % id) FROM polls WHERE id=%s""",
poll_id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error(msg, 'There is no such poll.') irc.error(msg, 'There is no such poll.')
return return
expires = cursor.fetchone()[0] elif cursor.fetchone()[0] == 0:
if expires and time.time() >= expires:
irc.error(msg, 'That poll is closed.') irc.error(msg, 'That poll is closed.')
return return
cursor.execute("""SELECT id FROM options
cursor.execute("""SELECT option_id FROM options
WHERE poll_id=%s WHERE poll_id=%s
AND option_id=%s""" % (id, option)) AND id=%s""",
poll_id, option_id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error(msg, 'There is no such option.') irc.error(msg, 'There is no such option.')
return return
cursor.execute("""SELECT option_id FROM votes
cursor.execute("""SELECT vote FROM votes WHERE user_id=%s WHERE user_id=%s AND poll_id=%s""",
AND poll_id=%s""" % (userId, id)) userId, poll_id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
cursor.execute("""INSERT INTO votes VALUES (%s, %s, %s)""" % \ cursor.execute("""INSERT INTO votes VALUES (%s, %s, %s)""",
(userId, id, option)) userId, poll_id, option_id)
db.commit()
irc.reply(msg, 'You voted option #%s on poll #%s.' % (option, id))
else: else:
oldVote = int(cursor.fetchone()[0]) cursor.execute("""UPDATE votes SET option_id=%s
if option == oldVote: WHERE user_id=%s AND poll_id=%s""",
irc.error(msg, 'You already voted option #%s ' option_id, userId, poll_id)
'on that poll.' % option) irc.reply(msg, conf.replySuccess)
return
cursor.execute("""UPDATE options SET votes=votes-1
WHERE poll_id=%s AND option_id=%s""" \
% (id, oldVote))
cursor.execute("""UPDATE options SET votes=votes+1
WHERE poll_id=%s AND option_id=%s""" \
% (id, option))
cursor.execute("""UPDATE votes SET option_id=%s WHERE user_id=%s
AND poll_id=%s""" % (option, userId, id))
db.commit()
irc.reply(msg, 'Your vote on poll #%s has been updated to option '
'#%s.' % (id, option))
def results(self, irc, msg, args): def results(self, irc, msg, args):
"""[<channel>] <id> """[<channel>] <id>
Shows the (current) results for the poll with the given id. Shows the results for the poll with the given id.
""" """
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
id = privmsgs.getArgs(args) poll_id = privmsgs.getArgs(args)
try: try:
id = int(id) poll_id = int(poll_id)
except ValueError: except ValueError:
irc.error(msg, 'The <id> argument must be an integer.') irc.error(msg, 'The id argument must be an integer.')
return return
db = self.getDb(channel) db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""SELECT * FROM polls WHERE id=%s""" % id) cursor.execute("""SELECT id, question, started_by, open
FROM polls WHERE id=%s""",
poll_id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error(msg, 'There is no such poll.') irc.error(msg, 'There is no such poll.')
return return
(id, question, startedBy, expires) = cursor.fetchone() (id, question, startedBy, open) = cursor.fetchone()
try: try:
startedBy = ircdb.users.getUser(msg.prefix).name startedBy = ircdb.users.getUser(msg.prefix).name
except KeyError: except KeyError:
startedBy = 'an unknown user' startedBy = 'an unknown user'
return return
reply = 'Results for poll #%s: "%s" by %s' % \ reply = 'Results for poll #%s: "%s" by %s' % \
(ircutils.bold(id), question, ircutils.bold(startedBy)) (id, question, startedBy)
cursor.execute("""SELECT option_id, option, votes FROM options cursor.execute("""SELECT count(user_id), option_id
WHERE poll_id=%s ORDER BY option_id""" % id) FROM votes
totalVotes = 0 WHERE poll_id=%s
results = [] GROUP BY option_id""",
poll_id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
reply = '%s - This poll has no options yet.' % reply s = 'This poll has no votes yet.'
else: else:
for (optionId, option, votes) in cursor.fetchall(): results = []
if votes == 0: for count, option_id in cursor.fetchall():
percent = 0 cursor.execute("""SELECT option FROM options
else: WHERE id=%s""", option_id)
percent = int(float(votes) / float(totalVotes) * 100.0) option = cursor.fetchone()[0]
results.append('%s. %s: %s (%s%%)'\ results.append('%s: %s' % (option, count))
% (ircutils.bold(option_id), option, s = utils.commaAndify(results)
ircutils.bold(votes), percent)) reply += ' - %s' % s
reply = '%s - %s' % (reply, utils.commaAndify(results))
expires = int(expires)
if expires:
if time.time() >= expires:
reply = '%s - Poll is closed.' % reply
else:
expires -= time.time()
reply = '%s - Poll expires in %s' % (reply,
utils.timeElapsed(expires))
irc.reply(msg, reply) irc.reply(msg, reply)
def list(self, irc, msg, args):
"""takes no arguments.
Lists the currently open polls for the channel and their ids.
"""
channel = privmsgs.getChannel(msg, args)
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT id, question FROM polls WHERE open=1""")
if cursor.rowcount == 0:
irc.reply(msg, 'This channel currently has no open polls.')
return
polls = ['#%s: %r' % (id, q) for id, q in cursor.fetchall()]
irc.reply(msg, utils.commaAndify(polls))
def options(self, irc, msg, args):
"""<id>
Lists the options for the poll with the given id.
"""
channel = privmsgs.getChannel(msg, args)
poll_id = privmsgs.getArgs(args)
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT id, option FROM options
WHERE poll_id=%s""",
poll_id)
if cursor.rowcount == 0:
irc.reply(msg, 'This poll has no options yet '
'or no such poll exists.')
return
options = ['%s: %r' % (id, o) for id, o in cursor.fetchall()]
irc.reply(msg, utils.commaAndify(options))
Class = Poll Class = Poll

View File

@ -43,37 +43,56 @@ except ImportError:
if sqlite is not None: if sqlite is not None:
class PollTestCase(ChannelPluginTestCase, PluginDocumentation): class PollTestCase(ChannelPluginTestCase, PluginDocumentation):
plugins = ('Poll', 'User') plugins = ('Poll', 'User')
def testNew(self): def setUp(self):
#self.assertError('poll new Is this a question?') ChannelPluginTestCase.setUp(self)
self.prefix = 'foo!bar@baz' self.prefix = 'foo!bar@baz'
self.assertNotError('register foo bar') self.nick = 'foo'
self.assertRegexp('poll new Is this a question?', '(poll #1)') self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick,
self.assertNotError('poll vote 1 Yes') 'register foo bar',
self.assertError('poll vote 1 Yes') prefix=self.prefix))
self.assertNotError('poll vote 1 No') _ = self.irc.takeMsg()
self.assertError('poll vote 1 No')
self.prefix = 'not!me@anymore'
self.assertError('poll vote 1 Yes')
self.prefix = 'foo!bar@baz'
self.assertNotError('poll close 1')
self.assertError('poll vote 1 Yes')
self.assertNotError('poll open 1')
self.assertNotError('poll vote 1 Yes')
def testOpen(self): def testOpen(self):
self.assertNotError('poll open 1') self.assertRegexp('poll open Foo?', '(poll #1)')
self.assertError('poll open blah')
def testClose(self): def testClose(self):
self.assertRegexp('poll open Foo?', '(poll #1)')
self.assertNotError('poll close 1') self.assertNotError('poll close 1')
self.assertError('poll close blah') self.assertError('poll close blah')
self.assertError('poll close 2')
def testAdd(self):
self.assertNotError('poll open Foo?')
self.assertNotError('poll add 1 moo')
self.assertError('poll add 2 moo')
def testVote(self): def testVote(self):
self.assertHelp('poll vote 1 blah') self.assertNotError('poll open Foo?')
self.assertNotError('poll add 1 moo')
self.assertNotError('poll vote 1 1')
self.assertError('poll vote 1 2')
self.assertError('poll vote blah Yes') self.assertError('poll vote blah Yes')
self.assertError('poll vote 2 blah')
def testResults(self): def testResults(self):
self.assertNotError('poll open Foo?')
self.assertNotError('poll add 1 moo')
self.assertNotError('poll vote 1 1')
self.assertError('poll results blah') self.assertError('poll results blah')
self.assertRegexp('poll results 1', 'moo: 1')
def testList(self):
self.assertNotError('poll open Foo?')
self.assertRegexp('poll list', '#1: \'Foo\?\'')
self.assertNotError('poll open Foo 2?')
self.assertRegexp('poll list', '#1: \'Foo\?\'.*#2: \'Foo 2\?\'')
def testOptions(self):
self.assertNotError('poll open Foo?')
self.assertNotError('poll add 1 moo')
self.assertNotError('poll add 1 bar')
self.assertNotError('poll add 1 baz')
self.assertRegexp('poll options 1', 'moo.*bar.*baz')
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: