byteflow

view byteflow/discussion/models.py @ 139:24fb49ffc89e

heavy reduced number of queries for post detail view
author Alexander Solovyov <piranha@piranha.org.ua>
date Wed Oct 31 22:35:04 2007 +0200 (2007-10-31)
parents d32fb19cb4a4
children ebeed4047f19
line source
1 import datetime
3 from django.db import backend, connection, models
4 from django.contrib.contenttypes.models import ContentType
5 from django.contrib.contenttypes import generic
6 from django.contrib.auth.models import User
7 from django.utils.translation import ugettext_lazy as _
9 from blog.templatetags.nofollow import nofollow
10 from lib.helpers import text_to_html
11 from lib.db import load_related
12 from accounts.models import UserProfile
15 def load_profiles_for_comments(comments):
16 users = [comment.user for comment in comments]
17 load_related(users, UserProfile.objects, field='user', cache_field='profile_cache')
20 class CommentNodeManager(models.Manager):
21 def for_object(self, obj):
22 """
23 Create a ``QuerySet`` containing all comments for the given
24 object.
25 """
26 ctype = ContentType.objects.get_for_model(obj)
27 return self.filter(content_type__pk=ctype.id, object_id=obj.id)
29 def tree_for_object(self, obj, filters=None):
30 """
31 Get the entire comment tree for an object, with a ``level`
32 attribute added to each comment indicating the level at which
33 it sits in the tree, starting at ``0`` for root comments.
34 """
35 comments = self.for_object(obj).select_related().order_by('lft')
36 if filters:
37 comments = comments.filter(**filters)
38 stack = []
39 for comment in comments:
40 # [:] is used to create a copy of the stack, as the stack will be
41 # modified during iteration.
42 stack_copy = stack[:]
43 for j in stack_copy:
44 if j < comment.rght:
45 stack.pop()
46 stack_size = len(stack)
47 comment.level = stack_size
48 stack.append(comment.rght)
49 load_profiles_for_comments(comments)
50 return comments
52 def get_counts_in_bulk(self, objects):
53 """
54 Get a dictionary mapping object ids to the total number of
55 comments made against each object.
56 """
57 query = """
58 SELECT object_id, COUNT(object_id)
59 FROM %s
60 WHERE content_type_id = %%s
61 AND object_id IN (%s)
62 GROUP BY object_id""" % (
63 backend.quote_name(self.model._meta.db_table),
64 ','.join(['%s'] * len(objects))
65 )
66 ctype = ContentType.objects.get_for_model(objects[0])
67 cursor = connection.cursor()
68 cursor.execute(query, [ctype.id] + [obj.id for obj in objects])
69 results = cursor.fetchall()
70 return dict([(object_id, num_comments) \
71 for object_id, num_comments in results])
73 class CommentNode(models.Model):
74 """
75 A comment about any ``Model`` instance, which is also a node in
76 a tree of comments.
77 """
78 # Comment fields
79 user = models.ForeignKey(User, related_name='comments')
80 pub_date = models.DateTimeField(_(u'Publishing date'), editable=False)
81 body = models.TextField(_(u'Body'))
82 body_html = models.TextField(_(u'Body HTML'), editable=False)
83 reply_to_id = models.PositiveIntegerField(editable=False, null=True, blank=True)
84 approved = models.BooleanField(default=False)
86 # Generic relation to the object being commented on
87 content_type = models.ForeignKey(ContentType)
88 object_id = models.PositiveIntegerField(db_index=True)
89 object = generic.GenericForeignKey('content_type', 'object_id')
91 # Tree fields
92 lft = models.PositiveIntegerField(db_index=True, editable=False)
93 rght = models.PositiveIntegerField(editable=False)
95 objects = CommentNodeManager()
97 def save(self):
98 if self.body:
99 self.body = self.body.strip()
100 self.body_html = nofollow(text_to_html(self.body))
101 if not self.id:
102 self.pub_date = datetime.datetime.now()
104 # Get the object being commented on
105 comment_on = self.content_type.get_object_for_this_type(pk=self.object_id)
107 if isinstance(comment_on, CommentNode):
108 # This is a reply to another comment - adopt its content type
109 # and object id so this comment is associated with the correct
110 # object.
111 self.reply_to_id = self.object_id
112 self.content_type = comment_on.content_type
113 self.object_id = comment_on.object_id
115 # We need to update the whole tree to the right of the comment
116 target_rght = comment_on.rght - 1
117 cursor = connection.cursor()
118 cursor.execute("""
119 UPDATE comment_nodes
120 SET rght = rght + 2
121 WHERE content_type_id = %s
122 AND object_id = %s
123 AND rght > %s""" % (self.content_type.id, self.object_id, target_rght))
124 cursor.execute("""
125 UPDATE comment_nodes
126 SET lft = lft + 2
127 WHERE content_type_id = %s
128 AND object_id = %s
129 AND lft > %s""" % (self.content_type.id, self.object_id, target_rght))
130 self.lft = target_rght + 1
131 self.rght = target_rght + 2
132 else:
133 # This is a new root comment
134 cursor = connection.cursor()
135 cursor.execute("""
136 SELECT MAX(rght)
137 FROM comment_nodes
138 WHERE content_type_id = %s
139 AND object_id = %s""", (self.content_type.id, self.object_id))
140 row = cursor.fetchone()
141 current_max_rght = row[0]
142 if current_max_rght is None:
143 # There are no comments for the content object so far
144 self.lft = 1
145 self.rght = 2
146 else:
147 # Put this comment at the top level
148 self.lft = current_max_rght + 1
149 self.rght = current_max_rght + 2
150 super(CommentNode, self).save()
152 def get_absolute_url(self):
153 return '%s#c%s' % (self.object.get_absolute_url(), self.id)
155 class Meta:
156 db_table = 'comment_nodes'
158 class Admin:
159 list_display = ('__str__', 'user', 'pub_date', 'content_type', 'object_id', 'reply_to_id', 'approved')
161 def __unicode__(self):
162 return self.body[:50]
Repositories maintained by Alexander Solovyov