"""Finish replying to a comment or discussion."""
# XXX form validation woo
+ owner_object = find_owner(owner_url)
+
new_comment = Comment(
text=request.params['text'],
user=c.user,
+ discussion=owner_object.discussion,
)
-
- owner_object = find_owner(owner_url)
- discussion = owner_object.discussion
- discussion.comments.append(new_comment)
elixir.session.commit()
return redirect('/' + owner_url, code=301)
import datetime
from elixir import *
+from sqlalchemy import func
class Discussion(Entity):
"""Represents a collection of comments attached to some other object."""
count = Field(Integer)
- comments = OneToMany('Comment')
+ comments = OneToMany('Comment', order_by='left')
class Comment(Entity):
time = Field(DateTime, default=datetime.datetime.now)
text = Field(Unicode(65536))
+ # Comments are a tree, and are stored as a nested set, because:
+ # - It's easy to get a subtree with a single query.
+ # - It's easy to get a comment's depth.
+ # - It's easy to sort comments without recursion.
+ # The only real disadvantage is that adding a comment requires a quick
+ # update of all the following comments (in post-order), but that's rare
+ # enough that it shouldn't be a problem.
+ left = Field(Integer, index=True)
+ right = Field(Integer)
+
discussion = ManyToOne('Discussion')
user = ManyToOne('User')
+
+ def __init__(self, parent=None, **kwargs):
+ """Constructor override to set left/right correctly on a new comment.
+ """
+ super(Comment, self).__init__(**kwargs)
+
+ # Keep the comment count updated
+ self.discussion.count += 1
+
+ if parent:
+ # Parent comment given. Add this comment just before the parent's
+ # right side...
+ self.left = parent.right
+ self.right = parent.right + 1
+
+ # ...then adjust all rightward comments accordingly
+ Comment.query.filter(Comment.discussion == self.discussion) \
+ .filter(Comment.right > parent.right) \
+ .update({ Comment.left: Comment.left + 2,
+ Comment.right: Comment.right + 2 })
+
+ # And, finally, update the parent's right endpoint
+ parent.right += 2
+
+ else:
+ query = session.query(func.max(Comment.right)) \
+ .filter(Comment.discussion == self.discussion)
+ (max_right,) = query.one()
+
+ if not max_right:
+ # No comments yet. Use 1 and 2
+ self.left = 1
+ self.right = 2
+ else:
+ self.left = max_right + 1
+ self.right = max_right + 2
<%def name="comment_block(comments)">
+<h1>${len(comments)} comments</h1>
+## XXX make sure these do the right thing when this is a subtree
<p><a href="${url(controller='comments', action='thread', owner_url=h.get_comment_owner_url(**c.route))}">View all</a></p>
<p><a href="${url(controller='comments', action='reply', owner_url=h.get_comment_owner_url(**c.route))}">Reply</a></p>
${comment_thread(comments)}