summaryrefslogtreecommitdiff
path: root/sphinx/websupport/storage/sqlalchemy_db.py
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2010-08-21 22:36:35 +0200
committerGeorg Brandl <georg@python.org>2010-08-21 22:36:35 +0200
commit2399e4627c40b373869da4409213475ade655846 (patch)
tree572132c497bf23782f4b64e6c4177e76b702b6f5 /sphinx/websupport/storage/sqlalchemy_db.py
parent092ebec8a7eb938a2c720d57b258bd6e106bff9e (diff)
downloadsphinx-git-2399e4627c40b373869da4409213475ade655846.tar.gz
Rename module to make clear it is only for sqlalchemy.
Diffstat (limited to 'sphinx/websupport/storage/sqlalchemy_db.py')
-rw-r--r--sphinx/websupport/storage/sqlalchemy_db.py205
1 files changed, 205 insertions, 0 deletions
diff --git a/sphinx/websupport/storage/sqlalchemy_db.py b/sphinx/websupport/storage/sqlalchemy_db.py
new file mode 100644
index 000000000..4e2757a9e
--- /dev/null
+++ b/sphinx/websupport/storage/sqlalchemy_db.py
@@ -0,0 +1,205 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinx.websupport.storage.sqlalchemy_db
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ SQLAlchemy table and mapper definitions used by the
+ :class:`sphinx.websupport.storage.sqlalchemystorage.SQLAlchemyStorage`.
+
+ :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from datetime import datetime
+
+from sqlalchemy import Column, Integer, Text, String, Boolean, \
+ ForeignKey, DateTime
+from sqlalchemy.orm import relation, sessionmaker, aliased
+from sqlalchemy.ext.declarative import declarative_base
+
+Base = declarative_base()
+Session = sessionmaker()
+
+db_prefix = 'sphinx_'
+
+
+class Node(Base):
+ """Data about a Node in a doctree."""
+ __tablename__ = db_prefix + 'nodes'
+
+ id = Column(String(32), primary_key=True)
+ document = Column(String(256), nullable=False)
+ 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:
+ # If a username is provided, create a subquery to retrieve all
+ # votes by this user. We will outerjoin with the comment query
+ # with this subquery so we have a user's voting information.
+ sq = session.query(CommentVote).\
+ filter(CommentVote.username == username).subquery()
+ 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) + '.%'))
+
+ 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()
+
+ 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:
+ comment, vote = r if username else (r, 0)
+
+ inheritance_chain = comment.path.split('.')[1:]
+
+ if len(inheritance_chain) == len(list_stack) + 1:
+ parent = list_stack[-1][-1]
+ list_stack.append(parent['children'])
+ elif len(inheritance_chain) < len(list_stack):
+ while len(inheritance_chain) < len(list_stack):
+ list_stack.pop()
+
+ list_stack[-1].append(comment.serializable(vote=vote))
+
+ return comments
+
+ def __init__(self, id, document, source):
+ self.id = id
+ self.document = document
+ self.source = source
+
+
+class Comment(Base):
+ """An individual Comment being stored."""
+ __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)
+ username = Column(String(64))
+ proposal = Column(Text)
+ proposal_diff = Column(Text)
+ path = Column(String(256), index=True)
+
+ node_id = Column(String, ForeignKey(db_prefix + 'nodes.id'))
+ node = relation(Node, backref="comments")
+
+ def __init__(self, text, displayed, username, rating, time,
+ proposal, proposal_diff):
+ self.text = text
+ self.displayed = displayed
+ self.username = username
+ self.rating = rating
+ self.time = time
+ self.proposal = proposal
+ 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.node_id = node_id
+ self.path = '%s.%s' % (node_id, self.id)
+ else:
+ session = Session()
+ parent_path = session.query(Comment.path).\
+ filter(Comment.id == parent_id).one().path
+ session.close()
+ self.node_id = parent_path.split('.')[0]
+ 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,
+ 'month': self.time.month,
+ 'day': self.time.day,
+ 'hour': self.time.hour,
+ 'minute': self.time.minute,
+ 'second': self.time.second,
+ 'iso': self.time.isoformat(),
+ 'delta': self.pretty_delta(delta)}
+
+ path = self.path.split('.')
+ node = path[0] if len(path) == 2 else None
+ parent = path[-2] if len(path) > 2 else None
+
+ return {'text': self.text,
+ 'username': self.username or 'Anonymous',
+ 'id': self.id,
+ 'node': node,
+ 'parent': parent,
+ 'rating': self.rating,
+ 'displayed': self.displayed,
+ 'age': delta.seconds,
+ 'time': time,
+ 'vote': vote or 0,
+ 'proposal_diff': self.proposal_diff,
+ '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
+ 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
+
+
+class CommentVote(Base):
+ """A vote a user has made on a Comment."""
+ __tablename__ = db_prefix + 'commentvote'
+
+ username = Column(String(64), primary_key=True)
+ comment_id = Column(Integer, ForeignKey(db_prefix + 'comments.id'),
+ primary_key=True)
+ comment = relation(Comment, backref="votes")
+ # -1 if downvoted, +1 if upvoted, 0 if voted then unvoted.
+ value = Column(Integer, nullable=False)
+
+ def __init__(self, comment_id, username, value):
+ self.comment_id = comment_id
+ self.username = username
+ self.value = value