annotate forums/unread.py @ 661:15dbe0ccda95

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