annotate messages/views.py @ 803:b3eeaefc39a8

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