annotate messages/views.py @ 806:59e36169a814

Private messages refactor: add delete functionality.
author Brian Neal <bgneal@gmail.com>
date Mon, 01 Sep 2014 15:53:35 -0500
parents e966d5553955
children 22cc8c1aec03
rev   line source
bgneal@425 1 """
bgneal@425 2 Views for the messages application.
bgneal@425 3
bgneal@425 4 """
bgneal@425 5 import datetime
bgneal@679 6 import json
bgneal@425 7
bgneal@425 8 from django.contrib.auth.decorators import login_required
bgneal@806 9 from django.views.decorators.http import require_POST
bgneal@428 10 from django.contrib.auth.models import User
bgneal@436 11 from django.contrib import messages as django_messages
bgneal@801 12 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
bgneal@425 13 from django.core.urlresolvers import reverse
bgneal@425 14 from django.http import HttpResponse
bgneal@425 15 from django.http import HttpResponseForbidden
bgneal@425 16 from django.http import HttpResponseNotAllowed
bgneal@425 17 from django.shortcuts import get_object_or_404
bgneal@805 18 from django.shortcuts import render, redirect
bgneal@425 19
bgneal@425 20 from messages.models import Message, Options
bgneal@425 21 from messages.forms import OptionsForm, ComposeForm
bgneal@566 22 from messages.utils import reply_subject
bgneal@436 23 from messages import MSG_BOX_LIMIT
bgneal@566 24 from core.functions import quote_message
bgneal@425 25
bgneal@425 26
bgneal@436 27 MSGS_PER_PAGE = 20 # message pagination value
bgneal@425 28
bgneal@436 29 # This must match the jQuery UI tab control
bgneal@425 30 TAB_INDICES = {
bgneal@425 31 'inbox': 0,
bgneal@425 32 'compose': 1,
bgneal@425 33 'outbox': 2,
bgneal@425 34 'trash': 3,
bgneal@425 35 'options': 4,
bgneal@425 36 }
bgneal@425 37
bgneal@425 38
bgneal@436 39 def _quota_check(box_name, count, request):
bgneal@436 40 """
bgneal@436 41 Checks the message box count against MSG_BOX_LIMIT.
bgneal@436 42 Emits a message to the user if the quota is exceeded.
bgneal@441 43
bgneal@568 44 Returns the percent used as an integer between 0-100.
bgneal@568 45
bgneal@436 46 """
bgneal@436 47 if count >= MSG_BOX_LIMIT:
bgneal@436 48 django_messages.warning(request,
bgneal@436 49 "Your %s is full. Please delete some messages." % box_name)
bgneal@436 50
bgneal@568 51 return 100 * count / MSG_BOX_LIMIT
bgneal@568 52
bgneal@436 53
bgneal@801 54 def _get_page(request, qs):
bgneal@801 55 """Paginates the given queryset and returns a page object"""
bgneal@801 56 paginator = Paginator(qs, MSGS_PER_PAGE)
bgneal@801 57 try:
bgneal@801 58 page = paginator.page(request.GET.get('page', '1'))
bgneal@801 59 except PageNotAnInteger:
bgneal@801 60 page = paginator.page(1)
bgneal@801 61 except EmptyPage:
bgneal@801 62 page = paginator.page(paginator.num_pages)
bgneal@801 63 return page
bgneal@801 64
bgneal@801 65
bgneal@425 66 @login_required
bgneal@801 67 def inbox(request):
bgneal@425 68
bgneal@801 69 msg_list = Message.objects.inbox(request.user)
bgneal@801 70 msg_count = msg_list.count()
bgneal@801 71 pct_used = _quota_check('inbox', msg_count, request)
bgneal@801 72
bgneal@801 73 page = _get_page(request, msg_list)
bgneal@801 74
bgneal@801 75 return render(request, 'messages/inbox.html', {
bgneal@801 76 'tab': 'inbox',
bgneal@801 77 'page': page,
bgneal@801 78 'inbox_pct': pct_used,
bgneal@801 79 'outbox_pct': None,
bgneal@801 80 })
bgneal@801 81
bgneal@801 82
bgneal@801 83 @login_required
bgneal@801 84 def outbox(request):
bgneal@801 85
bgneal@801 86 msg_list = Message.objects.outbox(request.user)
bgneal@801 87 msg_count = msg_list.count()
bgneal@801 88 pct_used = _quota_check('outbox', msg_count, request)
bgneal@801 89
bgneal@801 90 page = _get_page(request, msg_list)
bgneal@801 91
bgneal@801 92 return render(request, 'messages/outbox.html', {
bgneal@801 93 'tab': 'outbox',
bgneal@801 94 'page': page,
bgneal@801 95 'inbox_pct': None,
bgneal@801 96 'outbox_pct': pct_used,
bgneal@425 97 })
bgneal@425 98
bgneal@425 99
bgneal@428 100 @login_required
bgneal@802 101 def trash(request):
bgneal@802 102
bgneal@802 103 msg_list = Message.objects.trash(request.user)
bgneal@802 104
bgneal@802 105 page = _get_page(request, msg_list)
bgneal@802 106
bgneal@802 107 return render(request, 'messages/trash.html', {
bgneal@802 108 'tab': 'trash',
bgneal@802 109 'page': page,
bgneal@802 110 'inbox_pct': None,
bgneal@802 111 'outbox_pct': None,
bgneal@802 112 })
bgneal@802 113
bgneal@802 114
bgneal@802 115 @login_required
bgneal@803 116 def options(request):
bgneal@803 117 """
bgneal@803 118 This view handles the displaying and changing of private message options.
bgneal@803 119
bgneal@803 120 """
bgneal@803 121 if request.method == 'POST':
bgneal@803 122 options = Options.objects.for_user(request.user)
bgneal@803 123 form = OptionsForm(request.POST, instance=options, prefix='opts')
bgneal@803 124 if form.is_valid():
bgneal@803 125 form.save()
bgneal@803 126 django_messages.success(request, 'Options saved.')
bgneal@803 127 else:
bgneal@803 128 options = Options.objects.for_user(request.user)
bgneal@803 129 form = OptionsForm(instance=options, prefix='opts')
bgneal@803 130
bgneal@803 131 return render(request, 'messages/options.html', {
bgneal@803 132 'tab': 'options',
bgneal@803 133 'form': form,
bgneal@803 134 })
bgneal@803 135
bgneal@803 136
bgneal@803 137 @login_required
bgneal@804 138 def compose(request):
bgneal@428 139 """
bgneal@804 140 Process or prepare the compose form to create a new private message.
bgneal@428 141
bgneal@428 142 """
bgneal@804 143 if request.method == 'POST':
bgneal@804 144 compose_form = ComposeForm(request.user, request.POST)
bgneal@804 145 if compose_form.is_valid():
bgneal@804 146 compose_form.save()
bgneal@804 147 django_messages.success(request, 'Message sent.')
bgneal@804 148 compose_form = ComposeForm(request.user)
bgneal@804 149 else:
bgneal@804 150 receiver = request.GET.get('to')
bgneal@804 151 if receiver:
bgneal@804 152 form_data = {'receiver': receiver}
bgneal@804 153 compose_form = ComposeForm(request.user, initial=form_data)
bgneal@804 154 else:
bgneal@804 155 compose_form = ComposeForm(request.user)
bgneal@804 156
bgneal@804 157 _quota_check('outbox', Message.objects.outbox(request.user).count(), request)
bgneal@804 158
bgneal@804 159 return render(request, 'messages/compose.html', {
bgneal@804 160 'tab': 'compose',
bgneal@804 161 'compose_form': compose_form,
bgneal@428 162 })
bgneal@428 163
bgneal@428 164
bgneal@804 165 @login_required
bgneal@804 166 def view(request, msg_id):
bgneal@425 167 """
bgneal@805 168 This view function displays a private message for reading to the user. If
bgneal@805 169 the user is a recipient of the message, a reply can be composed and sent.
bgneal@425 170
bgneal@425 171 """
bgneal@805 172 if request.method == 'POST':
bgneal@805 173 form = ComposeForm(request.user, request.POST)
bgneal@805 174 if form.is_valid():
bgneal@805 175 form.save()
bgneal@805 176 django_messages.success(request, 'Reply sent.')
bgneal@805 177 return redirect('messages-inbox')
bgneal@805 178 else:
bgneal@805 179 msg = get_object_or_404(Message.objects.select_related(), pk=msg_id)
bgneal@805 180 if msg.sender != request.user and msg.receiver != request.user:
bgneal@805 181 django_messages.error(request,
bgneal@805 182 "You don't have permission to read that message.")
bgneal@805 183 return redirect('messages-inbox')
bgneal@425 184
bgneal@805 185 initial_data = {
bgneal@805 186 'receiver': msg.sender.username,
bgneal@805 187 'subject': reply_subject(msg.subject),
bgneal@805 188 'message': quote_message(msg.sender.username, msg.message),
bgneal@805 189 'parent_id': msg.pk,
bgneal@805 190 }
bgneal@425 191
bgneal@805 192 if msg.receiver == request.user:
bgneal@805 193 if msg.read_date is None:
bgneal@805 194 msg.read_date = datetime.datetime.now()
bgneal@805 195 msg.save()
bgneal@805 196 else:
bgneal@805 197 initial_data['receiver'] = msg.receiver.username
bgneal@425 198
bgneal@805 199 form = ComposeForm(request.user, initial=initial_data)
bgneal@425 200
bgneal@805 201 return render(request, 'messages/view_message.html', {
bgneal@805 202 'msg': msg,
bgneal@805 203 'form': form,
bgneal@805 204 })
bgneal@425 205
bgneal@425 206
bgneal@806 207 def _delete_pms(user, pm_ids):
bgneal@425 208 """
bgneal@806 209 Process the request to delete the list of PM ids by user.
bgneal@425 210
bgneal@425 211 """
bgneal@806 212 msgs = Message.objects.filter(id__in=pm_ids)
bgneal@806 213 now = datetime.datetime.now()
bgneal@425 214
bgneal@425 215 for msg in msgs:
bgneal@425 216 if msg.sender == user:
bgneal@806 217 if msg.receiver_delete_date is not None or msg.read_date is None:
bgneal@425 218 # Both parties deleted the message or receiver hasn't read it
bgneal@425 219 # yet, we can delete it now
bgneal@425 220 msg.delete()
bgneal@425 221 else:
bgneal@806 222 # receiver still has PM in their inbox
bgneal@806 223 msg.sender_delete_date = now
bgneal@425 224 msg.save()
bgneal@425 225 elif msg.receiver == user:
bgneal@425 226 if msg.sender_delete_date is not None:
bgneal@425 227 # both parties deleted the message, we can delete it now
bgneal@425 228 msg.delete()
bgneal@425 229 else:
bgneal@806 230 # sender still has PM in their inbox
bgneal@806 231 msg.receiver_delete_date = now
bgneal@425 232 msg.save()
bgneal@425 233
bgneal@425 234
bgneal@806 235 @login_required
bgneal@806 236 @require_POST
bgneal@806 237 def delete(request):
bgneal@806 238 """
bgneal@806 239 Deletes the requested PM's. The user must be either a sender or receiver for
bgneal@806 240 this to work.
bgneal@806 241
bgneal@806 242 """
bgneal@806 243 pm_ids = request.POST.getlist('pm_ids')
bgneal@806 244 if pm_ids:
bgneal@806 245 _delete_pms(request.user, pm_ids)
bgneal@806 246
bgneal@806 247 # Figure out where to redirect to
bgneal@806 248 src = request.POST.get('src', 'inbox')
bgneal@806 249 try:
bgneal@806 250 page = int(request.POST.get('page', '1'))
bgneal@806 251 except ValueError:
bgneal@806 252 page = 1
bgneal@806 253
bgneal@806 254 view_name = 'messages-inbox' if src == inbox else 'messages-outbox'
bgneal@806 255 url = reverse(view_name) + '?page={}'.format(page)
bgneal@806 256 return redirect(url)
bgneal@806 257
bgneal@806 258
bgneal@425 259 def _undelete_msgs(user, msg_ids):
bgneal@425 260 """
bgneal@425 261 Attempts to "undelete" the messages given by the msg_ids list.
bgneal@425 262 This will only succeed if the user is either the sender or receiver.
bgneal@425 263
bgneal@425 264 """
bgneal@425 265 msg_ids = _only_integers(msg_ids)
bgneal@425 266 msgs = Message.objects.filter(id__in=msg_ids)
bgneal@425 267 for msg in msgs:
bgneal@425 268 if msg.sender == user:
bgneal@425 269 msg.sender_delete_date = None
bgneal@425 270 msg.save()
bgneal@425 271 elif msg.receiver == user:
bgneal@425 272 msg.receiver_delete_date = None
bgneal@425 273 msg.save()
bgneal@425 274
bgneal@425 275
bgneal@425 276 def bulk(request):
bgneal@425 277 """
bgneal@425 278 This view processes messages in bulk. Arrays of message ids are expected in
bgneal@425 279 the POST query dict: inbox_ids and outbox_ids will be deleted; trash_ids will
bgneal@425 280 be undeleted.
bgneal@425 281
bgneal@425 282 """
bgneal@425 283 if not request.user.is_authenticated():
bgneal@425 284 return HttpResponseForbidden()
bgneal@425 285 if request.method != 'POST':
bgneal@425 286 return HttpResponseNotAllowed(['POST'])
bgneal@425 287
bgneal@425 288 delete_ids = []
bgneal@425 289 if 'inbox_ids' in request.POST:
bgneal@425 290 delete_ids.extend(request.POST.getlist('inbox_ids'))
bgneal@425 291 if 'outbox_ids' in request.POST:
bgneal@425 292 delete_ids.extend(request.POST.getlist('outbox_ids'))
bgneal@425 293
bgneal@425 294 if len(delete_ids):
bgneal@425 295 _delete_msgs(request.user, delete_ids)
bgneal@425 296
bgneal@425 297 if 'trash_ids' in request.POST:
bgneal@425 298 _undelete_msgs(request.user, request.POST.getlist('trash_ids'))
bgneal@425 299
bgneal@425 300 return HttpResponse('');