mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Basic comment system.
This commit is contained in:
parent
ac262bb012
commit
03056a8a31
@ -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):
|
||||
|
@ -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)
|
||||
|
106
sphinx/websupport/comments/__init__.py
Normal file
106
sphinx/websupport/comments/__init__.py
Normal 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
|
53
sphinx/websupport/comments/db.py
Normal file
53
sphinx/websupport/comments/db.py
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user