Mercurial > public > sg101
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/forums/unread.py Sat May 05 17:10:48 2012 -0500 @@ -0,0 +1,257 @@ +""" +This file contains routines for implementing the "has unread" feature. +Forums, topics, and posts are displayed with a visual indication if they have +been read or not. +""" +import datetime +import logging + +from django.db import IntegrityError + +from forums.models import ForumLastVisit, TopicLastVisit, Topic, Forum + + +THRESHOLD = datetime.timedelta(days=14) + +####################################################################### + +def get_forum_unread_status(qs, user): + if not user.is_authenticated(): + for forum in qs: + forum.has_unread = False + return + + now = datetime.datetime.now() + min_date = now - THRESHOLD + + # retrieve ForumLastVisit records in one SQL query + forum_ids = [forum.id for forum in qs] + flvs = ForumLastVisit.objects.filter(user=user, + forum__in=forum_ids).select_related() + flvs = dict([(flv.forum.id, flv) for flv in flvs]) + + for forum in qs: + # Edge case: forum has no posts + if forum.last_post is None: + forum.has_unread = False + continue + + # Get the ForumLastVisit record + if forum.id in flvs: + flv = flvs[forum.id] + else: + # One doesn't exist, create a default one for next time, + # mark it as having no unread topics, and bail. + flv = ForumLastVisit(user=user, forum=forum) + flv.begin_date = now + flv.end_date = now + + # There is a race condition and sometimes another thread + # saves a record before we do; just log this if it happens. + try: + flv.save() + except IntegrityError: + logging.exception('get_forum_unread_status') + + forum.has_unread = False + continue + + # If the last visit record was too far in the past, + # catch that user up and mark as no unreads. + if now - flv.end_date > THRESHOLD: + forum.catchup(user, flv) + forum.has_unread = False + continue + + # Check the easy cases first. Check the last_post in the + # forum. If created after the end_date in our window, there + # are new posts. Likewise, if before the begin_date in our window, + # there are no new posts. + if forum.last_post.creation_date > flv.end_date: + forum.has_unread = True + elif forum.last_post.creation_date < flv.begin_date: + if not flv.is_caught_up(): + forum.catchup(user, flv) + forum.has_unread = False + else: + # Going to have to examine the topics in our window. + # First adjust our window if it is too old. + if now - flv.begin_date > THRESHOLD: + flv.begin_date = min_date + flv.save() + TopicLastVisit.objects.filter(user=user, topic__forum=forum, + last_visit__lt=min_date).delete() + + topics = Topic.objects.filter(forum=forum, + update_date__gt=flv.begin_date) + tracked_topics = TopicLastVisit.objects.filter( + user=user, + topic__forum=forum, + last_visit__gt=flv.begin_date).select_related('topic') + + # If the number of topics created since our window was started + # is greater than the tracked topic records, then there are new + # posts. + if topics.count() > tracked_topics.count(): + forum.has_unread = True + continue + + tracked_dict = dict((t.topic.id, t) for t in tracked_topics) + + for topic in topics: + if topic.id in tracked_dict: + if topic.update_date > tracked_dict[topic.id].last_visit: + forum.has_unread = True + break + else: + forum.has_unread = True + break + else: + # If we made it through the above loop without breaking out, + # then we are all caught up. + forum.catchup(user, flv) + forum.has_unread = False + +####################################################################### + +def get_topic_unread_status(forum, topics, user): + + # Edge case: no topics + if forum.last_post is None: + return + + # This service isn't provided to unauthenticated users + if not user.is_authenticated(): + for topic in topics: + topic.has_unread = False + return + + now = datetime.datetime.now() + + # Get the ForumLastVisit record + try: + flv = ForumLastVisit.objects.get(forum=forum, user=user) + except ForumLastVisit.DoesNotExist: + # One doesn't exist, create a default one for next time, + # mark it as having no unread topics, and bail. + flv = ForumLastVisit(user=user, forum=forum) + flv.begin_date = now + flv.end_date = now + + # There is a race condition and sometimes another thread + # saves a record before we do; just log this if it happens. + try: + flv.save() + except IntegrityError: + logging.exception('get_topic_unread_status') + + for topic in topics: + topic.has_unread = False + return + + # Are all the posts before our window? If so, all have been read. + if forum.last_post.creation_date < flv.begin_date: + for topic in topics: + topic.has_unread = False + return + + topic_ids = [topic.id for topic in topics] + tlvs = TopicLastVisit.objects.filter(user=user, topic__id__in=topic_ids) + tlvs = dict([(tlv.topic.id, tlv) for tlv in tlvs]) + + # Otherwise we have to go through the topics one by one: + for topic in topics: + if topic.update_date < flv.begin_date: + topic.has_unread = False + elif topic.update_date > flv.end_date: + topic.has_unread = True + elif topic.id in tlvs: + topic.has_unread = topic.update_date > tlvs[topic.id].last_visit + else: + topic.has_unread = True + +####################################################################### + +def get_post_unread_status(topic, posts, user): + # This service isn't provided to unauthenticated users + if not user.is_authenticated(): + for post in posts: + post.unread = False + return + + # Get the ForumLastVisit record + try: + flv = ForumLastVisit.objects.get(forum=topic.forum, user=user) + except ForumLastVisit.DoesNotExist: + # One doesn't exist, all posts are old. + for post in posts: + post.unread = False + return + + # Are all the posts before our window? If so, all have been read. + if topic.last_post.creation_date < flv.begin_date: + for post in posts: + post.unread = False + return + + # Do we have a topic last visit record for this topic? + + try: + tlv = TopicLastVisit.objects.get(user=user, topic=topic) + except TopicLastVisit.DoesNotExist: + # No we don't, we could be all caught up, or all are new + for post in posts: + post.unread = post.creation_date > flv.end_date + else: + for post in posts: + post.unread = post.creation_date > tlv.last_visit + +####################################################################### + +def get_unread_topics(user): + """Returns a list of topics the user hasn't read yet.""" + + # This is only available to authenticated users + if not user.is_authenticated(): + return [] + + now = datetime.datetime.now() + + # Obtain list of forums the user can view + forums = Forum.objects.forums_for_user(user) + + # Get forum last visit records for the forum ids + flvs = ForumLastVisit.objects.filter(user=user, + forum__in=forums).select_related() + flvs = dict([(flv.forum.id, flv) for flv in flvs]) + + unread_topics = [] + topics = Topic.objects.none() + for forum in forums: + # if the user hasn't visited the forum, create a last + # visit record set to "now" + if not forum.id in flvs: + flv = ForumLastVisit(user=user, forum=forum, begin_date=now, + end_date=now) + flv.save() + else: + flv = flvs[forum.id] + topics |= Topic.objects.filter(forum=forum, + update_date__gt=flv.begin_date).order_by('-update_date').select_related( + 'forum', 'user', 'last_post', 'last_post__user') + + if topics is not None: + # get all topic last visit records for the topics of interest + + tlvs = TopicLastVisit.objects.filter(user=user, topic__in=topics) + tlvs = dict([(tlv.topic.id, tlv) for tlv in tlvs]) + + for topic in topics: + if topic.id in tlvs: + tlv = tlvs[topic.id] + if topic.update_date > tlv.last_visit: + unread_topics.append(topic) + else: + unread_topics.append(topic) + + return unread_topics