Basic comment system.

This commit is contained in:
Jacob Mason 2010-07-08 21:35:19 -05:00
parent ac262bb012
commit 03056a8a31
5 changed files with 204 additions and 10 deletions

View File

@ -28,7 +28,7 @@ class WebSupportBuilder(StandaloneHTMLBuilder):
def write_doc(self, docname, doctree):
# The translator needs the docname to generate ids.
self.docname = docname
self.cur_docname = docname
StandaloneHTMLBuilder.write_doc(self, docname, doctree)
def get_target_uri(self, docname, typ=None):

View File

@ -11,19 +11,24 @@
import cPickle as pickle
from os import path
from datetime import datetime
from jinja2 import Environment, FileSystemLoader
from sphinx.application import Sphinx
from sphinx.util.osutil import ensuredir
from sphinx.websupport.search import search_adapters
from sphinx.websupport import comments as sphinxcomments
class WebSupportApp(Sphinx):
def __init__(self, *args, **kwargs):
self.search = kwargs.pop('search', None)
self.comments = kwargs.pop('comments', None)
Sphinx.__init__(self, *args, **kwargs)
class WebSupport(object):
def __init__(self, srcdir='', outdir='', search=None):
def __init__(self, srcdir='', outdir='', search=None,
comments=None):
self.srcdir = srcdir
self.outdir = outdir or path.join(self.srcdir, '_build',
'websupport')
@ -31,6 +36,21 @@ class WebSupport(object):
if search is not None:
self.init_search(search)
self.init_comments(comments)
def init_comments(self, comments):
if isinstance(comments, sphinxcomments.CommentBackend):
self.comments = comments
elif comments is not None:
# If a CommentBackend isn't provided, use the default
# SQLAlchemy backend with an SQLite db.
from sphinx.websupport.comments import SQLAlchemyComments
from sqlalchemy import create_engine
db_path = path.join(self.outdir, 'comments', 'comments.db')
ensuredir(path.dirname(db_path))
engine = create_engine('sqlite:///%s' % db_path)
self.comments = SQLAlchemyComments(engine)
def init_templating(self):
import sphinx
template_path = path.join(path.dirname(sphinx.__file__),
@ -52,8 +72,11 @@ class WebSupport(object):
path.join(self.outdir, 'doctrees'))
app = WebSupportApp(self.srcdir, self.srcdir,
self.outdir, doctreedir, 'websupport',
search=self.search)
search=self.search,
comments=self.comments)
self.comments.pre_build()
app.build()
self.comments.post_build()
def get_document(self, docname):
infilename = path.join(self.outdir, docname + '.fpickle')
@ -70,3 +93,11 @@ class WebSupport(object):
document['body'] = self.results_template.render(ctx)
document['title'] = 'Search Results'
return document
def get_comments(self, node_id):
return self.comments.get_comments(node_id)
def add_comment(self, parent_id, text, displayed=True, user_id=None,
rating=0, time=None):
return self.comments.add_comment(parent_id, text, displayed, user_id,
rating, time)

View File

