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):
|
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):
|
||||||
|
@ -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)
|
||||||
|
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.
|
# 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
|
||||||
|
Loading…
Reference in New Issue
Block a user