annotate messages/views.py @ 631:f36d1a168be7

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