comparison forums/unread.py @ 581:ee87ea74d46b

For Django 1.4, rearranged project structure for new manage.py.
author Brian Neal <bgneal@gmail.com>
date Sat, 05 May 2012 17:10:48 -0500
parents gpp/forums/unread.py@c06d836c8b84
children
comparison
equal deleted inserted replaced
580:c525f3e0b5d0 581:ee87ea74d46b
1 """
2 This file contains routines for implementing the "has unread" feature.
3 Forums, topics, and posts are displayed with a visual indication if they have
4 been read or not.
5 """
6 import datetime
7 import logging
8
9 from django.db import IntegrityError
10
11 from forums.models import ForumLastVisit, TopicLastVisit, Topic, Forum
12
13
14 THRESHOLD = datetime.timedelta(days=14)
15
16 #######################################################################
17
18 def get_forum_unread_status(qs, user):
19 if not user.is_authenticated():
20 for forum in qs:
21 forum.has_unread = False
22 return
23
24 now = datetime.datetime.now()
25 min_date = now - THRESHOLD
26
27 # retrieve ForumLastVisit records in one SQL query
28 forum_ids = [forum.id for forum in qs]
29 flvs = ForumLastVisit.objects.filter(user=user,
30 forum__in=forum_ids).select_related()
31 flvs = dict([(flv.forum.id, flv) for flv in flvs])
32
33 for forum in qs:
34 # Edge case: forum has no posts
35 if forum.last_post is None:
36 forum.has_unread = False
37 continue
38
39 # Get the ForumLastVisit record
40 if forum.id in flvs:
41 flv = flvs[forum.id]
42 else:
43 # One doesn't exist, create a default one for next time,
44 # mark it as having no unread topics, and bail.
45 flv = ForumLastVisit(user=user, forum=forum)
46 flv.begin_date = now
47 flv.end_date = now
48
49 # There is a race condition and sometimes another thread
50 # saves a record before we do; just log this if it happens.
51 try:
52 flv.save()
53 except IntegrityError:
54 logging.exception('get_forum_unread_status')
55
56 forum.has_unread = False
57 continue
58
59 # If the last visit record was too far in the past,
60 # catch that user up and mark as no unreads.
61 if now - flv.end_date > THRESHOLD:
62 forum.catchup(user, flv)
63 forum.has_unread = False
64 continue
65
66 # Check the easy cases first. Check the last_post in the
67 # forum. If created after the end_date in our window, there
68 # are new posts. Likewise, if before the begin_date in our window,
69 # there are no new posts.
70 if forum.last_post.creation_date > flv.end_date:
71 forum.has_unread = True
72 elif forum.last_post.creation_date < flv.begin_date:
73 if not flv.is_caught_up():
74 forum.catchup(user, flv)
75 forum.has_unread = False
76 else:
77 # Going to have to examine the topics in our window.
78 # First adjust our window if it is too old.
79 if now - flv.begin_date > THRESHOLD:
80 flv.begin_date = min_date
81 flv.save()
82 TopicLastVisit.objects.filter(user=user, topic__forum=forum,
83 last_visit__lt=min_date).delete()
84
85 topics = Topic.objects.filter(forum=forum,
86 update_date__gt=flv.begin_date)
87 tracked_topics = TopicLastVisit.objects.filter(
88 user=user,
89 topic__forum=forum,
90 last_visit__gt=flv.begin_date).select_related('topic')
91
92 # If the number of topics created since our window was started
93 # is greater than the tracked topic records, then there are new
94 # posts.
95 if topics.count() > tracked_topics.count():
96 forum.has_unread = True
97 continue
98
99 tracked_dict = dict((t.topic.id, t) for t in tracked_topics)
100
101 for topic in topics:
102 if topic.id in tracked_dict:
103 if topic.update_date > tracked_dict[topic.id].last_visit:
104 forum.has_unread = True
105 break
106 else:
107 forum.has_unread = True
108 break
109 else:
110 # If we made it through the above loop without breaking out,
111 # then we are all caught up.
112 forum.catchup(user, flv)
113 forum.has_unread = False
114
115 #######################################################################
116
117 def get_topic_unread_status(forum, topics, user):
118
119 # Edge case: no topics
120 if forum.last_post is None:
121 return
122
123 # This service isn't provided to unauthenticated users
124 if not user.is_authenticated():
125 for topic in topics:
126 topic.has_unread = False
127 return
128
129 now = datetime.datetime.now()
130
131 # Get the ForumLastVisit record
132 try:
133 flv = ForumLastVisit.objects.get(forum=forum, user=user)
134 except ForumLastVisit.DoesNotExist:
135 # One doesn't exist, create a default one for next time,
136 # mark it as having no unread topics, and bail.
137 flv = ForumLastVisit(user=user, forum=forum)
138 flv.begin_date = now
139 flv.end_date = now
140
141 # There is a race condition and sometimes another thread
142 # saves a record before we do; just log this if it happens.
143 try:
144 flv.save()
145 except IntegrityError:
146 logging.exception('get_topic_unread_status')
147
148 for topic in topics:
149 topic.has_unread = False
150 return
151
152 # Are all the posts before our window? If so, all have been read.
153 if forum.last_post.creation_date < flv.begin_date:
154 for topic in topics:
155 topic.has_unread = False
156 return
157
158 topic_ids = [topic.id for topic in topics]
159 tlvs = TopicLastVisit.objects.filter(user=user, topic__id__in=topic_ids)
160 tlvs = dict([(tlv.topic.id, tlv) for tlv in tlvs])
161
162 # Otherwise we have to go through the topics one by one:
163 for topic in topics:
164 if topic.update_date < flv.begin_date:
165 topic.has_unread = False
166 elif topic.update_date > flv.end_date:
167 topic.has_unread = True
168 elif topic.id in tlvs:
169 topic.has_unread = topic.update_date > tlvs[topic.id].last_visit
170 else:
171 topic.has_unread = True
172
173 #######################################################################
174
175 def get_post_unread_status(topic, posts, user):
176 # This service isn't provided to unauthenticated users
177 if not user.is_authenticated():
178 for post in posts:
179 post.unread = False
180 return
181
182 # Get the ForumLastVisit record
183 try:
184 flv = ForumLastVisit.objects.get(forum=topic.forum, user=user)
185 except ForumLastVisit.DoesNotExist:
186 # One doesn't exist, all posts are old.
187 for post in posts:
188 post.unread = False
189 return
190
191 # Are all the posts before our window? If so, all have been read.
192 if topic.last_post.creation_date < flv.begin_date:
193 for post in posts:
194 post.unread = False
195 return
196
197 # Do we have a topic last visit record for this topic?
198
199 try:
200 tlv = TopicLastVisit.objects.get(user=user, topic=topic)
201 except TopicLastVisit.DoesNotExist:
202 # No we don't, we could be all caught up, or all are new
203 for post in posts:
204 post.unread = post.creation_date > flv.end_date
205 else:
206 for post in posts:
207 post.unread = post.creation_date > tlv.last_visit
208
209 #######################################################################
210
211 def get_unread_topics(user):
212 """Returns a list of topics the user hasn't read yet."""
213
214 # This is only available to authenticated users
215 if not user.is_authenticated():
216 return []
217
218 now = datetime.datetime.now()
219
220 # Obtain list of forums the user can view
221 forums = Forum.objects.forums_for_user(user)
222
223 # Get forum last visit records for the forum ids
224 flvs = ForumLastVisit.objects.filter(user=user,
225 forum__in=forums).select_related()
226 flvs = dict([(flv.forum.id, flv) for flv in flvs])
227
228 unread_topics = []
229 topics = Topic.objects.none()
230 for forum in forums:
231 # if the user hasn't visited the forum, create a last
232 # visit record set to "now"
233 if not forum.id in flvs:
234 flv = ForumLastVisit(user=user, forum=forum, begin_date=now,
235 end_date=now)
236 flv.save()
237 else:
238 flv = flvs[forum.id]
239 topics |= Topic.objects.filter(forum=forum,
240 update_date__gt=flv.begin_date).order_by('-update_date').select_related(
241 'forum', 'user', 'last_post', 'last_post__user')
242
243 if topics is not None:
244 # get all topic last visit records for the topics of interest
245
246 tlvs = TopicLastVisit.objects.filter(user=user, topic__in=topics)
247 tlvs = dict([(tlv.topic.id, tlv) for tlv in tlvs])
248
249 for topic in topics:
250 if topic.id in tlvs:
251 tlv = tlvs[topic.id]
252 if topic.update_date > tlv.last_visit:
253 unread_topics.append(topic)
254 else:
255 unread_topics.append(topic)
256
257 return unread_topics