2012-10-18 13:05:54 +00:00
|
|
|
# -*- encoding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# Copyright 2012, Martin Zimmermann <info@posativ.org>. All rights reserved.
|
|
|
|
# License: BSD Style, 2 clauses. see isso/__init__.py
|
2012-10-16 17:11:59 +00:00
|
|
|
|
|
|
|
import abc
|
2012-10-16 19:04:20 +00:00
|
|
|
import time
|
2012-10-16 17:11:59 +00:00
|
|
|
import sqlite3
|
|
|
|
|
2012-10-16 19:07:29 +00:00
|
|
|
from isso.models import Comment
|
2012-10-16 17:11:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Abstract:
|
|
|
|
|
|
|
|
__metaclass__ = abc.ABCMeta
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
2012-10-17 16:32:53 +00:00
|
|
|
def __init__(self, app):
|
2012-10-16 17:11:59 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
2012-10-18 13:39:04 +00:00
|
|
|
def add(self, path, comment):
|
2012-10-17 09:42:21 +00:00
|
|
|
"""Add a new comment to the database. Returns a Comment object."""
|
2012-10-16 17:11:59 +00:00
|
|
|
return
|
|
|
|
|
2012-10-18 13:39:04 +00:00
|
|
|
@abc.abstractmethod
|
|
|
|
def activate(self, path, id):
|
|
|
|
"""Activate comment id if pending and return comment for (path, id)."""
|
|
|
|
return
|
|
|
|
|
2012-10-16 17:11:59 +00:00
|
|
|
@abc.abstractmethod
|
2012-10-16 18:20:29 +00:00
|
|
|
def update(self, path, id, comment):
|
2012-10-17 09:42:21 +00:00
|
|
|
"""
|
|
|
|
Update an existing comment, but only writeable fields such as text,
|
|
|
|
author, email, website and parent. This method should set the modified
|
|
|
|
field to the current time.
|
|
|
|
"""
|
2012-10-16 17:11:59 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
2012-10-17 09:42:21 +00:00
|
|
|
def delete(self, path, id):
|
|
|
|
"""
|
|
|
|
Delete a comment. There are two distinctions: a comment is referenced
|
|
|
|
by another valid comment's parent attribute or stand-a-lone. In this
|
|
|
|
case the comment can't be removed without losing depending comments.
|
|
|
|
Hence, delete removes all visible data such as text, author, email,
|
|
|
|
website sets the mode field to 2.
|
|
|
|
|
|
|
|
In the second case this comment can be safely removed without any side
|
|
|
|
effects."""
|
2012-10-16 17:11:59 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
2012-10-17 09:42:21 +00:00
|
|
|
def retrieve(self, path, limit=20):
|
2012-10-16 17:11:59 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
class SQLite(Abstract):
|
2012-10-16 17:32:55 +00:00
|
|
|
"""A basic :class:`Abstract` implementation using SQLite3. All comments
|
|
|
|
share a single database. The tuple (id, path) acts as unique identifier
|
|
|
|
for a comment. Multiple comments per path (= that is the URI to your blog
|
|
|
|
post) are ordered by that id."""
|
2012-10-16 17:11:59 +00:00
|
|
|
|
|
|
|
fields = [
|
2012-10-16 18:20:29 +00:00
|
|
|
'id', 'path', 'created', 'modified',
|
2012-10-16 17:42:51 +00:00
|
|
|
'text', 'author', 'email', 'website', 'parent', 'mode'
|
2012-10-16 17:11:59 +00:00
|
|
|
]
|
|
|
|
|
2012-10-17 16:32:53 +00:00
|
|
|
def __init__(self, app):
|
2012-10-16 17:42:51 +00:00
|
|
|
|
2012-10-17 16:32:53 +00:00
|
|
|
self.dbpath = app.SQLITE
|
2012-10-18 13:39:04 +00:00
|
|
|
self.mode = 2 if app.MODERATION else 1
|
2012-10-16 17:11:59 +00:00
|
|
|
|
|
|
|
with sqlite3.connect(self.dbpath) as con:
|
|
|
|
sql = ('main.comments (id INTEGER NOT NULL, path VARCHAR(255) NOT NULL,'
|
2012-10-16 18:20:29 +00:00
|
|
|
'created FLOAT NOT NULL, modified FLOAT, text VARCHAR,'
|
|
|
|
'author VARCHAR(64), email VARCHAR(64), website VARCHAR(64),'
|
|
|
|
'parent INTEGER, mode INTEGER, PRIMARY KEY (id, path))')
|
2012-10-16 17:11:59 +00:00
|
|
|
con.execute("CREATE TABLE IF NOT EXISTS %s;" % sql)
|
|
|
|
|
|
|
|
# increment id if (id, path) is no longer unique
|
|
|
|
con.execute("""\
|
|
|
|
CREATE TRIGGER IF NOT EXISTS increment AFTER INSERT ON comments
|
|
|
|
BEGIN
|
|
|
|
UPDATE comments SET
|
|
|
|
id=(SELECT MAX(id)+1 FROM comments WHERE path=NEW.path)
|
|
|
|
WHERE rowid=NEW.rowid;
|
|
|
|
END;""")
|
|
|
|
|
|
|
|
def query2comment(self, query):
|
2012-10-16 20:56:21 +00:00
|
|
|
if query is None:
|
|
|
|
return None
|
|
|
|
|
2012-10-16 17:11:59 +00:00
|
|
|
return Comment(
|
2012-10-16 18:20:29 +00:00
|
|
|
text=query[4], author=query[5], email=query[6], website=query[7],
|
|
|
|
parent=query[8], mode=query[9], id=query[0], created=query[2], modified=query[3]
|
2012-10-16 17:11:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
def add(self, path, c):
|
|
|
|
with sqlite3.connect(self.dbpath) as con:
|
|
|
|
keys = ','.join(self.fields)
|
2012-10-19 13:51:51 +00:00
|
|
|
values = ','.join('?' * len(self.fields))
|
2012-10-16 19:30:30 +00:00
|
|
|
con.execute('INSERT INTO comments (%s) VALUES (%s);' % (keys, values), (
|
2012-10-16 18:20:29 +00:00
|
|
|
0, path, c.created, c.modified, c.text, c.author, c.email, c.website,
|
2012-10-16 17:42:51 +00:00
|
|
|
c.parent, self.mode)
|
|
|
|
)
|
2012-10-16 17:11:59 +00:00
|
|
|
|
2012-10-16 19:00:10 +00:00
|
|
|
with sqlite3.connect(self.dbpath) as con:
|
2012-10-17 10:00:11 +00:00
|
|
|
return self.query2comment(
|
|
|
|
con.execute('SELECT *, MAX(id) FROM comments;').fetchone())
|
2012-10-16 19:00:10 +00:00
|
|
|
|
2012-10-18 13:39:04 +00:00
|
|
|
def activate(self, path, id):
|
|
|
|
with sqlite3.connect(self.dbpath) as con:
|
|
|
|
con.execute("UPDATE comments SET mode=1 WHERE path=? AND id=? AND mode=2", (path, id))
|
|
|
|
return self.get(path, id)
|
|
|
|
|
2012-10-16 18:20:29 +00:00
|
|
|
def update(self, path, id, comment):
|
|
|
|
with sqlite3.connect(self.dbpath) as con:
|
2012-10-16 20:49:43 +00:00
|
|
|
for field, value in comment.iteritems(False):
|
2012-10-16 19:00:10 +00:00
|
|
|
con.execute('UPDATE comments SET %s=? WHERE path=? AND id=?;' % field,
|
2012-10-17 08:56:43 +00:00
|
|
|
(value, path, id))
|
2012-10-16 19:04:20 +00:00
|
|
|
|
|
|
|
with sqlite3.connect(self.dbpath) as con:
|
|
|
|
con.execute('UPDATE comments SET modified=? WHERE path=? AND id=?',
|
|
|
|
(time.time(), path, id))
|
2012-10-17 10:00:11 +00:00
|
|
|
return self.get(path, id)
|
2012-10-16 19:00:10 +00:00
|
|
|
|
|
|
|
def get(self, path, id):
|
|
|
|
with sqlite3.connect(self.dbpath) as con:
|
|
|
|
return self.query2comment(con.execute(
|
|
|
|
'SELECT * FROM comments WHERE path=? AND id=?;', (path, id)).fetchone())
|
2012-10-16 17:11:59 +00:00
|
|
|
|
2012-10-16 18:20:29 +00:00
|
|
|
def delete(self, path, id):
|
2012-10-16 19:00:10 +00:00
|
|
|
with sqlite3.connect(self.dbpath) as con:
|
2012-10-17 10:22:52 +00:00
|
|
|
refs = con.execute('SELECT * FROM comments WHERE parent=?', (id, )).fetchone()
|
2012-10-17 10:00:11 +00:00
|
|
|
|
2012-10-17 10:22:52 +00:00
|
|
|
if refs is None:
|
2012-10-17 10:00:11 +00:00
|
|
|
con.execute('DELETE FROM comments WHERE path=? AND id=?', (path, id))
|
|
|
|
return None
|
|
|
|
|
2012-10-16 19:00:10 +00:00
|
|
|
con.execute('UPDATE comments SET text=? WHERE path=? AND id=?', ('', path, id))
|
2012-10-18 13:39:04 +00:00
|
|
|
con.execute('UPDATE comments SET mode=? WHERE path=? AND id=?', (4, path, id))
|
2012-10-17 09:42:21 +00:00
|
|
|
for field in set(Comment.fields) - set(['text', 'parent']):
|
2012-10-16 19:00:10 +00:00
|
|
|
con.execute('UPDATE comments SET %s=? WHERE path=? AND id=?' % field,
|
|
|
|
(None, path, id))
|
2012-10-17 10:00:11 +00:00
|
|
|
return self.get(path, id)
|
2012-10-16 17:11:59 +00:00
|
|
|
|
2012-10-18 13:39:04 +00:00
|
|
|
def retrieve(self, path, limit=20, mode=1):
|
2012-10-16 17:11:59 +00:00
|
|
|
with sqlite3.connect(self.dbpath) as con:
|
2012-10-18 13:39:04 +00:00
|
|
|
rv = con.execute("SELECT * FROM comments WHERE path=? AND (? | mode) = ?" \
|
2012-10-19 19:12:56 +00:00
|
|
|
+ " ORDER BY id ASC LIMIT ?;", (path, mode, mode, limit)).fetchall()
|
2012-10-16 17:11:59 +00:00
|
|
|
|
|
|
|
for item in rv:
|
|
|
|
yield self.query2comment(item)
|