view gpp/forums/views/subscriptions.py @ 301:ee451ad46af1

Fixing #140; limit topic notification emails to at most 1 per day, or more if the user visits the topic.
author Brian Neal <bgneal@gmail.com>
date Thu, 13 Jan 2011 03:27:42 +0000
parents a46788862737
children b2b37cdd020a
line wrap: on
line source
"""This module handles the subscriptions of users to forum topics."""
import datetime

from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.sites.models import Site
from django.core.paginator import InvalidPage
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.http import Http404
from django.template.loader import render_to_string
from django.shortcuts import get_object_or_404
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.views.decorators.http import require_POST

from forums.models import Topic, TopicLastVisit, Subscription
from core.functions import send_mail
from core.paginator import DiggPaginator


# This constant is the minimum amount of time a user will be
# notified of new posts in a topic. Currently it is set to 1 day.
# Thus a user will be notified at most once per day of new posts.
TOPIC_NOTIFY_DELAY = datetime.timedelta(days=1)


def notify_topic_subscribers(post):
    """
    The argument post is a newly created post. Send out an email
    notification to all subscribers of the post's parent Topic.
    The emails are throttled such that unless the user visits the topic,
    only 1 email will be sent per TOPIC_NOTIFY_DELAY period.
    Visiting the topic will reset this delay.

    """
    #TODO: consider moving this function of the HTTP request/response cycle.

    topic = post.topic
    subscriptions = Subscription.objects.filter(topic=post.topic).exclude(
            user=post.user).select_related()

    if not subscriptions:
        return

    subscriber_ids = [sub.user.id for sub in subscriptions]
    tlvs = dict(TopicLastVisit.objects.filter(topic=topic,
            user__in=subscriber_ids).values_list('user', 'last_visit'))

    recipients = []
    for sub in subscriptions:
        if (sub.notify_date is None or
            post.creation_date - sub.notify_date > TOPIC_NOTIFY_DELAY or
            tlvs.get(sub.user.id, datetime.datetime.min) > sub.notify_date):

            recipients.append(sub.user.email)
            sub.notify_date = post.creation_date
            sub.save()

    if recipients:
        site = Site.objects.get_current()
        subject = "[%s] Topic Reply: %s" % (site.name, topic.name)
        url_prefix = "http://%s" % site.domain
        post_url = url_prefix + post.get_absolute_url()
        unsubscribe_url = url_prefix + reverse("forums-manage_subscriptions")
        msg = render_to_string("forums/topic_notify_email.txt", {
                'poster': post.user.username,
                'topic_name': topic.name,
                'message': post.body,
                'post_url': post_url,
                'unsubscribe_url': unsubscribe_url,
                })
        for recipient in recipients:
            send_mail(subject, msg, settings.DEFAULT_FROM_EMAIL, [recipient])


@login_required
@require_POST
def subscribe_topic(request, topic_id):
    """Subscribe the user to the requested topic."""
    topic = get_object_or_404(Topic.objects.select_related(), id=topic_id)
    if topic.forum.category.can_access(request.user):
        sub = Subscription(topic=topic, user=request.user)
        sub.save()
        return HttpResponseRedirect(
            reverse("forums-subscription_status", args=[topic.id]))
    raise Http404   # TODO return HttpResponseForbidden instead


@login_required
@require_POST
def unsubscribe_topic(request, topic_id):
    """Unsubscribe the user to the requested topic."""
    topic = get_object_or_404(Topic, id=topic_id)
    try:
        sub = Subscription.objects.get(topic=topic, user=request.user)
    except Subscriptions.DoesNotExist:
        pass
    else:
        sub.delete()
    return HttpResponseRedirect(
        reverse("forums-subscription_status", args=[topic.id]))


@login_required
def subscription_status(request, topic_id):
    """Display the subscription status for the given topic."""
    topic = get_object_or_404(Topic.objects.select_related(), id=topic_id)
    is_subscribed = request.user in topic.subscribers.all()
    return render_to_response('forums/subscription_status.html', {
        'topic': topic,
        'is_subscribed': is_subscribed,
        },
        context_instance=RequestContext(request))


@login_required
def manage_subscriptions(request):
    """Display a user's topic subscriptions, and allow them to be deleted."""

    user = request.user
    if request.method == "POST":
        if request.POST.get('delete_all'):
            user.subscriptions.clear()
        else:
            delete_ids = request.POST.getlist('delete_ids')
            try:
                delete_ids = [int(id) for id in delete_ids]
            except ValueError:
                raise Http404
            for topic in user.subscriptions.filter(id__in=delete_ids):
                user.subscriptions.remove(topic)

        page_num = request.POST.get('page', 1)
    else:
        page_num = request.GET.get('page', 1)

    topics = user.subscriptions.select_related().order_by('-update_date')
    paginator = DiggPaginator(topics, 20, body=5, tail=2, margin=3, padding=2)
    try:
        page_num = int(page_num)
    except ValueError:
        page_num = 1
    try:
        page = paginator.page(page_num)
    except InvalidPage:
        raise Http404

    return render_to_response('forums/manage_topics.html', {
        'page_title': 'Topic Subscriptions',
        'description': 'The forum topics you are currently subscribed to are listed below.',
        'page': page,
        },
        context_instance=RequestContext(request))