diff --git a/sphinx/websupport/storage/db.py b/sphinx/websupport/storage/db.py
index 983eb66d9..64f7f3e25 100644
--- a/sphinx/websupport/storage/db.py
+++ b/sphinx/websupport/storage/db.py
@@ -34,6 +34,12 @@ class Node(Base):
source = Column(Text, nullable=False)
def nested_comments(self, username, moderator):
+ """Create a tree of comments. First get all comments that are
+ descendents of this node, then convert them to a tree form.
+
+ :param username: the name of the user to get comments for.
+ :param moderator: whether the user is moderator.
+ """
session = Session()
if username:
@@ -45,24 +51,30 @@ class Node(Base):
cvalias = aliased(CommentVote, sq)
q = session.query(Comment, cvalias.value).outerjoin(cvalias)
else:
+ # If a username is not provided, we don't need to join with
+ # CommentVote.
q = session.query(Comment)
# Filter out all comments not descending from this node.
q = q.filter(Comment.path.like(str(self.id) + '.%'))
- # Filter out non-displayed comments if this isn't a moderator.
+
if not moderator:
q = q.filter(Comment.displayed == True)
+
# Retrieve all results. Results must be ordered by Comment.path
# so that we can easily transform them from a flat list to a tree.
results = q.order_by(Comment.path).all()
session.close()
- # We now need to convert the flat list of results to a nested
- # lists to form the comment tree. Results will by ordered by
- # the materialized path.
return self._nest_comments(results, username)
def _nest_comments(self, results, username):
+ """Given the flat list of results, convert the list into a
+ tree.
+
+ :param results: the flat list of comments
+ :param username: the name of the user requesting the comments.
+ """
comments = []
list_stack = [comments]
for r in results:
@@ -87,6 +99,7 @@ class Node(Base):
self.source = source
class Comment(Base):
+ """An individual Comment being stored."""
__tablename__ = db_prefix + 'comments'
id = Column(Integer, primary_key=True)
@@ -110,6 +123,9 @@ class Comment(Base):
self.proposal_diff = proposal_diff
def set_path(self, node_id, parent_id):
+ """Set the materialized path for this comment."""
+ # This exists because the path can't be set until the session has
+ # been flushed and this Comment has an id.
if node_id:
self.path = '%s.%s' % (node_id, self.id)
else:
@@ -120,6 +136,9 @@ class Comment(Base):
self.path = '%s.%s' % (parent_path, self.id)
def serializable(self, vote=0):
+ """Creates a serializable representation of the comment. This is
+ converted to JSON, and used on the client side.
+ """
delta = datetime.now() - self.time
time = {'year': self.time.year,
@@ -149,6 +168,9 @@ class Comment(Base):
'children': []}
def pretty_delta(self, delta):
+ """Create a pretty representation of the Comment's age.
+ (e.g. 2 minutes).
+ """
days = delta.days
seconds = delta.seconds
hours = seconds / 3600
@@ -162,6 +184,7 @@ class Comment(Base):
return '%s %s ago' % dt if dt[0] == 1 else '%s %ss ago' % dt
class CommentVote(Base):
+ """A vote a user has made on a Comment."""
__tablename__ = db_prefix + 'commentvote'
username = Column(String(64), primary_key=True)
diff --git a/sphinx/websupport/storage/differ.py b/sphinx/websupport/storage/differ.py
index c82ba7427..068d7e6fc 100644
--- a/sphinx/websupport/storage/differ.py
+++ b/sphinx/websupport/storage/differ.py
@@ -14,39 +14,18 @@ from cgi import escape
from difflib import Differ
class CombinedHtmlDiff(object):
-
+ """Create an HTML representation of the differences between two pieces
+ of text.
+ """
highlight_regex = re.compile(r'([\+\-\^]+)')
- def _highlight_text(self, text, next, tag):
- next = next[2:]
- new_text = []
- start = 0
- for match in self.highlight_regex.finditer(next):
- new_text.append(text[start:match.start()])
- new_text.append('<%s>' % tag)
- new_text.append(text[match.start():match.end()])
- new_text.append('%s>' % tag)
- start = match.end()
- new_text.append(text[start:])
- return ''.join(new_text)
-
- def _handle_line(self, line, next=None):
- prefix = line[0]
- text = line[2:]
-
- if prefix == ' ':
- return text
- elif prefix == '?':
- return ''
-
- if next is not None and next[0] == '?':
- tag = 'ins' if prefix == '+' else 'del'
- text = self._highlight_text(text, next, tag)
- css_class = 'prop_added' if prefix == '+' else 'prop_removed'
-
- return '%s\n' % (css_class, text.rstrip())
-
def make_html(self, source, proposal):
+ """Return the HTML representation of the differences between
+ `source` and `proposal`.
+
+ :param source: the original text
+ :param proposal: the proposed text
+ """
proposal = escape(proposal)
differ = Differ()
@@ -64,3 +43,37 @@ class CombinedHtmlDiff(object):
self._handle_line(line)
break
return ''.join(html)
+
+ def _handle_line(self, line, next=None):
+ """Handle an individual line in a diff."""
+ prefix = line[0]
+ text = line[2:]
+
+ if prefix == ' ':
+ return text
+ elif prefix == '?':
+ return ''
+
+ if next is not None and next[0] == '?':
+ tag = 'ins' if prefix == '+' else 'del'
+ text = self._highlight_text(text, next, tag)
+ css_class = 'prop_added' if prefix == '+' else 'prop_removed'
+
+ return '%s\n' % (css_class, text.rstrip())
+
+ def _highlight_text(self, text, next, tag):
+ """Highlight the specific changes made to a line by adding
+ and tags.
+ """
+ next = next[2:]
+ new_text = []
+ start = 0
+ for match in self.highlight_regex.finditer(next):
+ new_text.append(text[start:match.start()])
+ new_text.append('<%s>' % tag)
+ new_text.append(text[match.start():match.end()])
+ new_text.append('%s>' % tag)
+ start = match.end()
+ new_text.append(text[start:])
+ return ''.join(new_text)
+
diff --git a/sphinx/websupport/storage/sqlalchemystorage.py b/sphinx/websupport/storage/sqlalchemystorage.py
index 7a906dcba..553450d32 100644
--- a/sphinx/websupport/storage/sqlalchemystorage.py
+++ b/sphinx/websupport/storage/sqlalchemystorage.py
@@ -18,6 +18,9 @@ from sphinx.websupport.storage.db import Base, Node, Comment, CommentVote,\
from sphinx.websupport.storage.differ import CombinedHtmlDiff
class SQLAlchemyStorage(StorageBackend):
+ """A :class:`~sphinx.websupport.storage.StorageBackend` using
+ SQLAlchemy.
+ """
def __init__(self, engine):
self.engine = engine
Base.metadata.bind = engine
@@ -40,7 +43,8 @@ class SQLAlchemyStorage(StorageBackend):
def add_comment(self, text, displayed, username, time,
proposal, node_id, parent_id, moderator):
session = Session()
-
+ proposal_diff = None
+
if node_id and proposal:
node = session.query(Node).filter(Node.id == node_id).one()
differ = CombinedHtmlDiff()
@@ -51,19 +55,18 @@ class SQLAlchemyStorage(StorageBackend):
if not parent.displayed:
raise CommentNotAllowedError(
"Can't add child to a parent that is not displayed")
- proposal_diff = None
- else:
- proposal_diff = None
comment = Comment(text, displayed, username, 0,
time or datetime.now(), proposal, proposal_diff)
session.add(comment)
session.flush()
+ # We have to flush the session before setting the path so the
+ # Comment has an id.
comment.set_path(node_id, parent_id)
session.commit()
- comment = comment.serializable()
+ d = comment.serializable()
session.close()
- return comment
+ return d
def delete_comment(self, comment_id, username, moderator):
session = Session()
@@ -72,6 +75,7 @@ class SQLAlchemyStorage(StorageBackend):
if moderator or comment.username == username:
comment.username = '[deleted]'
comment.text = '[deleted]'
+ comment.proposal = ''
session.commit()
session.close()
else: