# HG changeset patch # User Brian Neal # Date 1264372391 0 # Node ID cf9f9d4c4d547971f9faedd41f7dec68dc0e8f67 # Parent 8acf5be27f18083f339ceec057188dfa19f7937f Adding a query to the forums to get all the topics with unread posts. This is for ticket #54. diff -r 8acf5be27f18 -r cf9f9d4c4d54 gpp/forums/models.py --- a/gpp/forums/models.py Sun Jan 17 20:24:01 2010 +0000 +++ b/gpp/forums/models.py Sun Jan 24 22:33:11 2010 +0000 @@ -10,9 +10,6 @@ from core.markup import site_markup -POST_EDIT_DELTA = datetime.timedelta(seconds=3) - - class Category(models.Model): """ Forums belong to a category, whose access may be assigned to groups. @@ -57,17 +54,23 @@ Returns a queryset containing the forums that the given user can "see" due to authenticated status, superuser status and group membership. """ + qs = self._for_user(user) + return qs.select_related('category', 'last_post', 'last_post__user') + + def forum_ids_for_user(self, user): + """Returns a list of forum IDs that the given user can "see".""" + qs = self._for_user(user) + return qs.values_list('id', flat=True) + + def _for_user(self, user): + """Common code for the xxx_for_user() methods.""" if user.is_superuser: qs = self.all() else: - user_groups = [] - if user.is_authenticated(): - user_groups = user.groups.all() - - qs = self.filter(Q(category__groups__isnull=True) | \ + user_groups = user.groups.all() if user.is_authenticated() else [] + qs = self.filter(Q(category__groups__isnull=True) | Q(category__groups__in=user_groups)) - - return qs.select_related('category', 'last_post', 'last_post__user') + return qs class Forum(models.Model): diff -r 8acf5be27f18 -r cf9f9d4c4d54 gpp/forums/templatetags/forum_tags.py --- a/gpp/forums/templatetags/forum_tags.py Sun Jan 17 20:24:01 2010 +0000 +++ b/gpp/forums/templatetags/forum_tags.py Sun Jan 24 22:33:11 2010 +0000 @@ -172,3 +172,20 @@ 'user_count': user_count, 'latest_user': latest_user, } + + +@register.inclusion_tag('forums/topic_icons_tag.html') +def topic_icons(topic): + """Displays the "unread", "sticky", and "locked" icons for a given topic.""" + return { + 'topic': topic, + 'MEDIA_URL': settings.MEDIA_URL, + } + + +@register.inclusion_tag('forums/topic_page_range_tag.html') +def topic_page_range(topic): + """Displays the page range links for a topic.""" + return { + 'topic': topic, + } diff -r 8acf5be27f18 -r cf9f9d4c4d54 gpp/forums/unread.py --- a/gpp/forums/unread.py Sun Jan 17 20:24:01 2010 +0000 +++ b/gpp/forums/unread.py Sun Jan 24 22:33:11 2010 +0000 @@ -5,7 +5,9 @@ """ import datetime -from forums.models import ForumLastVisit, TopicLastVisit, Topic +from django.db.models import Q + +from forums.models import ForumLastVisit, TopicLastVisit, Topic, Forum THRESHOLD = datetime.timedelta(days=7) @@ -186,3 +188,53 @@ 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.""" + #import pdb; pdb.set_trace() + + # 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() + + 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 diff -r 8acf5be27f18 -r cf9f9d4c4d54 gpp/forums/urls.py --- a/gpp/forums/urls.py Sun Jan 17 20:24:01 2010 +0000 +++ b/gpp/forums/urls.py Sun Jan 24 22:33:11 2010 +0000 @@ -22,5 +22,6 @@ url(r'^post/(\d+)/$', 'goto_post', name='forums-goto_post'), url(r'^post/new/(?P\d+)/$', 'new_post', name='forums-new_post'), url(r'^quick-reply/$', 'quick_reply_ajax', name='forums-quick_reply'), + url(r'^unread/$', 'unread_topics', name='forums-unread_topics'), ) diff -r 8acf5be27f18 -r cf9f9d4c4d54 gpp/forums/views.py --- a/gpp/forums/views.py Sun Jan 17 20:24:01 2010 +0000 +++ b/gpp/forums/views.py Sun Jan 24 22:33:11 2010 +0000 @@ -25,7 +25,7 @@ from forums.forms import NewTopicForm, NewPostForm, PostForm, MoveTopicForm, \ SplitTopicForm from forums.unread import get_forum_unread_status, get_topic_unread_status, \ - get_post_unread_status + get_post_unread_status, get_unread_topics from bio.models import UserProfile ####################################################################### @@ -33,18 +33,50 @@ TOPICS_PER_PAGE = 50 POSTS_PER_PAGE = 20 + +def get_page_num(request): + """Returns the value of the 'page' variable in GET if it exists, or 1 + if it does not.""" + + try: + page_num = int(request.GET.get('page', 1)) + except ValueError: + page_num = 1 + + return page_num + + def create_topic_paginator(topics): return DiggPaginator(topics, TOPICS_PER_PAGE, body=5, tail=2, margin=3, padding=2) def create_post_paginator(posts): return DiggPaginator(posts, POSTS_PER_PAGE, body=5, tail=2, margin=3, padding=2) + +def attach_topic_page_ranges(topics): + """Attaches a page_range attribute to each topic in the supplied list. + This attribute will be None if it is a single page topic. This is used + by the templates to generate "goto page x" links. + """ + for topic in topics: + if topic.post_count > POSTS_PER_PAGE: + pp = DiggPaginator(range(topic.post_count), POSTS_PER_PAGE, + body=2, tail=3, margin=1) + topic.page_range = pp.page(1).page_range + else: + topic.page_range = None + ####################################################################### def index(request): """ This view displays all the forums available, ordered in each category. """ + # check for special forum queries + query = request.GET.get("query") + if query == "unread": + return HttpResponseRedirect(reverse('forums-unread_topics')) + forums = Forum.objects.forums_for_user(request.user) get_forum_unread_status(forums, request.user) cats = {} @@ -77,23 +109,13 @@ get_topic_unread_status(forum, topics, request.user) paginator = create_topic_paginator(topics) - page_num = int(request.GET.get('page', 1)) + page_num = get_page_num(request) try: page = paginator.page(page_num) except InvalidPage: raise Http404 - # Attach "goto page" navigation links onto those topics that have - # more than 1 page: - - for topic in page.object_list: - if topic.post_count > POSTS_PER_PAGE: - pp = DiggPaginator(range(topic.post_count), POSTS_PER_PAGE, - body=2, tail=3, margin=1) - topic.page_range = pp.page(1).page_range - else: - topic.page_range = None - + attach_topic_page_ranges(page.object_list) # we do this for the template since it is rendered twice page_nav = render_to_string('forums/pagination.html', {'page': page}) @@ -124,7 +146,7 @@ posts = topic.posts.select_related() paginator = create_post_paginator(posts) - page_num = int(request.GET.get('page', 1)) + page_num = get_page_num(request) try: page = paginator.page(page_num) except InvalidPage: @@ -525,7 +547,7 @@ topics = forum.topics.select_related('user', 'last_post', 'last_post__user') paginator = create_topic_paginator(topics) - page_num = int(request.REQUEST.get('page', 1)) + page_num = get_page_num(request) try: page = paginator.page(page_num) except InvalidPage: @@ -619,6 +641,30 @@ context_instance=RequestContext(request)) +@login_required +def unread_topics(request): + topics = get_unread_topics(request.user) + + paginator = create_topic_paginator(topics) + page_num = get_page_num(request) + try: + page = paginator.page(page_num) + except InvalidPage: + raise Http404 + + attach_topic_page_ranges(page.object_list) + + # we do this for the template since it is rendered twice + page_nav = render_to_string('forums/pagination.html', {'page': page}) + + return render_to_response('forums/topic_list.html', { + 'title': 'Topics With Unread Posts', + 'page': page, + 'page_nav': page_nav, + }, + context_instance=RequestContext(request)) + + def _can_moderate(forum, user): """ Determines if a user has permission to moderate a given forum. diff -r 8acf5be27f18 -r cf9f9d4c4d54 gpp/templates/forums/forum_index.html --- a/gpp/templates/forums/forum_index.html Sun Jan 17 20:24:01 2010 +0000 +++ b/gpp/templates/forums/forum_index.html Sun Jan 24 22:33:11 2010 +0000 @@ -31,21 +31,10 @@ {% for topic in page.object_list %} - {% if topic.has_unread %}New Posts{% endif %} - {% if topic.sticky %}Sticky{% endif %} - {% if topic.locked %}Locked{% endif %} + {% topic_icons topic %}

