Mercurial > public > sg101
view forums/unread.py @ 887:9a15f7c27526
Actually save model object upon change.
This commit was tested on the comments model.
Additional logging added.
Added check for Markdown image references.
Added TODOs after observing behavior on comments.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Tue, 03 Feb 2015 21:09:44 -0600 |
parents | ee87ea74d46b |
children |
line wrap: on
line source
""" 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