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): def write_doc(self, docname, doctree):
# The translator needs the docname to generate ids. # The translator needs the docname to generate ids.
self.docname = docname self.cur_docname = docname
StandaloneHTMLBuilder.write_doc(self, docname, doctree) StandaloneHTMLBuilder.write_doc(self, docname, doctree)
def get_target_uri(self, docname, typ=None): def get_target_uri(self, docname, typ=None):

View File

@ -11,26 +11,46 @@
import cPickle as pickle import cPickle as pickle
from os import path from os import path
from datetime import datetime
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.util.osutil import ensuredir
from sphinx.websupport.search import search_adapters from sphinx.websupport.search import search_adapters
from sphinx.websupport import comments as sphinxcomments
class WebSupportApp(Sphinx): class WebSupportApp(Sphinx):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.search = kwargs.pop('search', None) self.search = kwargs.pop('search', None)
self.comments = kwargs.pop('comments', None)
Sphinx.__init__(self, *args, **kwargs) Sphinx.__init__(self, *args, **kwargs)
class WebSupport(object): class WebSupport(object):
def __init__(self, srcdir='', outdir='', search=None): def __init__(self, srcdir='', outdir='', search=None,
comments=None):
self.srcdir = srcdir self.srcdir = srcdir
self.outdir = outdir or path.join(self.srcdir, '_build', self.outdir = outdir or path.join(self.srcdir, '_build',
'websupport') 'websupport')
self.init_templating() self.init_templating()
if search is not None: if search is not None:
self.init_search(search) 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): def init_templating(self):
import sphinx import sphinx
template_path = path.join(path.dirname(sphinx.__file__), template_path = path.join(path.dirname(sphinx.__file__),
@ -52,8 +72,11 @@ class WebSupport(object):
path.join(self.outdir, 'doctrees')) path.join(self.outdir, 'doctrees'))
app = WebSupportApp(self.srcdir, self.srcdir, app = WebSupportApp(self.srcdir, self.srcdir,
self.outdir, doctreedir, 'websupport', self.outdir, doctreedir, 'websupport',
search=self.search) search=self.search,
comments=self.comments)
self.comments.pre_build()
app.build() app.build()
self.comments.post_build()
def get_document(self, docname): def get_document(self, docname):
infilename = path.join(self.outdir, docname + '.fpickle') infilename = path.join(self.outdir, docname + '.fpickle')
@ -70,3 +93,11 @@ class WebSupport(object):
document['body'] = self.results_template.render(ctx) document['body'] = self.results_template.render(ctx)
document['title'] = 'Search Results' document['title'] = 'Search Results'
return document 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. # node will not be commented.
if not self.in_commentable: if not self.in_commentable:
self.in_commentable = True 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 # 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. # span with the existing id directly before this node's HTML.
if node.attributes['ids']: if node.attributes['ids']:
self.body.append('<span id="%s"></span>' self.body.append('<span id="%s"></span>'
% node.attributes['ids'][0]) % node.attributes['ids'][0])
node.attributes['ids'] = [id] node.attributes['ids'] = ['s%s' % node_id]
node.attributes['classes'].append(self.comment_class) node.attributes['classes'].append(self.comment_class)
def handle_depart_commentable(self, node): def handle_depart_commentable(self, node):
@ -56,6 +56,10 @@ class WebSupportTranslator(HTMLTranslator):
if self.comment_class in node.attributes['classes']: if self.comment_class in node.attributes['classes']:
self.in_commentable = False self.in_commentable = False
def create_id(self, node): def add_db_node(self, node):
self.current_id += 1 comments = self.builder.app.comments
return '%s_%s' % (node.__class__.__name__, self.current_id) db_node_id = comments.add_node(document=self.builder.cur_docname,
line=node.line,
source=node.rawsource,
treeloc='???')
return db_node_id