bgneal@232: """This module handles the subscriptions of users to forum topics.""" bgneal@301: import datetime bgneal@301: bgneal@232: from django.conf import settings bgneal@232: from django.contrib.auth.decorators import login_required bgneal@232: from django.contrib.sites.models import Site bgneal@232: from django.core.paginator import InvalidPage bgneal@232: from django.core.urlresolvers import reverse bgneal@232: from django.http import HttpResponseRedirect bgneal@232: from django.http import Http404 bgneal@232: from django.template.loader import render_to_string bgneal@232: from django.shortcuts import get_object_or_404 bgneal@232: from django.shortcuts import render_to_response bgneal@232: from django.template import RequestContext bgneal@232: from django.views.decorators.http import require_POST bgneal@232: bgneal@301: from forums.models import Topic, TopicLastVisit, Subscription bgneal@232: from core.functions import send_mail bgneal@232: from core.paginator import DiggPaginator bgneal@232: bgneal@232: bgneal@301: # This constant is the minimum amount of time a user will be bgneal@301: # notified of new posts in a topic. Currently it is set to 1 day. bgneal@301: # Thus a user will be notified at most once per day of new posts. bgneal@301: TOPIC_NOTIFY_DELAY = datetime.timedelta(days=1) bgneal@301: bgneal@301: bgneal@232: def notify_topic_subscribers(post): bgneal@301: """ bgneal@301: The argument post is a newly created post. Send out an email bgneal@301: notification to all subscribers of the post's parent Topic. bgneal@301: The emails are throttled such that unless the user visits the topic, bgneal@301: only 1 email will be sent per TOPIC_NOTIFY_DELAY period. bgneal@301: Visiting the topic will reset this delay. bgneal@301: bgneal@301: """ bgneal@301: #TODO: consider moving this function of the HTTP request/response cycle. bgneal@232: bgneal@232: topic = post.topic bgneal@301: subscriptions = Subscription.objects.filter(topic=post.topic).exclude( bgneal@301: user=post.user).select_related() bgneal@301: bgneal@301: if not subscriptions: bgneal@301: return bgneal@301: bgneal@301: subscriber_ids = [sub.user.id for sub in subscriptions] bgneal@301: tlvs = dict(TopicLastVisit.objects.filter(topic=topic, bgneal@301: user__in=subscriber_ids).values_list('user', 'last_visit')) bgneal@301: bgneal@301: recipients = [] bgneal@301: for sub in subscriptions: bgneal@301: if (sub.notify_date is None or bgneal@301: post.creation_date - sub.notify_date > TOPIC_NOTIFY_DELAY or bgneal@301: tlvs.get(sub.user.id, datetime.datetime.min) > sub.notify_date): bgneal@301: bgneal@301: recipients.append(sub.user.email) bgneal@301: sub.notify_date = post.creation_date bgneal@301: sub.save() bgneal@232: bgneal@232: if recipients: bgneal@232: site = Site.objects.get_current() bgneal@232: subject = "[%s] Topic Reply: %s" % (site.name, topic.name) bgneal@232: url_prefix = "http://%s" % site.domain bgneal@232: post_url = url_prefix + post.get_absolute_url() bgneal@232: unsubscribe_url = url_prefix + reverse("forums-manage_subscriptions") bgneal@232: msg = render_to_string("forums/topic_notify_email.txt", { bgneal@232: 'poster': post.user.username, bgneal@232: 'topic_name': topic.name, bgneal@232: 'message': post.body, bgneal@232: 'post_url': post_url, bgneal@232: 'unsubscribe_url': unsubscribe_url, bgneal@232: }) bgneal@232: for recipient in recipients: bgneal@232: send_mail(subject, msg, settings.DEFAULT_FROM_EMAIL, [recipient]) bgneal@232: bgneal@232: bgneal@232: @login_required bgneal@232: @require_POST bgneal@232: def subscribe_topic(request, topic_id): bgneal@232: """Subscribe the user to the requested topic.""" bgneal@232: topic = get_object_or_404(Topic.objects.select_related(), id=topic_id) bgneal@232: if topic.forum.category.can_access(request.user): bgneal@301: sub = Subscription(topic=topic, user=request.user) bgneal@301: sub.save() bgneal@232: return HttpResponseRedirect( bgneal@232: reverse("forums-subscription_status", args=[topic.id])) bgneal@232: raise Http404 # TODO return HttpResponseForbidden instead bgneal@232: bgneal@232: bgneal@232: @login_required bgneal@232: @require_POST bgneal@232: def unsubscribe_topic(request, topic_id): bgneal@232: """Unsubscribe the user to the requested topic.""" bgneal@232: topic = get_object_or_404(Topic, id=topic_id) bgneal@301: try: bgneal@301: sub = Subscription.objects.get(topic=topic, user=request.user) bgneal@301: except Subscriptions.DoesNotExist: bgneal@301: pass bgneal@301: else: bgneal@301: sub.delete() bgneal@232: return HttpResponseRedirect( bgneal@232: reverse("forums-subscription_status", args=[topic.id])) bgneal@232: bgneal@232: bgneal@232: @login_required bgneal@232: def subscription_status(request, topic_id): bgneal@232: """Display the subscription status for the given topic.""" bgneal@232: topic = get_object_or_404(Topic.objects.select_related(), id=topic_id) bgneal@232: is_subscribed = request.user in topic.subscribers.all() bgneal@232: return render_to_response('forums/subscription_status.html', { bgneal@232: 'topic': topic, bgneal@232: 'is_subscribed': is_subscribed, bgneal@232: }, bgneal@232: context_instance=RequestContext(request)) bgneal@232: bgneal@232: bgneal@232: @login_required bgneal@232: def manage_subscriptions(request): bgneal@232: """Display a user's topic subscriptions, and allow them to be deleted.""" bgneal@232: bgneal@232: user = request.user bgneal@232: if request.method == "POST": bgneal@232: if request.POST.get('delete_all'): bgneal@232: user.subscriptions.clear() bgneal@232: else: bgneal@232: delete_ids = request.POST.getlist('delete_ids') bgneal@232: try: bgneal@232: delete_ids = [int(id) for id in delete_ids] bgneal@232: except ValueError: bgneal@232: raise Http404 bgneal@232: for topic in user.subscriptions.filter(id__in=delete_ids): bgneal@232: user.subscriptions.remove(topic) bgneal@232: bgneal@232: page_num = request.POST.get('page', 1) bgneal@232: else: bgneal@232: page_num = request.GET.get('page', 1) bgneal@232: bgneal@232: topics = user.subscriptions.select_related().order_by('-update_date') bgneal@232: paginator = DiggPaginator(topics, 20, body=5, tail=2, margin=3, padding=2) bgneal@232: try: bgneal@232: page_num = int(page_num) bgneal@232: except ValueError: bgneal@232: page_num = 1 bgneal@232: try: bgneal@232: page = paginator.page(page_num) bgneal@232: except InvalidPage: bgneal@232: raise Http404 bgneal@232: bgneal@232: return render_to_response('forums/manage_topics.html', { bgneal@232: 'page_title': 'Topic Subscriptions', bgneal@232: 'description': 'The forum topics you are currently subscribed to are listed below.', bgneal@232: 'page': page, bgneal@232: }, bgneal@232: context_instance=RequestContext(request))