bgneal@425: """ bgneal@425: Views for the messages application. bgneal@425: bgneal@425: """ bgneal@425: import datetime bgneal@679: import json bgneal@425: bgneal@425: from django.contrib.auth.decorators import login_required bgneal@428: from django.contrib.auth.models import User bgneal@436: from django.contrib import messages as django_messages bgneal@801: from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 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.shortcuts import get_object_or_404 bgneal@425: from django.shortcuts import render bgneal@425: bgneal@425: from messages.models import Message, Options bgneal@425: from messages.forms import OptionsForm, ComposeForm bgneal@566: from messages.utils import reply_subject bgneal@436: from messages import MSG_BOX_LIMIT bgneal@566: from core.functions import quote_message bgneal@425: bgneal@425: bgneal@436: MSGS_PER_PAGE = 20 # message pagination value bgneal@425: bgneal@436: # This must match the jQuery UI tab control 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@436: def _quota_check(box_name, count, request): bgneal@436: """ bgneal@436: Checks the message box count against MSG_BOX_LIMIT. bgneal@436: Emits a message to the user if the quota is exceeded. bgneal@441: bgneal@568: Returns the percent used as an integer between 0-100. bgneal@568: bgneal@436: """ bgneal@436: if count >= MSG_BOX_LIMIT: bgneal@436: django_messages.warning(request, bgneal@436: "Your %s is full. Please delete some messages." % box_name) bgneal@436: bgneal@568: return 100 * count / MSG_BOX_LIMIT bgneal@568: bgneal@436: bgneal@801: def _get_page(request, qs): bgneal@801: """Paginates the given queryset and returns a page object""" bgneal@801: paginator = Paginator(qs, MSGS_PER_PAGE) bgneal@801: try: bgneal@801: page = paginator.page(request.GET.get('page', '1')) bgneal@801: except PageNotAnInteger: bgneal@801: page = paginator.page(1) bgneal@801: except EmptyPage: bgneal@801: page = paginator.page(paginator.num_pages) bgneal@801: return page bgneal@801: bgneal@801: bgneal@425: @login_required bgneal@801: def inbox(request): bgneal@425: bgneal@801: msg_list = Message.objects.inbox(request.user) bgneal@801: msg_count = msg_list.count() bgneal@801: pct_used = _quota_check('inbox', msg_count, request) bgneal@801: bgneal@801: page = _get_page(request, msg_list) bgneal@801: bgneal@801: return render(request, 'messages/inbox.html', { bgneal@801: 'tab': 'inbox', bgneal@801: 'page': page, bgneal@801: 'inbox_pct': pct_used, bgneal@801: 'outbox_pct': None, bgneal@801: }) bgneal@801: bgneal@801: bgneal@801: @login_required bgneal@801: def outbox(request): bgneal@801: bgneal@801: msg_list = Message.objects.outbox(request.user) bgneal@801: msg_count = msg_list.count() bgneal@801: pct_used = _quota_check('outbox', msg_count, request) bgneal@801: bgneal@801: page = _get_page(request, msg_list) bgneal@801: bgneal@801: return render(request, 'messages/outbox.html', { bgneal@801: 'tab': 'outbox', bgneal@801: 'page': page, bgneal@801: 'inbox_pct': None, bgneal@801: 'outbox_pct': pct_used, bgneal@425: }) bgneal@425: bgneal@425: bgneal@428: @login_required bgneal@802: def trash(request): bgneal@802: bgneal@802: msg_list = Message.objects.trash(request.user) bgneal@802: bgneal@802: page = _get_page(request, msg_list) bgneal@802: bgneal@802: return render(request, 'messages/trash.html', { bgneal@802: 'tab': 'trash', bgneal@802: 'page': page, bgneal@802: 'inbox_pct': None, bgneal@802: 'outbox_pct': None, bgneal@802: }) bgneal@802: bgneal@802: bgneal@802: @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 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@566: re_content=quote_message(msg.sender.username, 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@436: django_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@436: django_messages.success(request, 'Message sent.') bgneal@431: compose_form = ComposeForm(request.user) 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@436: _quota_check('outbox', Message.objects.outbox(request.user).count(), request) bgneal@436: 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('');