Mercurial > public > sg101
changeset 167:cf9f9d4c4d54
Adding a query to the forums to get all the topics with unread posts. This is for ticket #54.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Sun, 24 Jan 2010 22:33:11 +0000 |
parents | 8acf5be27f18 |
children | e6d4dfdfbc64 |
files | gpp/forums/models.py gpp/forums/templatetags/forum_tags.py gpp/forums/unread.py gpp/forums/urls.py gpp/forums/views.py gpp/templates/forums/forum_index.html gpp/templates/forums/index.html gpp/templates/forums/topic_icons_tag.html gpp/templates/forums/topic_list.html gpp/templates/forums/topic_page_range_tag.html media/css/base.css |
diffstat | 11 files changed, 261 insertions(+), 39 deletions(-) [+] |
line wrap: on
line diff
--- 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):
--- 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, + }
--- 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
--- 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<topic_id>\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'), )
--- 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.
--- 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 %} <tr class="{% cycle 'odd' 'even' %}"> <td> - {% if topic.has_unread %}<img src="{{ MEDIA_URL }}icons/new.png" alt="New Posts" title="New Posts" class="forums-topic-icon" />{% endif %} - {% if topic.sticky %}<img src="{{ MEDIA_URL }}icons/asterisk_orange.png" alt="Sticky" title="Sticky" class="forums-topic-icon" />{% endif %} - {% if topic.locked %}<img src="{{ MEDIA_URL }}icons/lock.png" alt="Locked" title="Locked" - class="forums-topic-icon" />{% endif %} + {% topic_icons topic %} <h4><a href="{{ topic.get_absolute_url }}">{{ topic.name }}</a></h4> {% if topic.page_range %} - <span class="small">Goto page: [ - {% for n in topic.page_range %} - {% if n %} - <a href="{{ topic.get_absolute_url }}?page={{ n }}">{{ n }}</a> - {% else %} - … - {% endif %} - {% endfor %} - ]</span> + {% topic_page_range topic %} {% endif %} </td> <td class="forum-index_replies">{{ topic.reply_count }}</td>
--- 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 %} <h2>Forums</h2> +<form action="." method="get" id="forum-query-form"> + <select name="query"> + <option value="unread">Show topics with unread posts</option> + <option value="mine">Show my posts</option> + <option value="unanswered">Show unanswered posts</option> + </select><input type="submit" value="Go" /> +</form> + <div class="forum-block"> {% for iter in cats %} <h3>{{ iter.cat }}</h3>
--- /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 %}<img src="{{ MEDIA_URL }}icons/new.png" alt="New Posts" title="New Posts" class="forums-topic-icon" />{% endif %} +{% if topic.sticky %}<img src="{{ MEDIA_URL }}icons/asterisk_orange.png" alt="Sticky" title="Sticky" class="forums-topic-icon" />{% endif %} +{% if topic.locked %}<img src="{{ MEDIA_URL }}icons/lock.png" alt="Locked" title="Locked" +class="forums-topic-icon" />{% endif %}
--- /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 %} +<h2>Forums: {{ title }}</h2> +<h3> +<a href="{% url forums-index %}">SurfGuitar101 Forum Index</a> » {{ title }} +</h3> +<div class="forum-block"> +{{ page_nav }} +<table class="forum-topic-table"> + <thead> + <th class="col-0">Forum</th> + <th class="col-1">Topic</th> + <th class="col-2">Author</th> + <th class="col-3">Replies</th> + <th class="col-4">Views</th> + <th class="col-5">Last Post</th> + </thead> + <tbody> + {% for topic in page.object_list %} + <tr class="{% cycle 'odd' 'even' %}"> + <td class="col-0"> + <h4><a href="{{ topic.forum.get_absolute_url }}">{{ topic.forum.name }}</a></h4> + </td> + <td class="col-1"> + {% topic_icons topic %} + <h4><a href="{{ topic.get_absolute_url }}">{{ topic.name }}</a></h4> + {% if topic.page_range %} + {% topic_page_range topic %} + {% endif %} + </td> + <td class="col-2"> + <a href="{% url bio-view_profile username=topic.user.username %}" title="View profile for {{ topic.user.username }}">{{ topic.user.username }}</a> + </td> + <td class="col-3"> + {{ topic.reply_count }} + </td> + <td class="col-4"> + {{ topic.view_count }} + </td> + <td class="col-5"> + {% last_post_info topic.last_post %} + </td> + </tr> + {% empty %} + <tr> + <td colspan="6" class="info"> + <em>No topics meet your search criteria.</em> + </td> + </tr> + {% endfor %} + </tbody> +</table> +{{ page_nav }} +</div> +{% endblock %}
--- /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 @@ +<span class="small">Goto page: [ +{% for n in topic.page_range %} + {% if n %} + <a href="{{ topic.get_absolute_url }}?page={{ n }}">{{ n }}</a> + {% else %} + … + {% endif %} +{% endfor %} +]</span>
--- 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; +}