annotate gpp/forums/unread.py @ 322:c3d3d7114749

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