@ -0,0 +1,106 @@
from datetime import datetime
from sqlalchemy.orm import sessionmaker
from sphinx.websupport.comments.db import Base, Node, Comment
Session = sessionmaker()
class CommentBackend(object):
def pre_build(self):
pass
def add_node(self, document, line, source, treeloc):
raise NotImplemented
def post_build(self):
pass
def add_comment(self, parent_id, text, displayed, user_id, rating, time):
raise NotImplemented
def get_comments(self, parent_id):
raise NotImplemented
class SQLAlchemyComments(CommentBackend):
def __init__(self, engine):
self.engine = engine
Base.metadata.bind = engine
Base.metadata.create_all()
Session.configure(bind=engine)
self.session = Session()
def add_node(self, document, line, source, treeloc):
node = Node(document, line, source, treeloc)
self.session.add(node)
return node.id
def post_build(self):
self.session.commit()
def add_comment(self, parent_id, text, displayed, user_id, rating, time):
time = time or datetime.now()
id = parent_id[1:]
if parent_id[0] == 's':
node = self.session.query(Node).filter(Node.id == id).first()
comment = Comment(text, displayed, user_id, rating,
time, node=node)
elif parent_id[0] == 'c':
parent = self.session.query(Comment).filter(Comment.id == id).first()
comment = Comment(text, displayed, user_id, rating,
time, parent=parent)
self.session.add(comment)
self.session.commit()
return self.serializable(comment)
def get_comments(self, parent_id):
parent_id = parent_id[1:]
node = self.session.query(Node).filter(Node.id == parent_id).first()
comments = []
for comment in node.comments:
comments.append(self.serializable(comment))
return comments
def serializable(self, comment):
time = {'year': comment.time.year,
'month': comment.time.month,
'day': comment.time.day,
'hour': comment.time.hour,
'minute': comment.time.minute,
'second': comment.time.second,
'iso': comment.time.isoformat(),
'delta': self.pretty_delta(comment)}
return {'text': comment.text,
'user_id': comment.user_id,
'id': comment.id,
'rating': self.pretty_rating(comment),
'time': time,
'node': comment.node.id if comment.node else None,
'parent': comment.parent.id if comment.parent else None,
'children': [self.serializable(child)
for child in comment.children]}
def pretty_rating(self, comment):
if comment.rating == 1:
return '%s point' % comment.rating
else:
return '%s points' % comment.rating
def pretty_delta(self, comment):
delta = datetime.now() - comment.time
days = delta.days
seconds = delta.seconds
hours = seconds / 3600
minutes = seconds / 60
if days == 0:
dt = (minutes, 'minute') if hours == 0 else (hours, 'hour')
else:
dt = (days, 'day')
return '%s %s ago' % dt if dt[0] == 1 else '%s %ss ago' % dt

View File

@ -0,0 +1,53 @@
from sqlalchemy import Column, Integer, Text, String, Boolean, ForeignKey,\
DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation
Base = declarative_base()
db_prefix = 'sphinx_'
class Node(Base):
"""Data about a Node in a doctree."""
__tablename__ = db_prefix + 'nodes'
id = Column(Integer, primary_key=True)
document = Column(String(256), nullable=False)
line = Column(Integer)
source = Column(Text, nullable=False)
treeloc = Column(String(32), nullable=False)
def __init__(self, document, line, source, treeloc):
self.document = document
self.line = line
self.source = source
self.treeloc = treeloc
def __repr__(self):
return '<Node %s#%s>' % (document, treeloc)
class Comment(Base):
__tablename__ = db_prefix + 'comments'
id = Column(Integer, primary_key=True)
rating = Column(Integer, nullable=False)
time = Column(DateTime, nullable=False)
text = Column(Text, nullable=False)
displayed = Column(Boolean, index=True, default=False)
user_id = Column(String(50), nullable=True)
node_id = Column(Integer, ForeignKey(db_prefix + 'nodes.id'))
node = relation(Node, backref='comments')
parent_id = Column(Integer, ForeignKey(db_prefix + 'comments.id'))
parent = relation('Comment', backref='children', remote_side=[id])
def __init__(self, text, displayed, user_id, rating, time,
node=None, parent=None):
self.text = text
self.displayed = displayed
self.user_id = user_id
self.rating = rating
self.time = time
self.node = node
self.parent = parent

View File

@ -41,14 +41,14 @@ class WebSupportTranslator(HTMLTranslator):
# node will not be commented.
if not self.in_commentable:
self.in_commentable = True
id = self.create_id(node)
node_id = self.add_db_node(node)
# We will place the node in the HTML id attribute. If the node
# already has another id (for indexing purposes) put an empty
# already has an id (for indexing purposes) put an empty
# span with the existing id directly before this node's HTML.
if node.attributes['ids']:
self.body.append('<span id="%s"></span>'
% node.attributes['ids'][0])
node.attributes['ids'] = [id]
node.attributes['ids'] = ['s%s' % node_id]
node.attributes['classes'].append(self.comment_class)
def handle_depart_commentable(self, node):
@ -56,6 +56,10 @@ class WebSupportTranslator(HTMLTranslator):
if self.comment_class in node.attributes['classes']:
self.in_commentable = False
def create_id(self, node):
self.current_id += 1
return '%s_%s' % (node.__class__.__name__, self.current_id)
def add_db_node(self, node):
comments = self.builder.app.comments
db_node_id = comments.add_node(document=self.builder.cur_docname,
line=node.line,
source=node.rawsource,
treeloc='???')
return db_node_id