Added partial support for comment threading.
authorEevee <git@veekun.com>
Fri, 16 Oct 2009 05:44:57 +0000 (22:44 -0700)
committerEevee <git@veekun.com>
Fri, 16 Oct 2009 05:44:57 +0000 (22:44 -0700)
floof/controllers/comments.py
floof/model/comments.py
floof/templates/comments/lib.mako
floof/templates/comments/thread.mako

index 03aee3d..70c9073 100644 (file)
@@ -49,14 +49,13 @@ class CommentsController(BaseController):
         """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)
index 4504b6f..e1ad150 100644 (file)
@@ -1,15 +1,62 @@
 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
index df5cbf6..38e6534 100644 (file)
@@ -1,4 +1,6 @@
 <%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)}
index 1f01295..7b3580a 100644 (file)
@@ -1,5 +1,4 @@
 <%inherit file="/base.mako" />
 <%namespace name="comments" file="/comments/lib.mako" />
 
-<h1>Comments</h1>
-${comments.comment_thread(c.comments)}
+${comments.comment_block(c.comments)}