{{ topic.name }}

{% if topic.page_range %} - Goto page: [ - {% for n in topic.page_range %} - {% if n %} - {{ n }} - {% else %} - … - {% endif %} - {% endfor %} - ] + {% topic_page_range topic %} {% endif %} {{ topic.reply_count }} diff -r 8acf5be27f18 -r cf9f9d4c4d54 gpp/templates/forums/index.html --- a/gpp/templates/forums/index.html Sun Jan 17 20:24:01 2010 +0000 +++ b/gpp/templates/forums/index.html Sun Jan 24 22:33:11 2010 +0000 @@ -5,6 +5,14 @@ {% block content %}

Forums

+
+ +
+
{% for iter in cats %}

{{ iter.cat }}

diff -r 8acf5be27f18 -r cf9f9d4c4d54 gpp/templates/forums/topic_icons_tag.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gpp/templates/forums/topic_icons_tag.html Sun Jan 24 22:33:11 2010 +0000 @@ -0,0 +1,4 @@ +{% if topic.has_unread %}New Posts{% endif %} +{% if topic.sticky %}Sticky{% endif %} +{% if topic.locked %}Locked{% endif %} diff -r 8acf5be27f18 -r cf9f9d4c4d54 gpp/templates/forums/topic_list.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gpp/templates/forums/topic_list.html Sun Jan 24 22:33:11 2010 +0000 @@ -0,0 +1,57 @@ +{% extends 'base.html' %} +{% load forum_tags %} +{% block title %}Forums: {{ title }}{% endblock %} +{% block content %} +

