4 from sqlalchemy
import func
6 ### Utility function(s)
8 def indent_comments(comments
):
9 """Given a list of comment objects, returns the same comments (and changes
10 them in-place) with an `indent` property set on each comment. This
11 indicates how deeply nested each comment is, relative to the first comment.
12 The first comment's indent level is 0.
14 The comments must be a complete subtree, ordered by their `left` property.
16 This function will also cache the `parent` property for each comment.
22 for comment
in comments
:
23 # If this comment is a child of the last, bump the nesting level
24 if last_comment
and comment
.left
< last_comment
.right
:
26 # Remember current ancestory relevant to the root
27 right_ancestry
.append(last_comment
)
29 # On the other hand, for every nesting level this comment may have just
30 # broken out of, back out a level
31 for i
in xrange(len(right_ancestry
) - 1, -1, -1):
32 if comment
.left
> right_ancestry
[i
].right
:
36 # Cache parent comment
37 if len(right_ancestry
):
38 comment
._parent
= right_ancestry
[-1]
40 comment
.indent
= indent
42 last_comment
= comment
47 ### Usual database classes
49 class Discussion(Entity
):
50 """Represents a collection of comments attached to some other object."""
51 count
= Field(Integer
)
52 comments
= OneToMany('Comment', order_by
='left')
54 class Comment(Entity
):
55 time
= Field(DateTime
, default
=datetime
.datetime
.now
, required
=True)
56 text
= Field(Unicode(65536), required
=True)
58 # Comments are a tree, and are stored as a nested set, because:
59 # - It's easy to get a subtree with a single query.
60 # - It's easy to get a comment's depth.
61 # - It's easy to sort comments without recursion.
62 # The only real disadvantage is that adding a comment requires a quick
63 # update of all the following comments (in post-order), but that's rare
64 # enough that it shouldn't be a problem.
65 left
= Field(Integer
, index
=True, required
=True)
66 right
= Field(Integer
, required
=True)
68 discussion
= ManyToOne('Discussion', required
=True)
69 user
= ManyToOne('User', required
=True)
71 def __init__(self
, parent
=None, **kwargs
):
72 """Constructor override to set left/right correctly on a new comment.
74 super(Comment
, self
).__init__(**kwargs
)
76 # Keep the comment count updated
77 self
.discussion
.count
+= 1
80 # Parent comment given.
81 parent_right
= parent
.right
82 # Shift every left/right ahead by two to make room for the new
83 # comment's left/right
84 Comment
.query
.filter(Comment
.discussion
== self
.discussion
) \
85 .filter(Comment
.left
>= parent_right
) \
86 .update({ 'left': Comment
.left
+ 2 })
87 Comment
.query
.filter(Comment
.discussion
== self
.discussion
) \
88 .filter(Comment
.right
>= parent_right
) \
89 .update({ 'right': Comment
.right
+ 2 })
91 # Then stick the new comment in the right place
92 self
.left
= parent_right
93 self
.right
= parent_right
+ 1
96 query
= session
.query(func
.max(Comment
.right
)) \
97 .filter(Comment
.discussion
== self
.discussion
)
98 (max_right
,) = query
.one()
101 # No comments yet. Use 1 and 2
105 self
.left
= max_right
+ 1
106 self
.right
= max_right
+ 2
110 """Returns this comment's parent. This is cached, hence its being a
111 property and not a method.
113 if not hasattr(self
, '_parent'):
114 self
._parent
= Comment
.query \
115 .filter(Comment
.discussion_id
== self
.discussion_id
) \
116 .filter(Comment
.left
< self
.left
) \
117 .filter(Comment
.right
> self
.right
) \
118 .order_by(Comment
.left
.desc()) \