Update to use wrap and our better db interface.

This commit is contained in:
James Vega 2004-11-26 23:37:34 +00:00
parent 6adc746696
commit e5b2459665

View File

@ -43,246 +43,199 @@ import supybot.plugins as plugins
import os import os
import time import time
import supybot.dbi as dbi
import supybot.conf as conf import supybot.conf as conf
import supybot.utils as utils import supybot.utils as utils
import supybot.ircdb as ircdb import supybot.ircdb as ircdb
from supybot.commands import *
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.privmsgs as privmsgs
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
try: class PollError(Exception):
import sqlite pass
except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at <http://pysqlite.sf.net/>'
class Poll(callbacks.Privmsg, plugins.ChannelDBHandler): class OptionRecord(dbi.Record):
def __init__(self): __fields__ = [
callbacks.Privmsg.__init__(self) 'text',
plugins.ChannelDBHandler.__init__(self) 'votes'
]
def __str__(self):
return '#%s: %s' % (self.id, utils.quoted(self.text))
def makeDb(self, filename): class PollRecord(dbi.Record):
if os.path.exists(filename): __fields__ = [
db = sqlite.connect(filename) 'by',
'question',
'options',
'status'
]
def __str__(self):
format = conf.supybot.humanTimestampFormat()
try:
user = ircdb.users.getUser(int(self.by)).name
except KeyError:
user = 'a user that is no longer registered'
if self.options:
options = 'Options: %s' % '; '.join(map(str, self.options))
else: else:
db = sqlite.connect(filename) options = 'The poll has no options, yet'
cursor = db.cursor() if self.status:
cursor.execute("""CREATE TABLE polls ( status = 'open'
id INTEGER PRIMARY KEY, else:
question TEXT UNIQUE ON CONFLICT IGNORE, status = 'closed'
started_by INTEGER, return 'Poll #%s: %s started by %s. %s. Poll is %s.' % \
open INTEGER)""") (self.id, utils.quoted(self.question), user,
cursor.execute("""CREATE TABLE options ( options, status)
id INTEGER,
poll_id INTEGER, class SqlitePollDB(object):
option TEXT, def __init__(self, filename):
UNIQUE (poll_id, id) ON CONFLICT IGNORE)""") self.dbs = ircutils.IrcDict()
cursor.execute("""CREATE TABLE votes ( self.filename = filename
user_id INTEGER,
poll_id INTEGER, def close(self):
option_id INTEGER, for db in self.dbs.itervalues():
UNIQUE (user_id, poll_id) db.close()
ON CONFLICT IGNORE)""")
db.commit() def _getDb(self, channel):
try:
import sqlite
except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to ' \
'use this plugin. Download it at ' \
'<http://pysqlite.sf.net/>'
filename = plugins.makeChannelFilename(self.filename, channel)
if filename in self.dbs:
return self.dbs[filename]
if os.path.exists(filename):
self.dbs[filename] = sqlite.connect(filename)
return self.dbs[filename]
db = sqlite.connect(filename)
self.dbs[filename] = db
cursor = db.cursor()
cursor.execute("""CREATE TABLE polls (
id INTEGER PRIMARY KEY,
question TEXT UNIQUE ON CONFLICT IGNORE,
started_by INTEGER,
open INTEGER)""")
cursor.execute("""CREATE TABLE options (
id INTEGER,
poll_id INTEGER,
option TEXT,
UNIQUE (poll_id, id) ON CONFLICT IGNORE)""")
cursor.execute("""CREATE TABLE votes (
user_id INTEGER,
poll_id INTEGER,
option_id INTEGER,
UNIQUE (user_id, poll_id)
ON CONFLICT IGNORE)""")
db.commit()
return db return db
def _getUserId(self, irc, prefix): def get(self, channel, poll_id):
try: db = self._getDb(channel)
return ircdb.users.getUserId(prefix)
except KeyError:
irc.errorNotRegistered(Raise=True)
def _getUserName(self, id):
try:
return ircdb.users.getUser(int(id)).name
except KeyError:
return 'an unknown user'
def _getId(self, irc, idStr):
try:
return int(idStr)
except ValueError:
irc.error('The id must be a valid integer.', Raise=True)
def poll(self, irc, msg, args):
"""[<channel>] <id>
Displays the poll question and options for the given poll id.
<channel> is only necessary if the message isn't sent in the channel
itself.
"""
channel = privmsgs.getChannel(msg, args)
poll_id = privmsgs.getArgs(args)
db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""SELECT question, started_by, open cursor.execute("""SELECT question, started_by, open
FROM polls WHERE id=%s""", poll_id) FROM polls WHERE id=%s""", poll_id)
if cursor.rowcount == 0: if cursor.rowcount:
irc.error('There is no poll with id %s.' % poll_id) (question, by, status) = cursor.fetchone()
return
question, started_by, open = cursor.fetchone()
starter = ircdb.users.getUser(started_by).name
if open:
statusstr = 'open'
else: else:
statusstr = 'closed' raise dbi.NoRecordError
cursor.execute("""SELECT id, option FROM options WHERE poll_id=%s""", cursor.execute("""SELECT id, option FROM options WHERE poll_id=%s""",
poll_id) poll_id)
if cursor.rowcount == 0: if cursor.rowcount:
optionstr = 'This poll has no options yet' options = [OptionRecord(i, text=o, votes=0)
for (i, o) in cursor.fetchall()]
else: else:
options = cursor.fetchall() options = []
optionstr = 'Options: %s' % \ return PollRecord(poll_id, question=question, status=status, by=by,
' '.join(['%s: %s' % (s, utils.quoted(t)) \ options=options)
for (s, t) in options])
pollstr = 'Poll #%s: %s started by %s. %s. Poll is %s.' % \
(poll_id, utils.quoted(question), starter, optionstr,
statusstr)
irc.reply(pollstr)
def open(self, irc, msg, args): def open(self, channel, user, question):
"""[<channel>] <question> db = self._getDb(channel)
Creates a new poll with the given question. <channel> is only
necessary if the message isn't sent in the channel itself.
"""
channel = privmsgs.getChannel(msg, args)
question = privmsgs.getArgs(args)
userId = self._getUserId(irc, msg.prefix)
db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""INSERT INTO polls VALUES (NULL, %s, %s, 1)""", cursor.execute("""INSERT INTO polls VALUES (NULL, %s, %s, 1)""",
question, userId) question, user.id)
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] return cursor.fetchone()[0]
irc.replySuccess('(poll #%s added)' % id)
def close(self, irc, msg, args): def closePoll(self, channel, id):
"""[<channel>] <id> db = self._getDb(channel)
Closes the poll with the given <id>; further votes will not be allowed.
<channel> is only necessary if the message isn't sent in the channel
itself.
"""
channel = privmsgs.getChannel(msg, args)
id = privmsgs.getArgs(args)
id = self._getId(irc, id)
db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
# Check to make sure that the poll exists # Check to make sure that the poll exists
cursor.execute("""SELECT id FROM polls WHERE id=%s""", id) cursor.execute("""SELECT id FROM polls WHERE id=%s""", id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error('Id #%s is not an existing poll.') raise dbi.NoRecordError
return
cursor.execute("""UPDATE polls SET open=0 WHERE id=%s""", id) cursor.execute("""UPDATE polls SET open=0 WHERE id=%s""", id)
irc.replySuccess() db.commit()
def add(self, irc, msg, args): def add(self, channel, user, id, option):
"""[<channel>] <id> <option text> db = self._getDb(channel)
Add an option with the given text to the poll with the given id.
<channel> is only necessary if the message isn't sent in the channel
itself.
"""
channel = privmsgs.getChannel(msg, args)
(poll_id, option) = privmsgs.getArgs(args, required=2)
poll_id = self._getId(irc, poll_id)
userId = self._getUserId(irc, msg.prefix)
db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
# Only the poll starter or an admin can add options # Only the poll starter or an admin can add options
cursor.execute("""SELECT started_by FROM polls cursor.execute("""SELECT started_by FROM polls
WHERE id=%s""", WHERE id=%s""",
poll_id) id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error('There is no such poll.') raise dbi.NoRecordError
return if not ((user.id == cursor.fetchone()[0]) or
if not ((userId == cursor.fetchone()[0]) or (ircdb.checkCapability(user.id, 'admin'))):
(ircdb.checkCapability(userId, 'admin'))): raise PollAddError, \
irc.error('That poll isn\'t yours and you aren\'t an admin.') 'That poll isn\'t yours and you aren\'t an admin.'
return
# and NOBODY can add options once a poll has votes # and NOBODY can add options once a poll has votes
cursor.execute("""SELECT COUNT(user_id) FROM votes cursor.execute("""SELECT COUNT(user_id) FROM votes
WHERE poll_id=%s""", WHERE poll_id=%s""",
poll_id) id)
if int(cursor.fetchone()[0]) != 0: if int(cursor.fetchone()[0]) != 0:
irc.error('Cannot add options to a poll with votes.') raise PollAddError, 'Cannot add options to a poll with votes.'
return
# Get the next highest id # Get the next highest id
cursor.execute("""SELECT MAX(id)+1 FROM options cursor.execute("""SELECT MAX(id)+1 FROM options
WHERE poll_id=%s""", WHERE poll_id=%s""",
poll_id) id)
option_id = cursor.fetchone()[0] or 1 option_id = cursor.fetchone()[0] or 1
cursor.execute("""INSERT INTO options VALUES cursor.execute("""INSERT INTO options VALUES
(%s, %s, %s)""", (%s, %s, %s)""",
option_id, poll_id, option) option_id, id, option)
irc.replySuccess() db.commit()
def vote(self, irc, msg, args): def vote(self, channel, user, id, option):
"""[<channel>] <poll id> <option id> db = self._getDb(channel)
Vote for the option with the given id on the poll with the given poll
id. This command can also be used to override any previous vote.
<channel> is only necesssary if the message isn't sent in the channel
itself.
"""
channel = privmsgs.getChannel(msg, args)
(poll_id, option_id) = privmsgs.getArgs(args, required=2)
poll_id = self._getId(irc, poll_id)
option_id = self._getId(irc, option_id)
userId = self._getUserId(irc, msg.prefix)
db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""SELECT open cursor.execute("""SELECT open
FROM polls WHERE id=%s""", FROM polls WHERE id=%s""",
poll_id) id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error('There is no such poll.') raise dbi.NoRecordError
return
elif int(cursor.fetchone()[0]) == 0: elif int(cursor.fetchone()[0]) == 0:
irc.error('That poll is closed.') raise PollError, 'That poll is closed.'
return
cursor.execute("""SELECT id FROM options cursor.execute("""SELECT id FROM options
WHERE poll_id=%s WHERE poll_id=%s
AND id=%s""", AND id=%s""",
poll_id, option_id) id, option)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error('There is no such option.') raise PollError, 'There is no such option.'
return
cursor.execute("""SELECT option_id FROM votes cursor.execute("""SELECT option_id FROM votes
WHERE user_id=%s AND poll_id=%s""", WHERE user_id=%s AND poll_id=%s""",
userId, poll_id) user.id, 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, poll_id, option_id) user.id, id, option)
else: else:
cursor.execute("""UPDATE votes SET option_id=%s cursor.execute("""UPDATE votes SET option_id=%s
WHERE user_id=%s AND poll_id=%s""", WHERE user_id=%s AND poll_id=%s""",
option_id, userId, poll_id) option, user.id, id)
irc.replySuccess() db.commit()
def results(self, irc, msg, args): def results(self, channel, poll_id):
"""[<channel>] <id> db = self._getDb(channel)
Shows the results for the poll with the given id. <channel> is only
necessary if the message is not sent in the channel itself.
"""
channel = privmsgs.getChannel(msg, args)
poll_id = privmsgs.getArgs(args)
poll_id = self._getId(irc, poll_id)
db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""SELECT id, question, started_by, open cursor.execute("""SELECT id, question, started_by, open
FROM polls WHERE id=%s""", FROM polls WHERE id=%s""",
poll_id) poll_id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
irc.error('There is no such poll.') raise dbi.NoRecordError
return (id, question, by, status) = cursor.fetchone()
(id, question, startedBy, open) = cursor.fetchone() by = ircdb.users.getUser(by).name
startedBy = self._getUserName(startedBy)
reply = 'Results for poll #%s: "%s" by %s' % \
(id, question, startedBy)
cursor.execute("""SELECT count(user_id), option_id cursor.execute("""SELECT count(user_id), option_id
FROM votes FROM votes
WHERE poll_id=%s WHERE poll_id=%s
@ -298,35 +251,152 @@ class Poll(callbacks.Privmsg, plugins.ChannelDBHandler):
ORDER BY count(user_id) DESC""", ORDER BY count(user_id) DESC""",
poll_id, poll_id, poll_id) poll_id, poll_id, poll_id)
if cursor.rowcount == 0: if cursor.rowcount == 0:
s = 'This poll has no votes yet.' raise PollError, 'This poll has no votes yet.'
else: else:
results = [] options = []
for count, option_id in cursor.fetchall(): for count, option_id in cursor.fetchall():
cursor.execute("""SELECT option FROM options cursor.execute("""SELECT option FROM options
WHERE id=%s AND poll_id=%s""", WHERE id=%s AND poll_id=%s""",
option_id, poll_id) option_id, poll_id)
option = cursor.fetchone()[0] option = cursor.fetchone()[0]
results.append('%s: %s' % (utils.quoted(option), int(count))) options.append(OptionRecord(option_id, votes=int(count),
s = utils.commaAndify(results) text=option))
reply += ' - %s' % s return PollRecord(poll_id, question=question, status=status, by=by,
irc.reply(reply) options=options)
def list(self, irc, msg, args): def select(self, channel):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT id, started_by, question
FROM polls
WHERE open=1""")
if cursor.rowcount:
return [PollRecord(id, question=q, by=by, status=1)
for (id, by, q) in cursor.fetchall()]
else:
raise dbi.NoRecordError
PollDB = plugins.DB('Poll', {'sqlite': SqlitePollDB})
class Poll(callbacks.Privmsg):
def __init__(self):
self.__parent = super(Poll, self)
self.__parent.__init__()
self.db = PollDB()
def die(self):
self.__parent.die()
self.db.close()
def poll(self, irc, msg, args, channel, id):
"""[<channel>] <id>
Displays the poll question and options for the given poll id.
<channel> is only necessary if the message isn't sent in the channel
itself.
"""
try:
record = self.db.get(channel, id)
except dbi.NoRecordError:
irc.error('There is no poll with id %s.' % id, Raise=True)
irc.reply(record)
poll = wrap(poll, ['channeldb', 'id'])
def open(self, irc, msg, args, channel, user, question):
"""[<channel>] <question>
Creates a new poll with the given question. <channel> is only
necessary if the message isn't sent in the channel itself.
"""
irc.replySuccess('(poll #%s added)' %
self.db.open(channel, user, question))
open = wrap(open, ['channeldb', 'user', 'text'])
def close(self, irc, msg, args, channel, id):
"""[<channel>] <id>
Closes the poll with the given <id>; further votes will not be allowed.
<channel> is only necessary if the message isn't sent in the channel
itself.
"""
try:
self.db.closePoll(channel, id)
irc.replySuccess()
except dbi.NoRecordError:
irc.errorInvalid('poll id')
close = wrap(close, ['channeldb', ('id', 'poll')])
def add(self, irc, msg, args, channel, user, id, option):
"""[<channel>] <id> <option text>
Add an option with the given text to the poll with the given id.
<channel> is only necessary if the message isn't sent in the channel
itself.
"""
try:
self.db.add(channel, user, id, option)
irc.replySuccess()
except dbi.NoRecordError:
irc.errorInvalid('poll id')
except PollError, e:
irc.error(str(e))
irc.replySuccess()
add = wrap(add, ['channeldb', 'user', ('id', 'poll'), 'text'])
def vote(self, irc, msg, args, channel, user, id, option):
"""[<channel>] <poll id> <option id>
Vote for the option with the given id on the poll with the given poll
id. This command can also be used to override any previous vote.
<channel> is only necesssary if the message isn't sent in the channel
itself.
"""
try:
self.db.vote(channel, user, id, option)
irc.replySuccess()
except dbi.NoRecordError:
irc.errorInvalid('poll id')
except PollError, e:
irc.error(str(e))
vote = wrap(vote, ['channeldb', 'user', ('id', 'poll'), ('id', 'option')])
def results(self, irc, msg, args, channel, id):
"""[<channel>] <id>
Shows the results for the poll with the given id. <channel> is only
necessary if the message is not sent in the channel itself.
"""
try:
poll = self.db.results(channel, id)
reply = 'Results for poll #%s: "%s" by %s' % \
(poll.id, poll.question, poll.by)
options = poll.options
L = []
for option in options:
L.append('%s: %s' % (utils.quoted(option.text), option.votes))
s = utils.commaAndify(L)
reply += ' - %s' % s
irc.reply(reply)
except dbi.NoRecordError:
irc.error('There is no such poll.', Raise=True)
except PollError, e:
irc.error(str(e))
results = wrap(results, ['channeldb', ('id', 'poll')])
def list(self, irc, msg, args, channel):
"""[<channel>] """[<channel>]
Lists the currently open polls for <channel>. <channel> is only Lists the currently open polls for <channel>. <channel> is only
necessary if the message isn't sent in the channel itself. necessary if the message isn't sent in the channel itself.
""" """
channel = privmsgs.getChannel(msg, args) try:
db = self.getDb(channel) polls = self.db.select(channel)
cursor = db.cursor() polls = ['#%s: %s' % (p.id, utils.quoted(p.question))
cursor.execute("""SELECT id, question FROM polls WHERE open=1""") for p in polls]
if cursor.rowcount == 0:
irc.reply('This channel currently has no open polls.')
else:
polls = ['#%s: %s' % (s, utils.quoted(t))
for (s, t) in cursor.fetchall()]
irc.reply(utils.commaAndify(polls)) irc.reply(utils.commaAndify(polls))
except dbi.NoRecordError:
irc.reply('This channel currently has no open polls.')
list = wrap(list, ['channeldb'])
Class = Poll Class = Poll