Forums: {{ title }}

+

+SurfGuitar101 Forum Index » {{ title }} +

+
+{{ page_nav }} + + + + + + + + + + + {% for topic in page.object_list %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
ForumTopicAuthorRepliesViewsLast Post
+

{{ topic.forum.name }}

+
+ {% topic_icons topic %} +

{{ topic.name }}

+ {% if topic.page_range %} + {% topic_page_range topic %} + {% endif %} +
+ {{ topic.user.username }} + + {{ topic.reply_count }} + + {{ topic.view_count }} + + {% last_post_info topic.last_post %} +
+ No topics meet your search criteria. +
+{{ page_nav }} +
+{% endblock %} diff -r 8acf5be27f18 -r cf9f9d4c4d54 gpp/templates/forums/topic_page_range_tag.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gpp/templates/forums/topic_page_range_tag.html Sun Jan 24 22:33:11 2010 +0000 @@ -0,0 +1,9 @@ +Goto page: [ +{% for n in topic.page_range %} + {% if n %} + {{ n }} + {% else %} + … + {% endif %} +{% endfor %} +] diff -r 8acf5be27f18 -r cf9f9d4c4d54 media/css/base.css --- a/media/css/base.css Sun Jan 17 20:24:01 2010 +0000 +++ b/media/css/base.css Sun Jan 24 22:33:11 2010 +0000 @@ -273,3 +273,39 @@ display: inline; margin: 0 3px; } +table.forum-topic-table { + width:100%; +} +table.forum-topic-table thead th { + background:teal; +} +table.forum-topic-table .col-0 { + width:23%; + text-align:center; +} +table.forum-topic-table .col-1 { + width:37%; + text-align:center; +} +table.forum-topic-table .col-2 { + width:5%; + text-align:center; +} +table.forum-topic-table .col-3 { + width:5%; + text-align:center; +} +table.forum-topic-table .col-4 { + width:5%; + text-align:center; +} +table.forum-topic-table .col-5 { + width:25%; + text-align:center; +} +table.forum-topic-table .info { + text-align:center; +} +#forum-query-form { + text-align:right; +}