annotate messages/views.py @ 673:92101013d5ac

For issue #28, add a quick search feature for new forum topics.
author Brian Neal <bgneal@gmail.com>
date Mon, 27 May 2013 15:04:52 -0500
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('');