bgneal@425: """
bgneal@425: Views for the messages application.
bgneal@425: 
bgneal@425: """
bgneal@425: import datetime
bgneal@425: 
bgneal@425: from django.contrib.auth.decorators import login_required
bgneal@428: from django.contrib.auth.models import User
bgneal@425: from django.contrib import messages
bgneal@425: from django.core.paginator import Paginator, EmptyPage, InvalidPage
bgneal@425: from django.core.urlresolvers import reverse
bgneal@425: from django.http import HttpResponse
bgneal@425: from django.http import HttpResponseForbidden
bgneal@425: from django.http import HttpResponseNotAllowed
bgneal@425: from django.http import HttpResponseRedirect
bgneal@425: from django.shortcuts import get_object_or_404
bgneal@425: from django.shortcuts import render
bgneal@425: import django.utils.simplejson as json
bgneal@425: 
bgneal@425: from messages.models import Message, Options
bgneal@425: from messages.forms import OptionsForm, ComposeForm
bgneal@425: from messages.utils import reply_subject, quote_message
bgneal@425: 
bgneal@425: 
bgneal@425: MSGS_PER_PAGE = 20
bgneal@425: 
bgneal@425: TAB_INDICES = {
bgneal@425:     'inbox': 0,
bgneal@425:     'compose': 1,
bgneal@425:     'outbox': 2,
bgneal@425:     'trash': 3,
bgneal@425:     'options': 4,
bgneal@425: }
bgneal@425: 
bgneal@425: 
bgneal@425: def _get_page(request):
bgneal@425:     try:
bgneal@425:         n = int(request.GET.get('page', '1'))
bgneal@425:     except ValueError:
bgneal@425:         n = 1
bgneal@425:     return n
bgneal@425: 
bgneal@425: 
bgneal@425: @login_required
bgneal@425: def index(request, tab=None):
bgneal@425:     """
bgneal@425:     This function displays the base tabbed private messages view.
bgneal@425: 
bgneal@425:     """
bgneal@425:     tab_index = TAB_INDICES[tab] if tab else 0
bgneal@425:     return render(request, 'messages/tabbed_base.html', {
bgneal@425:         'tab': tab_index,
bgneal@429:         'unread_count': Message.objects.unread_count(request.user),
bgneal@425:         })
bgneal@425: 
bgneal@425: 
bgneal@428: @login_required
bgneal@428: def compose_to(request, receiver):
bgneal@428:     """
bgneal@428:     This function displays the base tabbed private messages view,
bgneal@428:     and configures it to display the compose PM tab for the given
bgneal@428:     receiver.
bgneal@428: 
bgneal@428:     """
bgneal@428:     user = get_object_or_404(User, username=receiver)
bgneal@428:     tab_index = TAB_INDICES['compose']
bgneal@428:     return render(request, 'messages/tabbed_base.html', {
bgneal@428:         'tab': tab_index,
bgneal@428:         'receiver': receiver,
bgneal@429:         'unread_count': Message.objects.unread_count(request.user),
bgneal@428:         })
bgneal@428: 
bgneal@428: 
bgneal@425: def inbox(request):
bgneal@425:     """
bgneal@425:     Returns the inbox for the user.
bgneal@425: 
bgneal@425:     """
bgneal@425:     if not request.user.is_authenticated():
bgneal@425:         return HttpResponseForbidden()
bgneal@425: 
bgneal@425:     msg_list = Message.objects.inbox(request.user)
bgneal@425:     paginator = Paginator(msg_list, MSGS_PER_PAGE)
bgneal@425:     try:
bgneal@425:         msgs = paginator.page(_get_page(request))
bgneal@425:     except EmptyPage, InvalidPage:
bgneal@425:         msgs = paginator.page(paginator.num_pages)
bgneal@425: 
bgneal@425:     return render(request, 'messages/inbox_tab.html', {
bgneal@425:         'msgs': msgs,
bgneal@429:         'url': reverse('messages-inbox'),
bgneal@425:         })
bgneal@425: 
bgneal@425: 
bgneal@425: def outbox(request):
bgneal@425:     """
bgneal@425:     Returns the outbox for the user.
bgneal@425: 
bgneal@425:     """
bgneal@425:     if not request.user.is_authenticated():
bgneal@425:         return HttpResponseForbidden()
bgneal@425: 
bgneal@425:     msg_list = Message.objects.outbox(request.user)
bgneal@425:     paginator = Paginator(msg_list, MSGS_PER_PAGE)
bgneal@425:     try:
bgneal@425:         msgs = paginator.page(_get_page(request))
bgneal@425:     except EmptyPage, InvalidPage:
bgneal@425:         msgs = paginator.page(paginator.num_pages)
bgneal@425: 
bgneal@425:     return render(request, 'messages/outbox_tab.html', {
bgneal@425:         'msgs': msgs,
bgneal@429:         'url': reverse('messages-outbox'),
bgneal@425:         })
bgneal@425: 
bgneal@425: 
bgneal@425: def trash(request):
bgneal@425:     """
bgneal@425:     Returns the trash for the user.
bgneal@425: 
bgneal@425:     """
bgneal@425:     if not request.user.is_authenticated():
bgneal@425:         return HttpResponseForbidden()
bgneal@425: 
bgneal@425:     msg_list = Message.objects.trash(request.user)
bgneal@425:     paginator = Paginator(msg_list, MSGS_PER_PAGE)
bgneal@425:     try:
bgneal@425:         msgs = paginator.page(_get_page(request))
bgneal@425:     except EmptyPage, InvalidPage:
bgneal@425:         msgs = paginator.page(paginator.num_pages)
bgneal@425: 
bgneal@425:     return render(request, 'messages/trash_tab.html', {
bgneal@425:         'msgs': msgs,
bgneal@429:         'url': reverse('messages-trash'),
bgneal@425:         })
bgneal@425: 
bgneal@425: 
bgneal@425: def message(request):
bgneal@425:     """
bgneal@425:     This view function retrieves a message and returns it as a JSON object.
bgneal@425: 
bgneal@425:     """
bgneal@425:     if not request.user.is_authenticated():
bgneal@425:         return HttpResponseForbidden()
bgneal@425:     if request.method != 'POST':
bgneal@425:         return HttpResponseNotAllowed(['POST'])
bgneal@425: 
bgneal@425:     msg_id = request.POST.get('msg_id')
bgneal@425:     msg = get_object_or_404(Message.objects.select_related(), pk=msg_id)
bgneal@425:     if msg.sender != request.user and msg.receiver != request.user:
bgneal@425:         return HttpResponseForbidden()
bgneal@425: 
bgneal@425:     if msg.receiver == request.user and msg.read_date is None:
bgneal@425:         msg.read_date = datetime.datetime.now()
bgneal@425:         msg.save()
bgneal@425: 
bgneal@425:     msg_dict = dict(subject=msg.subject,
bgneal@425:                     sender=msg.sender.username,
bgneal@425:                     receiver=msg.receiver.username,
bgneal@425:                     content=msg.html,
bgneal@425:                     re_subject=reply_subject(msg.subject),
bgneal@425:                     re_content=quote_message(msg.sender.username, msg.send_date,
bgneal@425:                                              msg.message))
bgneal@425: 
bgneal@425:     result = json.dumps(msg_dict, ensure_ascii=False)
bgneal@425:     return HttpResponse(result, content_type='application/json')
bgneal@425: 
bgneal@425: 
bgneal@425: def options(request):
bgneal@425:     """
bgneal@425:     This view handles the displaying and changing of private message options.
bgneal@425: 
bgneal@425:     """
bgneal@425:     if not request.user.is_authenticated():
bgneal@425:         return HttpResponseForbidden()
bgneal@425: 
bgneal@425:     if request.method == "POST":
bgneal@425:         options = Options.objects.for_user(request.user)
bgneal@425:         form = OptionsForm(request.POST, instance=options, prefix='opts')
bgneal@425:         if form.is_valid():
bgneal@425:             form.save()
bgneal@425:             messages.success(request, 'Options saved.')
bgneal@425:     else:
bgneal@425:         options = Options.objects.for_user(request.user)
bgneal@425:         form = OptionsForm(instance=options, prefix='opts')
bgneal@425: 
bgneal@425:     return render(request, 'messages/options_tab.html', {
bgneal@425:         'form': form,
bgneal@425:         })
bgneal@425: 
bgneal@425: 
bgneal@425: def compose(request, receiver=None):
bgneal@425:     """
bgneal@425:     Process or prepare the compose form to create a new private message.
bgneal@425: 
bgneal@425:     """
bgneal@425:     if not request.user.is_authenticated():
bgneal@425:         return HttpResponseForbidden()
bgneal@425: 
bgneal@425:     if request.method == "POST":
bgneal@425:         compose_form = ComposeForm(request.user, request.POST)
bgneal@430: 
bgneal@430:         # Is this a reply to another message?
bgneal@430:         parent_msg_id = request.POST.get('reply_to')
bgneal@430:         if parent_msg_id:
bgneal@430:             parent_msg = get_object_or_404(Message, id=parent_msg_id)
bgneal@430:             if (request.user != parent_msg.receiver and
bgneal@430:                 request.user != parent_msg.sender):
bgneal@430:                 return HttpResponseForbidden()
bgneal@430:         else:
bgneal@430:             parent_msg = None
bgneal@430: 
bgneal@425:         if compose_form.is_valid():
bgneal@430:             compose_form.save(parent_msg=parent_msg)
bgneal@425:             messages.success(request, 'Message sent.')
bgneal@429:             return HttpResponseRedirect(reverse('messages-index_named', args=['compose']))
bgneal@425:     else:
bgneal@425:         if receiver is not None:
bgneal@425:             form_data = {'receiver': receiver}
bgneal@425:             compose_form = ComposeForm(request.user, initial=form_data)
bgneal@425:         else:
bgneal@425:             compose_form = ComposeForm(request.user)
bgneal@425: 
bgneal@425:     return render(request, 'messages/compose_tab.html', {
bgneal@425:         'compose_form': compose_form,
bgneal@425:         })
bgneal@425: 
bgneal@425: 
bgneal@425: def _only_integers(slist):
bgneal@425:     """
bgneal@425:     Accepts a list of strings. Returns a list of integers consisting of only
bgneal@425:     those elements from the original list that could be converted to integers
bgneal@425: 
bgneal@425:     """
bgneal@425:     result = []
bgneal@425:     for s in slist:
bgneal@425:         try:
bgneal@425:             n = int(s)
bgneal@425:         except ValueError:
bgneal@425:             pass
bgneal@425:         else:
bgneal@425:             result.append(n)
bgneal@425:     return result
bgneal@425: 
bgneal@425: 
bgneal@425: def _delete_msgs(user, msg_ids):
bgneal@425:     """
bgneal@425:     Deletes the messages given by the list of msg_ids. For this to succeed, the
bgneal@425:     user has to be either the sender or receiver on each message.
bgneal@425: 
bgneal@425:     """
bgneal@425:     msg_ids = _only_integers(msg_ids)
bgneal@425:     msgs = Message.objects.filter(id__in=msg_ids)
bgneal@425: 
bgneal@425:     for msg in msgs:
bgneal@425:         if msg.sender == user:
bgneal@425:             if (msg.receiver_delete_date is not None or
bgneal@425:                     msg.read_date is None):
bgneal@425:                 # Both parties deleted the message or receiver hasn't read it
bgneal@425:                 # yet, we can delete it now
bgneal@425:                 msg.delete()
bgneal@425:             else:
bgneal@425:                 # receiver still has it in inbox
bgneal@425:                 msg.sender_delete_date = datetime.datetime.now()
bgneal@425:                 msg.save()
bgneal@425: 
bgneal@425:         elif msg.receiver == user:
bgneal@425:             if msg.sender_delete_date is not None:
bgneal@425:                 # both parties deleted the message, we can delete it now
bgneal@425:                 msg.delete()
bgneal@425:             else:
bgneal@425:                 # sender still has it in the outbox
bgneal@425:                 msg.receiver_delete_date = datetime.datetime.now()
bgneal@425:                 msg.save()
bgneal@425: 
bgneal@425: 
bgneal@425: def _undelete_msgs(user, msg_ids):
bgneal@425:     """
bgneal@425:     Attempts to "undelete" the messages given by the msg_ids list.
bgneal@425:     This will only succeed if the user is either the sender or receiver.
bgneal@425: 
bgneal@425:     """
bgneal@425:     msg_ids = _only_integers(msg_ids)
bgneal@425:     msgs = Message.objects.filter(id__in=msg_ids)
bgneal@425:     for msg in msgs:
bgneal@425:         if msg.sender == user:
bgneal@425:             msg.sender_delete_date = None
bgneal@425:             msg.save()
bgneal@425:         elif msg.receiver == user:
bgneal@425:             msg.receiver_delete_date = None
bgneal@425:             msg.save()
bgneal@425: 
bgneal@425: 
bgneal@425: def bulk(request):
bgneal@425:     """
bgneal@425:     This view processes messages in bulk. Arrays of message ids are expected in
bgneal@425:     the POST query dict: inbox_ids and outbox_ids will be deleted; trash_ids will
bgneal@425:     be undeleted.
bgneal@425: 
bgneal@425:     """
bgneal@425:     if not request.user.is_authenticated():
bgneal@425:         return HttpResponseForbidden()
bgneal@425:     if request.method != 'POST':
bgneal@425:         return HttpResponseNotAllowed(['POST'])
bgneal@425: 
bgneal@425:     delete_ids = []
bgneal@425:     if 'inbox_ids' in request.POST:
bgneal@425:         delete_ids.extend(request.POST.getlist('inbox_ids'))
bgneal@425:     if 'outbox_ids' in request.POST:
bgneal@425:         delete_ids.extend(request.POST.getlist('outbox_ids'))
bgneal@425: 
bgneal@425:     if len(delete_ids):
bgneal@425:         _delete_msgs(request.user, delete_ids)
bgneal@425: 
bgneal@425:     if 'trash_ids' in request.POST:
bgneal@425:         _undelete_msgs(request.user, request.POST.getlist('trash_ids'))
bgneal@425: 
bgneal@425:     return HttpResponse('');