annotate messages/views.py @ 887:9a15f7c27526

Actually save model object upon change. This commit was tested on the comments model. Additional logging added. Added check for Markdown image references. Added TODOs after observing behavior on comments.
author Brian Neal <bgneal@gmail.com>
date Tue, 03 Feb 2015 21:09:44 -0600
parents cf486a8e8b43
children 79a71b9d0a2a
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@806 8 from django.views.decorators.http import require_POST
bgneal@436 9 from django.contrib import messages as django_messages
bgneal@801 10 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
bgneal@425 11 from django.core.urlresolvers import reverse
bgneal@425 12 from django.shortcuts import get_object_or_404
bgneal@805 13 from django.shortcuts import render, redirect
bgneal@818 14 from django.db.models import Q
bgneal@818 15 from django.template.loader import render_to_string
bgneal@818 16 from django.contrib.sites.models import Site
bgneal@818 17 from django.conf import settings
bgneal@425 18
bgneal@810 19 from messages.models import Flag, Message, Options
bgneal@810 20 from messages.forms import OptionsForm, ComposeForm, ReportForm
bgneal@566 21 from messages.utils import reply_subject
bgneal@436 22 from messages import MSG_BOX_LIMIT
bgneal@818 23 from core.functions import email_admins, quote_message, send_mail
bgneal@425 24
bgneal@425 25
bgneal@436 26 MSGS_PER_PAGE = 20 # message pagination value
bgneal@818 27 PM_EMAIL_MAX = 100 # max messages we will send via email
bgneal@425 28
bgneal@813 29 REPORT_SUBJECT = 'A user has flagged a private message'
bgneal@813 30 REPORT_MSG = """Hello,
bgneal@813 31
bgneal@813 32 A user has flagged a private message for review.
bgneal@813 33 """
bgneal@813 34
bgneal@425 35
bgneal@436 36 def _quota_check(box_name, count, request):
bgneal@436 37 """
bgneal@436 38 Checks the message box count against MSG_BOX_LIMIT.
bgneal@436 39 Emits a message to the user if the quota is exceeded.
bgneal@441 40
bgneal@568 41 Returns the percent used as an integer between 0-100.
bgneal@568 42
bgneal@436 43 """
bgneal@436 44 if count >= MSG_BOX_LIMIT:
bgneal@436 45 django_messages.warning(request,
bgneal@436 46 "Your %s is full. Please delete some messages." % box_name)
bgneal@436 47
bgneal@568 48 return 100 * count / MSG_BOX_LIMIT
bgneal@568 49
bgneal@436 50
bgneal@801 51 def _get_page(request, qs):
bgneal@801 52 """Paginates the given queryset and returns a page object"""
bgneal@801 53 paginator = Paginator(qs, MSGS_PER_PAGE)
bgneal@801 54 try:
bgneal@801 55 page = paginator.page(request.GET.get('page', '1'))
bgneal@801 56 except PageNotAnInteger:
bgneal@801 57 page = paginator.page(1)
bgneal@801 58 except EmptyPage:
bgneal@801 59 page = paginator.page(paginator.num_pages)
bgneal@801 60 return page
bgneal@801 61
bgneal@801 62
bgneal@425 63 @login_required
bgneal@801 64 def inbox(request):
bgneal@425 65
bgneal@801 66 msg_list = Message.objects.inbox(request.user)
bgneal@801 67 msg_count = msg_list.count()
bgneal@801 68 pct_used = _quota_check('inbox', msg_count, request)
bgneal@801 69
bgneal@801 70 page = _get_page(request, msg_list)
bgneal@801 71
bgneal@801 72 return render(request, 'messages/inbox.html', {
bgneal@801 73 'tab': 'inbox',
bgneal@801 74 'page': page,
bgneal@801 75 'inbox_pct': pct_used,
bgneal@801 76 'outbox_pct': None,
bgneal@801 77 })
bgneal@801 78
bgneal@801 79
bgneal@801 80 @login_required
bgneal@801 81 def outbox(request):
bgneal@801 82
bgneal@801 83 msg_list = Message.objects.outbox(request.user)
bgneal@801 84 msg_count = msg_list.count()
bgneal@801 85 pct_used = _quota_check('outbox', msg_count, request)
bgneal@801 86
bgneal@801 87 page = _get_page(request, msg_list)
bgneal@801 88
bgneal@801 89 return render(request, 'messages/outbox.html', {
bgneal@801 90 'tab': 'outbox',
bgneal@801 91 'page': page,
bgneal@801 92 'inbox_pct': None,
bgneal@801 93 'outbox_pct': pct_used,
bgneal@425 94 })
bgneal@425 95
bgneal@425 96
bgneal@428 97 @login_required
bgneal@802 98 def trash(request):
bgneal@802 99
bgneal@802 100 msg_list = Message.objects.trash(request.user)
bgneal@802 101
bgneal@802 102 page = _get_page(request, msg_list)
bgneal@802 103
bgneal@802 104 return render(request, 'messages/trash.html', {
bgneal@802 105 'tab': 'trash',
bgneal@802 106 'page': page,
bgneal@802 107 'inbox_pct': None,
bgneal@802 108 'outbox_pct': None,
bgneal@802 109 })
bgneal@802 110
bgneal@802 111
bgneal@802 112 @login_required
bgneal@803 113 def options(request):
bgneal@803 114 """
bgneal@803 115 This view handles the displaying and changing of private message options.
bgneal@803 116
bgneal@803 117 """
bgneal@803 118 if request.method == 'POST':
bgneal@803 119 options = Options.objects.for_user(request.user)
bgneal@803 120 form = OptionsForm(request.POST, instance=options, prefix='opts')
bgneal@803 121 if form.is_valid():
bgneal@803 122 form.save()
bgneal@803 123 django_messages.success(request, 'Options saved.')
bgneal@812 124 return redirect('messages-options')
bgneal@803 125 else:
bgneal@803 126 options = Options.objects.for_user(request.user)
bgneal@803 127 form = OptionsForm(instance=options, prefix='opts')
bgneal@803 128
bgneal@803 129 return render(request, 'messages/options.html', {
bgneal@803 130 'tab': 'options',
bgneal@803 131 'form': form,
bgneal@803 132 })
bgneal@803 133
bgneal@803 134
bgneal@803 135 @login_required
bgneal@804 136 def compose(request):
bgneal@428 137 """
bgneal@804 138 Process or prepare the compose form to create a new private message.
bgneal@428 139
bgneal@428 140 """
bgneal@804 141 if request.method == 'POST':
bgneal@804 142 compose_form = ComposeForm(request.user, request.POST)
bgneal@804 143 if compose_form.is_valid():
bgneal@804 144 compose_form.save()
bgneal@804 145 django_messages.success(request, 'Message sent.')
bgneal@804 146 compose_form = ComposeForm(request.user)
bgneal@804 147 else:
bgneal@804 148 receiver = request.GET.get('to')
bgneal@804 149 if receiver:
bgneal@804 150 form_data = {'receiver': receiver}
bgneal@804 151 compose_form = ComposeForm(request.user, initial=form_data)
bgneal@804 152 else:
bgneal@804 153 compose_form = ComposeForm(request.user)
bgneal@804 154
bgneal@804 155 _quota_check('outbox', Message.objects.outbox(request.user).count(), request)
bgneal@804 156
bgneal@804 157 return render(request, 'messages/compose.html', {
bgneal@804 158 'tab': 'compose',
bgneal@804 159 'compose_form': compose_form,
bgneal@428 160 })
bgneal@428 161
bgneal@428 162
bgneal@804 163 @login_required
bgneal@804 164 def view(request, msg_id):
bgneal@425 165 """
bgneal@805 166 This view function displays a private message for reading to the user. If
bgneal@805 167 the user is a recipient of the message, a reply can be composed and sent.
bgneal@425 168
bgneal@425 169 """
bgneal@812 170 msg = get_object_or_404(Message.objects.select_related(), pk=msg_id)
bgneal@812 171
bgneal@805 172 if request.method == 'POST':
bgneal@805 173 form = ComposeForm(request.user, request.POST)
bgneal@805 174 if form.is_valid():
bgneal@805 175 form.save()
bgneal@805 176 django_messages.success(request, 'Reply sent.')
bgneal@805 177 return redirect('messages-inbox')
bgneal@805 178 else:
bgneal@805 179 if msg.sender != request.user and msg.receiver != request.user:
bgneal@805 180 django_messages.error(request,
bgneal@805 181 "You don't have permission to read that message.")
bgneal@805 182 return redirect('messages-inbox')
bgneal@425 183
bgneal@805 184 initial_data = {
bgneal@805 185 'receiver': msg.sender.username,
bgneal@805 186 'subject': reply_subject(msg.subject),
bgneal@805 187 'message': quote_message(msg.sender.username, msg.message),
bgneal@805 188 'parent_id': msg.pk,
bgneal@805 189 }
bgneal@425 190
bgneal@805 191 if msg.receiver == request.user:
bgneal@805 192 if msg.read_date is None:
bgneal@805 193 msg.read_date = datetime.datetime.now()
bgneal@805 194 msg.save()
bgneal@805 195 else:
bgneal@805 196 initial_data['receiver'] = msg.receiver.username
bgneal@425 197
bgneal@805 198 form = ComposeForm(request.user, initial=initial_data)
bgneal@425 199
bgneal@810 200 try:
bgneal@810 201 msg_flag = msg.flag
bgneal@810 202 except Flag.DoesNotExist:
bgneal@810 203 msg_flag = None
bgneal@810 204
bgneal@805 205 return render(request, 'messages/view_message.html', {
bgneal@805 206 'msg': msg,
bgneal@805 207 'form': form,
bgneal@810 208 'msg_flag': msg_flag,
bgneal@805 209 })
bgneal@425 210
bgneal@425 211
bgneal@818 212 @login_required
bgneal@818 213 @require_POST
bgneal@818 214 def bulk(request):
bgneal@818 215 """
bgneal@818 216 Perform bulk action on a list of PMs.
bgneal@818 217
bgneal@818 218 """
bgneal@818 219 pm_ids = request.POST.getlist('pm_ids')
bgneal@818 220
bgneal@818 221 # Figure out what action to perform
bgneal@818 222 action = None
bgneal@818 223 action_val = request.POST.get('action', '')
bgneal@818 224 if action_val.startswith('Delete'):
bgneal@818 225 action = _delete_pms
bgneal@818 226 elif action_val.startswith('Email'):
bgneal@818 227 action = _email_pms
bgneal@818 228
bgneal@818 229 if pm_ids and action:
bgneal@818 230 action(request, pm_ids)
bgneal@818 231
bgneal@818 232 # Figure out where to redirect to
bgneal@818 233 src = request.POST.get('src', 'inbox')
bgneal@818 234 try:
bgneal@818 235 page = int(request.POST.get('page', '1'))
bgneal@818 236 except ValueError:
bgneal@818 237 page = 1
bgneal@818 238
bgneal@818 239 view_name = 'messages-inbox' if src == 'inbox' else 'messages-outbox'
bgneal@818 240 url = reverse(view_name) + '?page={}'.format(page)
bgneal@818 241 return redirect(url)
bgneal@818 242
bgneal@818 243
bgneal@818 244 def _delete_pms(request, pm_ids):
bgneal@425 245 """
bgneal@806 246 Process the request to delete the list of PM ids by user.
bgneal@425 247
bgneal@807 248 Returns the number of PM's deleted.
bgneal@807 249
bgneal@425 250 """
bgneal@818 251 user = request.user
bgneal@806 252 msgs = Message.objects.filter(id__in=pm_ids)
bgneal@806 253 now = datetime.datetime.now()
bgneal@425 254
bgneal@807 255 count = 0
bgneal@425 256 for msg in msgs:
bgneal@425 257 if msg.sender == user:
bgneal@806 258 if msg.receiver_delete_date is not None or msg.read_date is None:
bgneal@425 259 # Both parties deleted the message or receiver hasn't read it
bgneal@425 260 # yet, we can delete it now
bgneal@425 261 msg.delete()
bgneal@425 262 else:
bgneal@806 263 # receiver still has PM in their inbox
bgneal@806 264 msg.sender_delete_date = now
bgneal@425 265 msg.save()
bgneal@807 266 count += 1
bgneal@425 267 elif msg.receiver == user:
bgneal@425 268 if msg.sender_delete_date is not None:
bgneal@425 269 # both parties deleted the message, we can delete it now
bgneal@425 270 msg.delete()
bgneal@425 271 else:
bgneal@806 272 # sender still has PM in their inbox
bgneal@806 273 msg.receiver_delete_date = now
bgneal@425 274 msg.save()
bgneal@807 275 count += 1
bgneal@807 276
bgneal@818 277 msg = '{} message{} deleted.'.format(count, '' if count == 1 else 's')
bgneal@818 278 if count > 0:
bgneal@807 279 django_messages.success(request, msg)
bgneal@818 280 else:
bgneal@818 281 django_messages.error(request, msg)
bgneal@806 282
bgneal@806 283
bgneal@807 284 def _undelete_pms(user, msg_ids):
bgneal@425 285 """
bgneal@425 286 Attempts to "undelete" the messages given by the msg_ids list.
bgneal@425 287 This will only succeed if the user is either the sender or receiver.
bgneal@425 288
bgneal@807 289 Returns the number of PM's undeleted.
bgneal@807 290
bgneal@425 291 """
bgneal@425 292 msgs = Message.objects.filter(id__in=msg_ids)
bgneal@807 293 count = 0
bgneal@425 294 for msg in msgs:
bgneal@425 295 if msg.sender == user:
bgneal@425 296 msg.sender_delete_date = None
bgneal@425 297 msg.save()
bgneal@807 298 count += 1
bgneal@425 299 elif msg.receiver == user:
bgneal@425 300 msg.receiver_delete_date = None
bgneal@425 301 msg.save()
bgneal@807 302 count += 1
bgneal@807 303 return count
bgneal@425 304
bgneal@425 305
bgneal@807 306 @login_required
bgneal@807 307 @require_POST
bgneal@807 308 def undelete(request):
bgneal@425 309 """
bgneal@807 310 Undeletes the requested PM's. The user must be either a sender or receiver for
bgneal@807 311 this to work.
bgneal@425 312
bgneal@425 313 """
bgneal@807 314 pm_ids = request.POST.getlist('pm_ids')
bgneal@807 315 if pm_ids:
bgneal@807 316 count = _undelete_pms(request.user, pm_ids)
bgneal@807 317 msg = '{} message{} undeleted.'.format(count, '' if count == 1 else 's')
bgneal@807 318 django_messages.success(request, msg)
bgneal@425 319
bgneal@807 320 # Figure out where to redirect to
bgneal@807 321 try:
bgneal@807 322 page = int(request.POST.get('page', '1'))
bgneal@807 323 except ValueError:
bgneal@807 324 page = 1
bgneal@425 325
bgneal@807 326 url = reverse('messages-trash') + '?page={}'.format(page)
bgneal@807 327 return redirect(url)
bgneal@810 328
bgneal@810 329
bgneal@810 330 @login_required
bgneal@810 331 def report(request, msg_id):
bgneal@810 332 """This view is for reporting a PM as spam or abuse.
bgneal@810 333
bgneal@810 334 """
bgneal@810 335 msg = get_object_or_404(Message.objects.select_related(), pk=msg_id)
bgneal@810 336 if msg.receiver != request.user:
bgneal@810 337 django_messages.error(request, "You can't report this message.")
bgneal@810 338 return redirect('messages-inbox')
bgneal@810 339 try:
bgneal@810 340 msg.flag
bgneal@810 341 except Flag.DoesNotExist:
bgneal@810 342 pass
bgneal@810 343 else:
bgneal@810 344 django_messages.error(request, "This message has already been reported.")
bgneal@810 345 return redirect('messages-inbox')
bgneal@810 346
bgneal@810 347 if request.method == 'POST':
bgneal@810 348 form = ReportForm(request.POST)
bgneal@810 349 if form.is_valid():
bgneal@810 350 flag = form.save(commit=False)
bgneal@810 351 flag.message = msg
bgneal@810 352 flag.save()
bgneal@813 353 email_admins(REPORT_SUBJECT, REPORT_MSG)
bgneal@810 354 django_messages.success(request,
bgneal@810 355 'Message reported. An admin will be notified. Thank you.')
bgneal@810 356 return redirect('messages-inbox')
bgneal@810 357 else:
bgneal@810 358 form = ReportForm()
bgneal@810 359
bgneal@810 360 return render(request, 'messages/report_message.html', {
bgneal@810 361 'msg': msg,
bgneal@810 362 'form': form,
bgneal@810 363 })
bgneal@818 364
bgneal@818 365
bgneal@818 366 def _email_pms(request, msg_ids):
bgneal@818 367 """
bgneal@818 368 Attempts to email the messages given by the msg_ids list to the user.
bgneal@818 369 This will only succeed if the user is either the sender or receiver.
bgneal@818 370
bgneal@818 371 Returns the number of PM's sent.
bgneal@818 372
bgneal@818 373 """
bgneal@818 374 # Does the user have an email address?
bgneal@818 375 user = request.user
bgneal@818 376 email_addr = user.email
bgneal@818 377 if not email_addr:
bgneal@818 378 return 0
bgneal@818 379
bgneal@818 380 msgs = Message.objects.filter(
bgneal@818 381 Q(sender=user) | Q(receiver=user),
bgneal@818 382 id__in=msg_ids).order_by('pk')[:PM_EMAIL_MAX]
bgneal@818 383
bgneal@818 384 count = len(msgs)
bgneal@818 385 if count == 0:
bgneal@818 386 return 0
bgneal@818 387
bgneal@818 388 site = Site.objects.get_current()
bgneal@818 389 admin_email = settings.ADMINS[0][1]
bgneal@818 390
bgneal@818 391 subject = 'Private messages from {} - {}'.format(site.domain,
bgneal@818 392 msgs[0].subject)
bgneal@818 393 if count > 1:
bgneal@818 394 subject += ' (et al.)'
bgneal@818 395 from_email = '{}@{}'.format(settings.GPP_NO_REPLY_EMAIL, site.domain)
bgneal@818 396 msg_body = render_to_string('messages/pm_email.txt', {
bgneal@818 397 'site': site,
bgneal@818 398 'user': user,
bgneal@818 399 'msgs': msgs,
bgneal@818 400 'admin_email': admin_email,
bgneal@818 401 })
bgneal@818 402 send_mail(subject, msg_body, from_email, [email_addr], defer=False)
bgneal@818 403
bgneal@818 404 msg = '{} message{} sent to email.'.format(count, '' if count == 1 else 's')
bgneal@818 405 if count > 0:
bgneal@818 406 django_messages.success(request, msg)
bgneal@818 407 else:
bgneal@818 408 django_messages.error(request, msg)