annotate gpp/messages/views.py @ 505:a5d11471d031

Refactor the logic in the rate limiter decorator. Check to see if the request was ajax, as the ajax view always returns 200. Have to decode the JSON response to see if an error occurred or not.
author Brian Neal <bgneal@gmail.com>
date Sat, 03 Dec 2011 19:13:38 +0000
parents 33d0c55e57a9
children 4b9970ad0edb
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@425 6
bgneal@425 7 from django.contrib.auth.decorators import login_required
bgneal@428 8 from django.contrib.auth.models import User
bgneal@436 9 from django.contrib import messages as django_messages
bgneal@425 10 from django.core.paginator import Paginator, EmptyPage, InvalidPage
bgneal@425 11 from django.core.urlresolvers import reverse
bgneal@425 12 from django.http import HttpResponse
bgneal@425 13 from django.http import HttpResponseForbidden
bgneal@425 14 from django.http import HttpResponseNotAllowed
bgneal@425 15 from django.http import HttpResponseRedirect
bgneal@425 16 from django.shortcuts import get_object_or_404
bgneal@425 17 from django.shortcuts import render
bgneal@425 18 import django.utils.simplejson as json
bgneal@425 19
bgneal@425 20 from messages.models import Message, Options
bgneal@425 21 from messages.forms import OptionsForm, ComposeForm
bgneal@425 22 from messages.utils import reply_subject, quote_message
bgneal@436 23 from messages import MSG_BOX_LIMIT
bgneal@425 24
bgneal@425 25
bgneal@436 26 MSGS_PER_PAGE = 20 # message pagination value
bgneal@425 27
bgneal@436 28 # This must match the jQuery UI tab control
bgneal@425 29 TAB_INDICES = {
bgneal@425 30 'inbox': 0,
bgneal@425 31 'compose': 1,
bgneal@425 32 'outbox': 2,
bgneal@425 33 'trash': 3,
bgneal@425 34 'options': 4,
bgneal@425 35 }
bgneal@425 36
bgneal@425 37
bgneal@425 38 def _get_page(request):
bgneal@425 39 try:
bgneal@425 40 n = int(request.GET.get('page', '1'))
bgneal@425 41 except ValueError:
bgneal@425 42 n = 1
bgneal@425 43 return n
bgneal@425 44
bgneal@425 45
bgneal@436 46 def _quota_check(box_name, count, request):
bgneal@436 47 """
bgneal@436 48 Checks the message box count against MSG_BOX_LIMIT.
bgneal@436 49 Emits a message to the user if the quota is exceeded.
bgneal@441 50
bgneal@436 51 """
bgneal@436 52 if count >= MSG_BOX_LIMIT:
bgneal@436 53 django_messages.warning(request,
bgneal@436 54 "Your %s is full. Please delete some messages." % box_name)
bgneal@436 55
bgneal@436 56
bgneal@425 57 @login_required
bgneal@425 58 def index(request, tab=None):
bgneal@425 59 """
bgneal@425 60 This function displays the base tabbed private messages view.
bgneal@425 61
bgneal@425 62 """
bgneal@425 63 tab_index = TAB_INDICES[tab] if tab else 0
bgneal@425 64 return render(request, 'messages/tabbed_base.html', {
bgneal@425 65 'tab': tab_index,
bgneal@429 66 'unread_count': Message.objects.unread_count(request.user),
bgneal@425 67 })
bgneal@425 68
bgneal@425 69
bgneal@428 70 @login_required
bgneal@428 71 def compose_to(request, receiver):
bgneal@428 72 """
bgneal@428 73 This function displays the base tabbed private messages view,
bgneal@428 74 and configures it to display the compose PM tab for the given
bgneal@428 75 receiver.
bgneal@428 76
bgneal@428 77 """
bgneal@428 78 user = get_object_or_404(User, username=receiver)
bgneal@428 79 tab_index = TAB_INDICES['compose']
bgneal@428 80 return render(request, 'messages/tabbed_base.html', {
bgneal@428 81 'tab': tab_index,
bgneal@428 82 'receiver': receiver,
bgneal@429 83 'unread_count': Message.objects.unread_count(request.user),
bgneal@428 84 })
bgneal@428 85
bgneal@428 86
bgneal@425 87 def inbox(request):
bgneal@425 88 """
bgneal@425 89 Returns the inbox for the user.
bgneal@425 90
bgneal@425 91 """
bgneal@425 92 if not request.user.is_authenticated():
bgneal@425 93 return HttpResponseForbidden()
bgneal@425 94
bgneal@425 95 msg_list = Message.objects.inbox(request.user)
bgneal@436 96 _quota_check('inbox', msg_list.count(), request)
bgneal@436 97
bgneal@425 98 paginator = Paginator(msg_list, MSGS_PER_PAGE)
bgneal@425 99 try:
bgneal@425 100 msgs = paginator.page(_get_page(request))
bgneal@425 101 except EmptyPage, InvalidPage:
bgneal@425 102 msgs = paginator.page(paginator.num_pages)
bgneal@425 103
bgneal@425 104 return render(request, 'messages/inbox_tab.html', {
bgneal@425 105 'msgs': msgs,
bgneal@429 106 'url': reverse('messages-inbox'),
bgneal@425 107 })
bgneal@425 108
bgneal@425 109
bgneal@425 110 def outbox(request):
bgneal@425 111 """
bgneal@425 112 Returns the outbox for the user.
bgneal@425 113
bgneal@425 114 """
bgneal@425 115 if not request.user.is_authenticated():
bgneal@425 116 return HttpResponseForbidden()
bgneal@425 117
bgneal@425 118 msg_list = Message.objects.outbox(request.user)
bgneal@436 119 _quota_check('outbox', msg_list.count(), request)
bgneal@436 120
bgneal@425 121 paginator = Paginator(msg_list, MSGS_PER_PAGE)
bgneal@425 122 try:
bgneal@425 123 msgs = paginator.page(_get_page(request))
bgneal@425 124 except EmptyPage, InvalidPage:
bgneal@425 125 msgs = paginator.page(paginator.num_pages)
bgneal@425 126
bgneal@425 127 return render(request, 'messages/outbox_tab.html', {
bgneal@425 128 'msgs': msgs,
bgneal@429 129 'url': reverse('messages-outbox'),
bgneal@425 130 })
bgneal@425 131
bgneal@425 132
bgneal@425 133 def trash(request):
bgneal@425 134 """
bgneal@425 135 Returns the trash for the user.
bgneal@425 136
bgneal@425 137 """
bgneal@425 138 if not request.user.is_authenticated():
bgneal@425 139 return HttpResponseForbidden()
bgneal@425 140
bgneal@425 141 msg_list = Message.objects.trash(request.user)
bgneal@425 142 paginator = Paginator(msg_list, MSGS_PER_PAGE)
bgneal@425 143 try:
bgneal@425 144 msgs = paginator.page(_get_page(request))
bgneal@425 145 except EmptyPage, InvalidPage:
bgneal@425 146 msgs = paginator.page(paginator.num_pages)
bgneal@425 147
bgneal@425 148 return render(request, 'messages/trash_tab.html', {
bgneal@425 149 'msgs': msgs,
bgneal@429 150 'url': reverse('messages-trash'),
bgneal@425 151 })
bgneal@425 152
bgneal@425 153
bgneal@425 154 def message(request):
bgneal@425 155 """
bgneal@425 156 This view function retrieves a message and returns it as a JSON object.
bgneal@425 157
bgneal@425 158 """
bgneal@425 159 if not request.user.is_authenticated():
bgneal@425 160 return HttpResponseForbidden()
bgneal@425 161 if request.method != 'POST':
bgneal@425 162 return HttpResponseNotAllowed(['POST'])
bgneal@425 163
bgneal@425 164 msg_id = request.POST.get('msg_id')
bgneal@425 165 msg = get_object_or_404(Message.objects.select_related(), pk=msg_id)
bgneal@425 166 if msg.sender != request.user and msg.receiver != request.user:
bgneal@425 167 return HttpResponseForbidden()
bgneal@425 168
bgneal@425 169 if msg.receiver == request.user and msg.read_date is None:
bgneal@425 170 msg.read_date = datetime.datetime.now()
bgneal@425 171 msg.save()
bgneal@425 172
bgneal@425 173 msg_dict = dict(subject=msg.subject,
bgneal@425 174 sender=msg.sender.username,
bgneal@425 175 receiver=msg.receiver.username,
bgneal@425 176 content=msg.html,
bgneal@425 177 re_subject=reply_subject(msg.subject),
bgneal@425 178 re_content=quote_message(msg.sender.username, msg.send_date,
bgneal@425 179 msg.message))
bgneal@425 180
bgneal@425 181 result = json.dumps(msg_dict, ensure_ascii=False)
bgneal@425 182 return HttpResponse(result, content_type='application/json')
bgneal@425 183
bgneal@425 184
bgneal@425 185 def options(request):
bgneal@425 186 """
bgneal@425 187 This view handles the displaying and changing of private message options.
bgneal@425 188
bgneal@425 189 """
bgneal@425 190 if not request.user.is_authenticated():
bgneal@425 191 return HttpResponseForbidden()
bgneal@425 192
bgneal@425 193 if request.method == "POST":
bgneal@425 194 options = Options.objects.for_user(request.user)
bgneal@425 195 form = OptionsForm(request.POST, instance=options, prefix='opts')
bgneal@425 196 if form.is_valid():
bgneal@425 197 form.save()
bgneal@436 198 django_messages.success(request, 'Options saved.')
bgneal@425 199 else:
bgneal@425 200 options = Options.objects.for_user(request.user)
bgneal@425 201 form = OptionsForm(instance=options, prefix='opts')
bgneal@425 202
bgneal@425 203 return render(request, 'messages/options_tab.html', {
bgneal@425 204 'form': form,
bgneal@425 205 })
bgneal@425 206
bgneal@425 207
bgneal@425 208 def compose(request, receiver=None):
bgneal@425 209 """
bgneal@425 210 Process or prepare the compose form to create a new private message.
bgneal@425 211
bgneal@425 212 """
bgneal@425 213 if not request.user.is_authenticated():
bgneal@425 214 return HttpResponseForbidden()
bgneal@425 215
bgneal@425 216 if request.method == "POST":
bgneal@425 217 compose_form = ComposeForm(request.user, request.POST)
bgneal@430 218
bgneal@430 219 # Is this a reply to another message?
bgneal@430 220 parent_msg_id = request.POST.get('reply_to')
bgneal@430 221 if parent_msg_id:
bgneal@430 222 parent_msg = get_object_or_404(Message, id=parent_msg_id)
bgneal@430 223 if (request.user != parent_msg.receiver and
bgneal@430 224 request.user != parent_msg.sender):
bgneal@430 225 return HttpResponseForbidden()
bgneal@430 226 else:
bgneal@430 227 parent_msg = None
bgneal@430 228
bgneal@425 229 if compose_form.is_valid():
bgneal@430 230 compose_form.save(parent_msg=parent_msg)
bgneal@436 231 django_messages.success(request, 'Message sent.')
bgneal@431 232 compose_form = ComposeForm(request.user)
bgneal@425 233 else:
bgneal@425 234 if receiver is not None:
bgneal@425 235 form_data = {'receiver': receiver}
bgneal@425 236 compose_form = ComposeForm(request.user, initial=form_data)
bgneal@425 237 else:
bgneal@425 238 compose_form = ComposeForm(request.user)
bgneal@425 239
bgneal@436 240 _quota_check('outbox', Message.objects.outbox(request.user).count(), request)
bgneal@436 241
bgneal@425 242 return render(request, 'messages/compose_tab.html', {
bgneal@425 243 'compose_form': compose_form,
bgneal@425 244 })
bgneal@425 245
bgneal@425 246
bgneal@425 247 def _only_integers(slist):
bgneal@425 248 """
bgneal@425 249 Accepts a list of strings. Returns a list of integers consisting of only
bgneal@425 250 those elements from the original list that could be converted to integers
bgneal@425 251
bgneal@425 252 """
bgneal@425 253 result = []
bgneal@425 254 for s in slist:
bgneal@425 255 try:
bgneal@425 256 n = int(s)
bgneal@425 257 except ValueError:
bgneal@425 258 pass
bgneal@425 259 else:
bgneal@425 260 result.append(n)
bgneal@425 261 return result
bgneal@425 262
bgneal@425 263
bgneal@425 264 def _delete_msgs(user, msg_ids):
bgneal@425 265 """
bgneal@425 266 Deletes the messages given by the list of msg_ids. For this to succeed, the
bgneal@425 267 user has to be either the sender or receiver on each message.
bgneal@425 268
bgneal@425 269 """
bgneal@425 270 msg_ids = _only_integers(msg_ids)
bgneal@425 271 msgs = Message.objects.filter(id__in=msg_ids)
bgneal@425 272
bgneal@425 273 for msg in msgs:
bgneal@425 274 if msg.sender == user:
bgneal@425 275 if (msg.receiver_delete_date is not None or
bgneal@425 276 msg.read_date is None):
bgneal@425 277 # Both parties deleted the message or receiver hasn't read it
bgneal@425 278 # yet, we can delete it now
bgneal@425 279 msg.delete()
bgneal@425 280 else:
bgneal@425 281 # receiver still has it in inbox
bgneal@425 282 msg.sender_delete_date = datetime.datetime.now()
bgneal@425 283 msg.save()
bgneal@425 284
bgneal@425 285 elif msg.receiver == user:
bgneal@425 286 if msg.sender_delete_date is not None:
bgneal@425 287 # both parties deleted the message, we can delete it now
bgneal@425 288 msg.delete()
bgneal@425 289 else:
bgneal@425 290 # sender still has it in the outbox
bgneal@425 291 msg.receiver_delete_date = datetime.datetime.now()
bgneal@425 292 msg.save()
bgneal@425 293
bgneal@425 294
bgneal@425 295 def _undelete_msgs(user, msg_ids):
bgneal@425 296 """
bgneal@425 297 Attempts to "undelete" the messages given by the msg_ids list.
bgneal@425 298 This will only succeed if the user is either the sender or receiver.
bgneal@425 299
bgneal@425 300 """
bgneal@425 301 msg_ids = _only_integers(msg_ids)
bgneal@425 302 msgs = Message.objects.filter(id__in=msg_ids)
bgneal@425 303 for msg in msgs:
bgneal@425 304 if msg.sender == user:
bgneal@425 305 msg.sender_delete_date = None
bgneal@425 306 msg.save()
bgneal@425 307 elif msg.receiver == user:
bgneal@425 308 msg.receiver_delete_date = None
bgneal@425 309 msg.save()
bgneal@425 310
bgneal@425 311
bgneal@425 312 def bulk(request):
bgneal@425 313 """
bgneal@425 314 This view processes messages in bulk. Arrays of message ids are expected in
bgneal@425 315 the POST query dict: inbox_ids and outbox_ids will be deleted; trash_ids will
bgneal@425 316 be undeleted.
bgneal@425 317
bgneal@425 318 """
bgneal@425 319 if not request.user.is_authenticated():
bgneal@425 320 return HttpResponseForbidden()
bgneal@425 321 if request.method != 'POST':
bgneal@425 322 return HttpResponseNotAllowed(['POST'])
bgneal@425 323
bgneal@425 324 delete_ids = []
bgneal@425 325 if 'inbox_ids' in request.POST:
bgneal@425 326 delete_ids.extend(request.POST.getlist('inbox_ids'))
bgneal@425 327 if 'outbox_ids' in request.POST:
bgneal@425 328 delete_ids.extend(request.POST.getlist('outbox_ids'))
bgneal@425 329
bgneal@425 330 if len(delete_ids):
bgneal@425 331 _delete_msgs(request.user, delete_ids)
bgneal@425 332
bgneal@425 333 if 'trash_ids' in request.POST:
bgneal@425 334 _undelete_msgs(request.user, request.POST.getlist('trash_ids'))
bgneal@425 335
bgneal@425 336 return HttpResponse('');