annotate gpp/messages/views.py @ 479:32cec6cd8808

Refactor RateLimiter so that if Redis is not running, everything still runs normally (minus the rate limiting protection). My assumption that creating a Redis connection would throw an exception if Redis wasn't running was wrong. The exceptions actually occur when you issue a command. This is for #224.
author Brian Neal <bgneal@gmail.com>
date Sun, 25 Sep 2011 00:49:05 +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('');