Mercurial > public > sg101
changeset 429:d0f0800eef0c
Making the jquery tabbed version of the messages app the current version and removing the old. Also figured out how to dynamically update the base template's count of unread messages when messages are read.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Tue, 03 May 2011 02:56:58 +0000 |
parents | 77b3b01843b5 |
children | 9df368d9775d |
files | gpp/messages/models.py gpp/messages/static/js/box.js gpp/messages/static/js/tabbed_messages.js gpp/messages/templatetags/messages_tags.py gpp/messages/urls.py gpp/messages/views.py gpp/messages/views2.py gpp/templates/base.html gpp/templates/messages/base.html gpp/templates/messages/compose.html gpp/templates/messages/compose_tab.html gpp/templates/messages/inbox.html gpp/templates/messages/markdown.html gpp/templates/messages/options.html gpp/templates/messages/outbox.html gpp/templates/messages/tabbed_base.html gpp/templates/messages/trash.html gpp/templates/messages/unread_messages_tag.html gpp/templates/messages/view.html |
diffstat | 19 files changed, 312 insertions(+), 993 deletions(-) [+] |
line wrap: on
line diff
--- a/gpp/messages/models.py Sun May 01 02:11:48 2011 +0000 +++ b/gpp/messages/models.py Tue May 03 02:56:58 2011 +0000 @@ -67,10 +67,6 @@ self.html = site_markup(self.message) super(Message, self).save(*args, **kwargs) - @models.permalink - def get_absolute_url(self): - return ('messages.views.view', [str(self.id)]) - def __unicode__(self): return self.subject
--- a/gpp/messages/static/js/box.js Sun May 01 02:11:48 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -function messages_master_click() -{ - var state = document.getElementById('master_select').checked - for (i = 0; i < document.messages_box_form.length; ++i) - { - if (document.messages_box_form.elements[i].type == 'checkbox') - { - document.messages_box_form.elements[i].checked = state; - } - } -} - -function messages_set_master() -{ - var count = 0; - var numChkBoxes = 0; - for (i = 0; i < document.messages_box_form.length; ++i) - { - if (document.messages_box_form.elements[i].type == 'checkbox' && - document.messages_box_form.elements[i].id != 'master_select') - { - ++numChkBoxes; - if (document.messages_box_form.elements[i].checked) - { - ++count; - } - } - } - document.getElementById('master_select').checked = count == numChkBoxes; -} - -function messages_count_selected() -{ - var count = 0; - for (i = 0; i < document.messages_box_form.length; ++i) - { - if (document.messages_box_form.elements[i].type == 'checkbox' && - document.messages_box_form.elements[i].checked && - document.messages_box_form.elements[i].id != 'master_select') - { - ++count; - } - } - return count; -} - -function messages_confirm_delete() -{ - var count = messages_count_selected(); - if (count == 0) - { - alert("No messages selected."); - return false; - } - return confirm('Really delete selected messages?'); -} - -function messages_confirm_undelete() -{ - var count = messages_count_selected(); - if (count == 0) - { - alert("No messages selected."); - return false; - } - return confirm('Really undelete selected messages?'); -}
--- a/gpp/messages/static/js/tabbed_messages.js Sun May 01 02:11:48 2011 +0000 +++ b/gpp/messages/static/js/tabbed_messages.js Tue May 03 02:56:58 2011 +0000 @@ -55,13 +55,36 @@ var doReply = false; var selectedTab = 0; +function updateUnreadMsgText(n) +{ + var txt = ''; + if (n == 1) { + txt = "1 New Message"; + } + else if (n > 1) { + txt = n + " New Messages"; + } + else { + txt = "Private Messages"; + } + $('#unread_msg_text').html(txt); +} + function showMsg(link, id) { $msgDialog.msgId = id; // create a msgId attribute on the dialog var msg = msgCache[id]; // mark as read if necessary - if (username == msg.receiver) { + var $link = $(link); + + if (username == msg.receiver && $link.hasClass('unread')) { $(link).removeClass('unread'); + + // decrement count of unread messages in base template + if (unreadMsgCount > 0) + { + updateUnreadMsgText(--unreadMsgCount); + } } $msgDialog.html(msg.content); @@ -76,7 +99,7 @@ return; } $.ajax({ - url: '/messages/beta/message/', + url: '/messages/message/', type: 'POST', data: { msg_id : id @@ -95,7 +118,7 @@ function submitOptions(form) { $.ajax({ - url: '/messages/beta/options-tab/', + url: '/messages/options-tab/', type: 'POST', data: $(form).serialize(), dataType: 'html', @@ -112,7 +135,7 @@ function messageSubmit(form) { $.ajax({ - url: '/messages/beta/compose-tab/', + url: '/messages/compose-tab/', type: 'POST', data: $(form).serialize(), dataType: 'html', @@ -137,7 +160,7 @@ function bulkMsgAction(form, action) { if (confirm("Really " + action + " checked messages?")) { $.ajax({ - url: '/messages/beta/bulk/', + url: '/messages/bulk/', type: 'POST', data: $(form).serialize(), dataType: 'text',
--- a/gpp/messages/templatetags/messages_tags.py Sun May 01 02:11:48 2011 +0000 +++ b/gpp/messages/templatetags/messages_tags.py Tue May 03 02:56:58 2011 +0000 @@ -9,17 +9,7 @@ register = template.Library() -@register.simple_tag +@register.inclusion_tag('messages/unread_messages_tag.html') def unread_messages(user): - inbox_url = reverse('messages-inbox') unread_count = Message.objects.unread_count(user) - if unread_count == 0: - link_text = u"Private Messages" - elif unread_count == 1: - link_text = u"1 New Message" - else: - link_text = u"%s New Messages" % unread_count - return u'<a href="%s">%s</a>' % (inbox_url, link_text) - - -# vim: ts=4 sw=4 + return {'unread_count': unread_count}
--- a/gpp/messages/urls.py Sun May 01 02:11:48 2011 +0000 +++ b/gpp/messages/urls.py Tue May 03 02:56:58 2011 +0000 @@ -1,59 +1,35 @@ """urls for the Messages application""" from django.conf.urls.defaults import * -from django.views.generic import RedirectView - urlpatterns = patterns('messages.views', - url(r'^inbox/$', 'inbox', name='messages-inbox'), - url(r'^outbox/$', 'outbox', name='messages-outbox'), - url(r'^trash/$', 'trash', name='messages-trash'), - url(r'^view/(\d+)/$', 'view', name='messages-view'), - url(r'^reply/(\d+)/$', 'reply', name='messages-reply'), - url(r'^compose/$', 'compose', name='messages-compose'), - url(r'^compose/([\w.@+-]{1,30})/$', 'compose', name='messages-compose_to'), - url(r'^delete/$', 'delete_bulk', name='messages-delete_bulk'), - url(r'^delete/(\d+)/$', 'delete', name='messages-delete'), - url(r'^undelete/$', 'undelete_bulk', name='messages-undelete_bulk'), - url(r'^undelete/(\d+)/$', 'undelete', name='messages-undelete'), - url(r'^options/$', 'options', name='messages-options'), + url(r'^$', + 'index', + name='messages-index'), + url(r'^(inbox|compose|outbox|trash|options)/$', + 'index', + name='messages-index_named'), + url(r'^compose/([\w.@+-]{1,30})/$', + 'compose_to', + name='messages-compose_to'), + url(r'^inbox-tab/$', + 'inbox', + name='messages-inbox'), + url(r'^outbox-tab/$', + 'outbox', + name='messages-outbox'), + url(r'^trash-tab/$', + 'trash', + name='messages-trash'), + url(r'^message/$', + 'message', + name='messages-message'), + url(r'^options-tab/$', + 'options', + name='messages-options'), + url(r'^compose-tab/$', + 'compose', + name='messages-compose'), + url(r'^bulk/$', + 'bulk', + name='messages-bulk'), ) - -urlpatterns += patterns('', - (r'^$', RedirectView.as_view(url='inbox/')), -) - -urlpatterns += patterns('messages.views2', - url(r'^beta/$', - 'index', - name='messages-beta_index'), - url(r'^beta/(inbox|compose|outbox|trash|options)/$', - 'index', - name='messages-beta_index_named'), - url(r'^beta/compose/([\w.@+-]{1,30})/$', - 'compose_to', - name='messages-beta_compose_to'), - url(r'^beta/inbox-tab/$', - 'inbox', - name='messages-beta_inbox'), - url(r'^beta/outbox-tab/$', - 'outbox', - name='messages-beta_outbox'), - url(r'^beta/trash-tab/$', - 'trash', - name='messages-beta_trash'), - url(r'^beta/message/$', - 'message', - name='messages-beta_message'), - url(r'^beta/options-tab/$', - 'options', - name='messages-beta_options'), - url(r'^beta/compose-tab/$', - 'compose', - name='messages-beta_compose'), - url(r'^beta/compose-tab/([\w.@+-]{1,30})/$', - 'compose', - name='messages-beta_compose_to'), - url(r'^beta/bulk/$', - 'bulk', - name='messages-beta_bulk'), -)
--- a/gpp/messages/views.py Sun May 01 02:11:48 2011 +0000 +++ b/gpp/messages/views.py Tue May 03 02:56:58 2011 +0000 @@ -1,333 +1,306 @@ -"""Views for the messages application""" +""" +Views for the messages application. -import collections +""" import datetime -from django.shortcuts import render_to_response -from django.template import RequestContext +from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User +from django.contrib import messages +from django.core.paginator import Paginator, EmptyPage, InvalidPage +from django.core.urlresolvers import reverse +from django.http import HttpResponse +from django.http import HttpResponseForbidden +from django.http import HttpResponseNotAllowed from django.http import HttpResponseRedirect -from django.contrib.auth.decorators import login_required -from django.contrib import messages from django.shortcuts import get_object_or_404 -from django.core.urlresolvers import reverse -from django.http import Http404 -from django.views.decorators.http import require_POST +from django.shortcuts import render +import django.utils.simplejson as json -from messages.models import Message -from messages.models import Options -from messages.forms import ComposeForm -from messages.forms import OptionsForm -from messages.utils import reply_subject -from messages.utils import quote_message +from messages.models import Message, Options +from messages.forms import OptionsForm, ComposeForm +from messages.utils import reply_subject, quote_message -BOX_MAP = { - 'inbox': 'messages-inbox', - 'outbox': 'messages-outbox', - 'trash': 'messages-trash', +MSGS_PER_PAGE = 20 + +TAB_INDICES = { + 'inbox': 0, + 'compose': 1, + 'outbox': 2, + 'trash': 3, + 'options': 4, } -MSG_DELETED, MSG_TRASHED, MSG_ERROR = range(3) - -def box_redirect(request): - """ - Determines which box to redirect to by looking for a GET or - POST parameter. - """ - if request.method == 'GET': - box = request.GET.get('box', 'inbox') - else: - box = request.POST.get('box', 'inbox') - if BOX_MAP.has_key(box): - url = reverse(BOX_MAP[box]) - else: - url = reverse(BOX_MAP['inbox']) - return HttpResponseRedirect(url) +def _get_page(request): + try: + n = int(request.GET.get('page', '1')) + except ValueError: + n = 1 + return n @login_required -def inbox(request): - """Displays the inbox for the user making the request.""" - msgs = Message.objects.inbox(request.user) - return render_to_response('messages/inbox.html', { - 'msgs': msgs, - }, - context_instance = RequestContext(request)) +def index(request, tab=None): + """ + This function displays the base tabbed private messages view. + + """ + tab_index = TAB_INDICES[tab] if tab else 0 + return render(request, 'messages/tabbed_base.html', { + 'tab': tab_index, + 'unread_count': Message.objects.unread_count(request.user), + }) @login_required +def compose_to(request, receiver): + """ + This function displays the base tabbed private messages view, + and configures it to display the compose PM tab for the given + receiver. + + """ + user = get_object_or_404(User, username=receiver) + tab_index = TAB_INDICES['compose'] + return render(request, 'messages/tabbed_base.html', { + 'tab': tab_index, + 'receiver': receiver, + 'unread_count': Message.objects.unread_count(request.user), + }) + + +def inbox(request): + """ + Returns the inbox for the user. + + """ + if not request.user.is_authenticated(): + return HttpResponseForbidden() + + msg_list = Message.objects.inbox(request.user) + paginator = Paginator(msg_list, MSGS_PER_PAGE) + try: + msgs = paginator.page(_get_page(request)) + except EmptyPage, InvalidPage: + msgs = paginator.page(paginator.num_pages) + + return render(request, 'messages/inbox_tab.html', { + 'msgs': msgs, + 'url': reverse('messages-inbox'), + }) + + def outbox(request): - """Displays the outbox for the user making the request.""" - msgs = Message.objects.outbox(request.user) - return render_to_response('messages/outbox.html', { + """ + Returns the outbox for the user. + + """ + if not request.user.is_authenticated(): + return HttpResponseForbidden() + + msg_list = Message.objects.outbox(request.user) + paginator = Paginator(msg_list, MSGS_PER_PAGE) + try: + msgs = paginator.page(_get_page(request)) + except EmptyPage, InvalidPage: + msgs = paginator.page(paginator.num_pages) + + return render(request, 'messages/outbox_tab.html', { 'msgs': msgs, - }, - context_instance = RequestContext(request)) + 'url': reverse('messages-outbox'), + }) -@login_required def trash(request): - """Displays the trash for the user making the request.""" - msgs = Message.objects.trash(request.user) - return render_to_response('messages/trash.html', { + """ + Returns the trash for the user. + + """ + if not request.user.is_authenticated(): + return HttpResponseForbidden() + + msg_list = Message.objects.trash(request.user) + paginator = Paginator(msg_list, MSGS_PER_PAGE) + try: + msgs = paginator.page(_get_page(request)) + except EmptyPage, InvalidPage: + msgs = paginator.page(paginator.num_pages) + + return render(request, 'messages/trash_tab.html', { 'msgs': msgs, - }, - context_instance = RequestContext(request)) + 'url': reverse('messages-trash'), + }) -@login_required -def view(request, msg_id): +def message(request): """ - View a given message. Only the sender or receiver can see - the message. + This view function retrieves a message and returns it as a JSON object. + """ - msg = get_object_or_404(Message, pk=msg_id) + if not request.user.is_authenticated(): + return HttpResponseForbidden() + if request.method != 'POST': + return HttpResponseNotAllowed(['POST']) + + msg_id = request.POST.get('msg_id') + msg = get_object_or_404(Message.objects.select_related(), pk=msg_id) if msg.sender != request.user and msg.receiver != request.user: - raise Http404 + return HttpResponseForbidden() if msg.receiver == request.user and msg.read_date is None: msg.read_date = datetime.datetime.now() msg.save() - box = request.GET.get('box', None) + msg_dict = dict(subject=msg.subject, + sender=msg.sender.username, + receiver=msg.receiver.username, + content=msg.html, + re_subject=reply_subject(msg.subject), + re_content=quote_message(msg.sender.username, msg.send_date, + msg.message)) - return render_to_response('messages/view.html', { - 'box': box, - 'msg': msg, - 'is_deleted': msg.is_deleted(request.user), - }, - context_instance = RequestContext(request)) + result = json.dumps(msg_dict, ensure_ascii=False) + return HttpResponse(result, content_type='application/json') -@login_required -def reply(request, msg_id): +def options(request): """ - Process or prepare the compose form in order to reply - to a given message. + This view handles the displaying and changing of private message options. + """ - msg = get_object_or_404(Message, pk=msg_id) + if not request.user.is_authenticated(): + return HttpResponseForbidden() if request.method == "POST": - if request.POST.get('submit_button', 'Cancel') == 'Cancel': - return box_redirect(request) - compose_form = ComposeForm(request.user, request.POST) - if compose_form.is_valid(): - compose_form.save(sender=request.user, parent_msg=msg) - messages.success(request, 'Reply sent.') - return box_redirect(request) + options = Options.objects.for_user(request.user) + form = OptionsForm(request.POST, instance=options, prefix='opts') + if form.is_valid(): + form.save() + messages.success(request, 'Options saved.') else: - if msg.receiver == request.user: - receiver_name = msg.sender.username - else: - # replying to message in outbox - receiver_name = msg.receiver.username + options = Options.objects.for_user(request.user) + form = OptionsForm(instance=options, prefix='opts') - form_data = { - 'receiver': receiver_name, - 'subject': reply_subject(msg.subject), - 'message': quote_message(msg.sender, msg.send_date, msg.message), - 'box': request.GET.get('box', 'inbox'), - } + return render(request, 'messages/options_tab.html', { + 'form': form, + }) - compose_form = ComposeForm(request.user, initial=form_data) - return render_to_response('messages/compose.html', { - 'compose_form': compose_form, - }, - context_instance = RequestContext(request)) - - -@login_required def compose(request, receiver=None): """ - Process or prepare the compose form in order to create - a new message. + Process or prepare the compose form to create a new private message. + """ + if not request.user.is_authenticated(): + return HttpResponseForbidden() + if request.method == "POST": - if request.POST.get('submit_button', 'Cancel') == 'Cancel': - return HttpResponseRedirect(reverse('messages-inbox')) compose_form = ComposeForm(request.user, request.POST) if compose_form.is_valid(): compose_form.save(sender=request.user) messages.success(request, 'Message sent.') - return HttpResponseRedirect(reverse('messages-inbox')) + return HttpResponseRedirect(reverse('messages-index_named', args=['compose'])) else: if receiver is not None: - form_data = { - 'receiver': receiver, - } + form_data = {'receiver': receiver} compose_form = ComposeForm(request.user, initial=form_data) else: compose_form = ComposeForm(request.user) - return render_to_response('messages/compose.html', { + return render(request, 'messages/compose_tab.html', { 'compose_form': compose_form, - }, - context_instance = RequestContext(request)) + }) -def _delete_message(user, msg): +def _only_integers(slist): """ - Deletes a given message. The user must be either the sender or - receiver for this to succeed. - If both parties have deleted the message, it is deleted from the - database. If only one party has deleted the message, the message - is just sent to the trash. - - Returns MSG_DELETED, MSG_TRASHED, or MSG_ERROR to indicate what - action was performed. + Accepts a list of strings. Returns a list of integers consisting of only + those elements from the original list that could be converted to integers """ - if msg.sender == user: - if (msg.receiver_delete_date is not None or - msg.read_date is None): - # Both parties deleted the message or receiver hasn't read it yet, - # we can delete it now - msg.delete() - return MSG_DELETED + result = [] + for s in slist: + try: + n = int(s) + except ValueError: + pass else: - # receiver still has it in inbox - msg.sender_delete_date = datetime.datetime.now() + result.append(n) + return result + + +def _delete_msgs(user, msg_ids): + """ + Deletes the messages given by the list of msg_ids. For this to succeed, the + user has to be either the sender or receiver on each message. + + """ + msg_ids = _only_integers(msg_ids) + msgs = Message.objects.filter(id__in=msg_ids) + + for msg in msgs: + if msg.sender == user: + if (msg.receiver_delete_date is not None or + msg.read_date is None): + # Both parties deleted the message or receiver hasn't read it + # yet, we can delete it now + msg.delete() + else: + # receiver still has it in inbox + msg.sender_delete_date = datetime.datetime.now() + msg.save() + + elif msg.receiver == user: + if msg.sender_delete_date is not None: + # both parties deleted the message, we can delete it now + msg.delete() + else: + # sender still has it in the outbox + msg.receiver_delete_date = datetime.datetime.now() + msg.save() + + +def _undelete_msgs(user, msg_ids): + """ + Attempts to "undelete" the messages given by the msg_ids list. + This will only succeed if the user is either the sender or receiver. + + """ + msg_ids = _only_integers(msg_ids) + msgs = Message.objects.filter(id__in=msg_ids) + for msg in msgs: + if msg.sender == user: + msg.sender_delete_date = None msg.save() - return MSG_TRASHED + elif msg.receiver == user: + msg.receiver_delete_date = None + msg.save() - elif msg.receiver == user: - if msg.sender_delete_date is not None: - # both parties deleted the message, we can delete it now - msg.delete() - return MSG_DELETED - else: - # sender still has it in the outbox - msg.receiver_delete_date = datetime.datetime.now() - msg.save() - return MSG_TRASHED - return MSG_ERROR +def bulk(request): + """ + This view processes messages in bulk. Arrays of message ids are expected in + the POST query dict: inbox_ids and outbox_ids will be deleted; trash_ids will + be undeleted. + """ + if not request.user.is_authenticated(): + return HttpResponseForbidden() + if request.method != 'POST': + return HttpResponseNotAllowed(['POST']) -@login_required -@require_POST -def delete(request, msg_id): - """ - Deletes a given message. The user must be either the sender or - receiver for this to succeed. - """ - msg = get_object_or_404(Message, pk=msg_id) - result = _delete_message(request.user, msg) - if result == MSG_DELETED: - messages.success(request, 'Message deleted.') - elif result == MSG_TRASHED: - messages.success(request, 'Message sent to trash.') - else: - messages.error(request, 'Error deleting message.') + delete_ids = [] + if 'inbox_ids' in request.POST: + delete_ids.extend(request.POST.getlist('inbox_ids')) + if 'outbox_ids' in request.POST: + delete_ids.extend(request.POST.getlist('outbox_ids')) - return box_redirect(request) + if len(delete_ids): + _delete_msgs(request.user, delete_ids) + if 'trash_ids' in request.POST: + _undelete_msgs(request.user, request.POST.getlist('trash_ids')) -@login_required -def delete_bulk(request): - """ - Deletes messages in bulk. The message ID's to be deleted are expected - to be in the delete POST array. The user must be either the sender - or receiver for this to succeed. - """ - if request.method == "POST": - delete_ids = request.POST.getlist('delete_ids') - try: - delete_ids = [int(id) for id in delete_ids] - except ValueError: - raise Http404 - - msgs = Message.objects.filter(id__in=delete_ids) - - counts = collections.defaultdict(int) - for msg in msgs: - result = _delete_message(request.user, msg) - counts[result] += 1 - - if counts[MSG_DELETED]: - messages.success(request, 'Messages deleted: %d' % counts[MSG_DELETED]) - if counts[MSG_TRASHED]: - messages.success(request, 'Messages sent to trash: %d' % counts[MSG_TRASHED]) - if counts[MSG_ERROR]: - messages.error(request, 'Message errors: %d' % counts[MSG_ERROR]) - - return box_redirect(request) - - -@login_required -@require_POST -def undelete(request, msg_id): - """ - Undeletes a given message. The user must be either the sender or - receiver for this to succeed. - """ - msg = get_object_or_404(Message, pk=msg_id) - if msg.sender == request.user: - msg.sender_delete_date = None - elif msg.receiver == request.user: - msg.receiver_delete_date = None - else: - raise Http404 - msg.save() - messages.success(request, 'Message retrieved from the trash.') - - return box_redirect(request) - - -@login_required -def undelete_bulk(request): - """ - Undeletes messages in bulk. The message ID's to be deleted are expected - to be in the delete POST array. The user must be either the sender - or receiver for this to succeed. - """ - if request.method == "POST": - undelete_ids = request.POST.getlist('undelete_ids') - try: - undelete_ids = [int(id) for id in undelete_ids] - except ValueError: - raise Http404 - msgs = Message.objects.filter(id__in = undelete_ids) - for msg in msgs: - if msg.sender == request.user: - msg.sender_delete_date = None - msg.save() - elif msg.receiver == request.user: - msg.receiver_delete_date = None - msg.save() - messages.success(request, 'Messages retrieved from the trash.') - - return box_redirect(request) - - -@login_required -def options(request): - """ - View to display/change user options. - """ - if request.method == "POST": - if request.POST.get('submit_button', 'Cancel') == 'Cancel': - return HttpResponseRedirect(reverse('messages-inbox')) - options = Options.objects.for_user(request.user) - form = OptionsForm(request.POST, instance=options) - if form.is_valid(): - form.save() - messages.success(request, 'Options saved.') - return HttpResponseRedirect(reverse('messages-inbox')) - else: - try: - options = Options.objects.for_user(request.user) - except: - options = Options() - options.user = request.user - options.save() - - form = OptionsForm(instance=options) - - return render_to_response('messages/options.html', { - 'form': form, - }, - context_instance = RequestContext(request)) - + return HttpResponse('');
--- a/gpp/messages/views2.py Sun May 01 02:11:48 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,304 +0,0 @@ -""" -Views for the messages application. - -""" -import datetime - -from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User -from django.contrib import messages -from django.core.paginator import Paginator, EmptyPage, InvalidPage -from django.core.urlresolvers import reverse -from django.http import HttpResponse -from django.http import HttpResponseForbidden -from django.http import HttpResponseNotAllowed -from django.http import HttpResponseRedirect -from django.shortcuts import get_object_or_404 -from django.shortcuts import render -import django.utils.simplejson as json - -from messages.models import Message, Options -from messages.forms import OptionsForm, ComposeForm -from messages.utils import reply_subject, quote_message - - -MSGS_PER_PAGE = 20 - -TAB_INDICES = { - 'inbox': 0, - 'compose': 1, - 'outbox': 2, - 'trash': 3, - 'options': 4, -} - - -def _get_page(request): - try: - n = int(request.GET.get('page', '1')) - except ValueError: - n = 1 - return n - - -@login_required -def index(request, tab=None): - """ - This function displays the base tabbed private messages view. - - """ - tab_index = TAB_INDICES[tab] if tab else 0 - return render(request, 'messages/tabbed_base.html', { - 'tab': tab_index, - }) - - -@login_required -def compose_to(request, receiver): - """ - This function displays the base tabbed private messages view, - and configures it to display the compose PM tab for the given - receiver. - - """ - user = get_object_or_404(User, username=receiver) - tab_index = TAB_INDICES['compose'] - return render(request, 'messages/tabbed_base.html', { - 'tab': tab_index, - 'receiver': receiver, - }) - - -def inbox(request): - """ - Returns the inbox for the user. - - """ - if not request.user.is_authenticated(): - return HttpResponseForbidden() - - msg_list = Message.objects.inbox(request.user) - paginator = Paginator(msg_list, MSGS_PER_PAGE) - try: - msgs = paginator.page(_get_page(request)) - except EmptyPage, InvalidPage: - msgs = paginator.page(paginator.num_pages) - - return render(request, 'messages/inbox_tab.html', { - 'msgs': msgs, - 'url': reverse('messages-beta_inbox'), - }) - - -def outbox(request): - """ - Returns the outbox for the user. - - """ - if not request.user.is_authenticated(): - return HttpResponseForbidden() - - msg_list = Message.objects.outbox(request.user) - paginator = Paginator(msg_list, MSGS_PER_PAGE) - try: - msgs = paginator.page(_get_page(request)) - except EmptyPage, InvalidPage: - msgs = paginator.page(paginator.num_pages) - - return render(request, 'messages/outbox_tab.html', { - 'msgs': msgs, - 'url': reverse('messages-beta_outbox'), - }) - - -def trash(request): - """ - Returns the trash for the user. - - """ - if not request.user.is_authenticated(): - return HttpResponseForbidden() - - msg_list = Message.objects.trash(request.user) - paginator = Paginator(msg_list, MSGS_PER_PAGE) - try: - msgs = paginator.page(_get_page(request)) - except EmptyPage, InvalidPage: - msgs = paginator.page(paginator.num_pages) - - return render(request, 'messages/trash_tab.html', { - 'msgs': msgs, - 'url': reverse('messages-beta_trash'), - }) - - -def message(request): - """ - This view function retrieves a message and returns it as a JSON object. - - """ - if not request.user.is_authenticated(): - return HttpResponseForbidden() - if request.method != 'POST': - return HttpResponseNotAllowed(['POST']) - - msg_id = request.POST.get('msg_id') - msg = get_object_or_404(Message.objects.select_related(), pk=msg_id) - if msg.sender != request.user and msg.receiver != request.user: - return HttpResponseForbidden() - - if msg.receiver == request.user and msg.read_date is None: - msg.read_date = datetime.datetime.now() - msg.save() - - msg_dict = dict(subject=msg.subject, - sender=msg.sender.username, - receiver=msg.receiver.username, - content=msg.html, - re_subject=reply_subject(msg.subject), - re_content=quote_message(msg.sender.username, msg.send_date, - msg.message)) - - result = json.dumps(msg_dict, ensure_ascii=False) - return HttpResponse(result, content_type='application/json') - - -def options(request): - """ - This view handles the displaying and changing of private message options. - - """ - if not request.user.is_authenticated(): - return HttpResponseForbidden() - - if request.method == "POST": - options = Options.objects.for_user(request.user) - form = OptionsForm(request.POST, instance=options, prefix='opts') - if form.is_valid(): - form.save() - messages.success(request, 'Options saved.') - else: - options = Options.objects.for_user(request.user) - form = OptionsForm(instance=options, prefix='opts') - - return render(request, 'messages/options_tab.html', { - 'form': form, - }) - - -def compose(request, receiver=None): - """ - Process or prepare the compose form to create a new private message. - - """ - if not request.user.is_authenticated(): - return HttpResponseForbidden() - - if request.method == "POST": - compose_form = ComposeForm(request.user, request.POST) - if compose_form.is_valid(): - compose_form.save(sender=request.user) - messages.success(request, 'Message sent.') - return HttpResponseRedirect(reverse('messages-beta_index_named', args=['compose'])) - else: - if receiver is not None: - form_data = {'receiver': receiver} - compose_form = ComposeForm(request.user, initial=form_data) - else: - compose_form = ComposeForm(request.user) - - return render(request, 'messages/compose_tab.html', { - 'compose_form': compose_form, - }) - - -def _only_integers(slist): - """ - Accepts a list of strings. Returns a list of integers consisting of only - those elements from the original list that could be converted to integers - - """ - result = [] - for s in slist: - try: - n = int(s) - except ValueError: - pass - else: - result.append(n) - return result - - -def _delete_msgs(user, msg_ids): - """ - Deletes the messages given by the list of msg_ids. For this to succeed, the - user has to be either the sender or receiver on each message. - - """ - msg_ids = _only_integers(msg_ids) - msgs = Message.objects.filter(id__in=msg_ids) - - for msg in msgs: - if msg.sender == user: - if (msg.receiver_delete_date is not None or - msg.read_date is None): - # Both parties deleted the message or receiver hasn't read it - # yet, we can delete it now - msg.delete() - else: - # receiver still has it in inbox - msg.sender_delete_date = datetime.datetime.now() - msg.save() - - elif msg.receiver == user: - if msg.sender_delete_date is not None: - # both parties deleted the message, we can delete it now - msg.delete() - else: - # sender still has it in the outbox - msg.receiver_delete_date = datetime.datetime.now() - msg.save() - - -def _undelete_msgs(user, msg_ids): - """ - Attempts to "undelete" the messages given by the msg_ids list. - This will only succeed if the user is either the sender or receiver. - - """ - msg_ids = _only_integers(msg_ids) - msgs = Message.objects.filter(id__in=msg_ids) - for msg in msgs: - if msg.sender == user: - msg.sender_delete_date = None - msg.save() - elif msg.receiver == user: - msg.receiver_delete_date = None - msg.save() - - -def bulk(request): - """ - This view processes messages in bulk. Arrays of message ids are expected in - the POST query dict: inbox_ids and outbox_ids will be deleted; trash_ids will - be undeleted. - - """ - if not request.user.is_authenticated(): - return HttpResponseForbidden() - if request.method != 'POST': - return HttpResponseNotAllowed(['POST']) - - delete_ids = [] - if 'inbox_ids' in request.POST: - delete_ids.extend(request.POST.getlist('inbox_ids')) - if 'outbox_ids' in request.POST: - delete_ids.extend(request.POST.getlist('outbox_ids')) - - if len(delete_ids): - _delete_msgs(request.user, delete_ids) - - if 'trash_ids' in request.POST: - _undelete_msgs(request.user, request.POST.getlist('trash_ids')) - - return HttpResponse('');
--- a/gpp/templates/base.html Sun May 01 02:11:48 2011 +0000 +++ b/gpp/templates/base.html Tue May 03 02:56:58 2011 +0000 @@ -65,7 +65,7 @@ <li><a href="{% url 'irc-main' %}">IRC</a></li> <li><a href="{% url 'bio-member_list' type='user' %}">Member List</a></li> <li><a href="{% url 'membermap-index' %}">Member Map</a></li> - <li><a href="{% url 'messages-inbox' %}">Private Messages</a></li> + <li><a href="{% url 'messages-index' %}">Private Messages</a></li> <li><a href="{% url 'podcast-main' %}">Podcast</a></li> <li><a href="{% url 'polls-main' %}">Polls</a></li> <li><a href="{% url 'potd-view' %}">Photo of the Day</a></li>
--- a/gpp/templates/messages/base.html Sun May 01 02:11:48 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -{% extends 'base.html' %} -{% load url from future %} -{% block custom_css %} -<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/tab-nav.css" /> -<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/messages.css" /> -{% endblock %} -{% block content %} -<h2>Your Private Messages</h2> -<ul class="tab-nav"> -<li><a {% block compose-class %}{% endblock %} - href="{% url 'messages-compose' %}">Compose</a></li> -<li><a {% block inbox-class %}{% endblock %} - href="{% url 'messages-inbox' %}">Inbox</a></li> -<li><a {% block outbox-class %}{% endblock %} - href="{% url 'messages-outbox' %}">Outbox</a></li> -<li><a {% block trash-class %}{% endblock %} - href="{% url 'messages-trash' %}">Trash</a></li> -<li><a {% block options-class %}{% endblock %} - href="{% url 'messages-options' %}">Options</a></li> -</ul> -{% block messages_content %} -{% endblock %} -<br /> -{% endblock %}
--- a/gpp/templates/messages/compose.html Sun May 01 02:11:48 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -{% extends 'messages/base.html' %} -{% load core_tags %} -{% block title %}Messages: Compose{% endblock %} -{% block custom_js %} - {{ compose_form.media }} -{% endblock %} -{% block compose-class %}class="active"{% endblock %} -{% block messages_content %} -<h3>Compose Message</h3> -<form action="." method="post">{% csrf_token %} -<table> -{{ compose_form.as_table }} -<tr> - <td> </td> - <td> - {% comment_dialogs %} - <input type="submit" name="submit_button" value="Send" /> - <input type="submit" name="submit_button" value="Cancel" /> - </td> -</tr> -</table> -</form> -<br /> -{% endblock %}
--- a/gpp/templates/messages/compose_tab.html Sun May 01 02:11:48 2011 +0000 +++ b/gpp/templates/messages/compose_tab.html Tue May 03 02:56:58 2011 +0000 @@ -9,7 +9,7 @@ {% endfor %} </ul> {% endif %} -<form action="{% url 'messages-beta_compose' %}" method="post">{% csrf_token %} +<form action="{% url 'messages-compose' %}" method="post">{% csrf_token %} <table> {{ compose_form.as_table }} <tr>
--- a/gpp/templates/messages/inbox.html Sun May 01 02:11:48 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -{% extends 'messages/base.html' %} -{% load url from future %} -{% block title %}Messages: Inbox{% endblock %} -{% block custom_js %} - <script type="text/javascript" src="{{ STATIC_URL }}js/box.js"></script> -{% endblock %} -{% block inbox-class %}class="active"{% endblock %} -{% block messages_content %} -<h3>Inbox</h3> -{% if messages %} -<ul class="user-messages"> - {% for msg in messages %} - <li{% if msg.tags %} class="{{ msg.tags }}"{% endif %}>{{ msg }}</li> - {% endfor %} -</ul> -{% endif %} -{% if msgs %} - <form action="{% url 'messages-delete_bulk' %}" method="post" name="messages_box_form" - onsubmit="return messages_confirm_delete();">{% csrf_token %} - <table class="messages"> - <tr> - <th>From</th> - <th>Subject</th> - <th>Date</th> - <th><input type="checkbox" id="master_select" onclick="messages_master_click();" /></th> - </tr> - {% for msg in msgs %} - <tr> - <td><a href="{% url 'bio.views.view_profile' msg.sender.username %}"> - {{ msg.sender.username }}</a></td> - <td> - {% if msg.unread %}<strong>{% endif %} - {% if msg.replied_to %}<em>{% endif %} - <a href="{{ msg.get_absolute_url }}">{{ msg.subject }}</a> - {% if msg.replied_to %}</em>{% endif %} - {% if msg.unread %}</strong>{% endif %} - </td> - <td>{{ msg.send_date|date:"M j, Y g:i A" }}</td> - <td><input type="checkbox" name="delete_ids" value="{{ msg.id }}" - onclick="messages_set_master();" /></td> - </tr> - {% endfor %} - <tr><td colspan="4" align="center"><input type="submit" value="Delete Checked Messages" /></td></tr> - </table> - <input type="hidden" name="box" value="inbox" /> - </form> - <ul> - <li>Messages in <strong>bold</strong> are unread.</li> - <li>Messages in <em>italics</em> have been replied to.</li> - </ul> -{% else %} - <p><em>Your Inbox is empty.</em></p> -{% endif %} -{% endblock %}
--- a/gpp/templates/messages/markdown.html Sun May 01 02:11:48 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -{% load markup %} -{% load smiley_tags %} -{{ data|markdown:"safe"|smilify }}
--- a/gpp/templates/messages/options.html Sun May 01 02:11:48 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -{% extends 'messages/base.html' %} -{% block title %}Messages: Options{% endblock %} -{% block options-class %}active{% endblock %} -{% block messages_content %} -<h3>Private Message Options</h3> -{% if messages %} -<ul class="user-messages"> - {% for msg in messages %} - <li{% if msg.tags %} class="{{ msg.tags }}"{% endif %}>{{ msg }}</li> - {% endfor %} -</ul> -{% endif %} -<form action="." method="post">{% csrf_token %} -<table> -{{ form.as_table }} -<tr> - <td> </td> - <td> - <input type="submit" name="submit_button" value="Save" /> - <input type="submit" name="submit_button" value="Cancel" /> - </td> -</tr> -</table> -</form> -<br /> -{% endblock %}
--- a/gpp/templates/messages/outbox.html Sun May 01 02:11:48 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -{% extends 'messages/base.html' %} -{% load url from future %} -{% block title %}Messages: Outbox{% endblock %} -{% block custom_js %} - <script type="text/javascript" src="{{ STATIC_URL }}js/box.js"></script> -{% endblock %} -{% block outbox-class %}class="active"{% endblock %} -{% block messages_content %} -<h3>Outbox</h3> -{% if messages %} -<ul class="user-messages"> - {% for msg in messages %} - <li{% if msg.tags %} class="{{ msg.tags }}"{% endif %}>{{ msg }}</li> - {% endfor %} -</ul> -{% endif %} -{% if msgs %} - <form action="{% url 'messages-delete_bulk' %}" method="post" name="messages_box_form" - onsubmit="return messages_confirm_delete();">{% csrf_token %} - <table class="messages"> - <tr> - <th>To</th> - <th>Subject</th> - <th>Sent</th> - <th>Received</th> - <th><input type="checkbox" id="master_select" onclick="messages_master_click();" /></th> - </tr> - {% for msg in msgs %} - <tr> - <td><a href="{% url 'bio.views.view_profile' msg.receiver.username %}"> - {{ msg.receiver.username }}</a></td> - <td> - {% if msg.unread %}<strong>{% endif %} - {% if msg.replied_to %}<em>{% endif %} - <a href="{{ msg.get_absolute_url }}?box=outbox">{{ msg.subject }}</a> - {% if msg.replied_to %}</em>{% endif %} - {% if msg.unread %}</strong>{% endif %} - </td> - <td>{{ msg.send_date|date:"M j, Y g:i A" }}</td> - <td>{% if msg.unread %}<em>Unread</em>{% else %}{{ msg.read_date|date:"M j, Y g:i A" }}{% endif %}</td> - <td><input type="checkbox" name="delete_ids" value="{{ msg.id }}" - onclick="messages_set_master();" /></td> - </tr> - {% endfor %} - <tr><td colspan="5" align="center"><input type="submit" value="Delete Checked Messages" /></td></tr> - </table> - <input type="hidden" name="box" value="outbox" /> - </form> -{% else %} - <p><em>Your Outbox is empty.</em></p> -{% endif %} -{% endblock %}
--- a/gpp/templates/messages/tabbed_base.html Sun May 01 02:11:48 2011 +0000 +++ b/gpp/templates/messages/tabbed_base.html Tue May 03 02:56:58 2011 +0000 @@ -15,26 +15,21 @@ {% else %} var receiver = ""; {% endif %} + var unreadMsgCount = {{ unread_count }}; //]]> </script> <script type="text/javascript" src="{{ STATIC_URL }}js/tabbed_messages.js"></script> {% endblock %} {% block content %} -<h2>Your Private Messages (Beta)</h2> -<p> -This is an experimental version of the SG101 private message system. Don't worry, if this -isn't working for you, you can always go back to the -<a href="{% url 'messages-inbox' %}">current system</a>. Please leave any feedback on this -change in forums. Thanks! -</p> +<h2>Your Private Messages</h2> <div id="tabs"> <ul> - <li><a href="{% url 'messages-beta_inbox' %}">Inbox</a></li> - <li><a href="{% url 'messages-beta_compose' %}">Compose</a></li> - <li><a href="{% url 'messages-beta_outbox' %}">Outbox</a></li> - <li><a href="{% url 'messages-beta_trash' %}">Trash</a></li> - <li><a href="{% url 'messages-beta_options' %}">Options</a></li> + <li><a href="{% url 'messages-inbox' %}">Inbox</a></li> + <li><a href="{% url 'messages-compose' %}">Compose</a></li> + <li><a href="{% url 'messages-outbox' %}">Outbox</a></li> + <li><a href="{% url 'messages-trash' %}">Trash</a></li> + <li><a href="{% url 'messages-options' %}">Options</a></li> </ul> </div>
--- a/gpp/templates/messages/trash.html Sun May 01 02:11:48 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -{% extends 'messages/base.html' %} -{% load url from future %} -{% block title %}Messages: Trash{% endblock %} -{% block custom_js %} - <script type="text/javascript" src="{{ STATIC_URL }}js/box.js"></script> -{% endblock %} -{% block trash-class %}class="active"{% endblock %} -{% block messages_content %} -<h3>Trash</h3> -{% if messages %} -<ul class="user-messages"> - {% for msg in messages %} - <li{% if msg.tags %} class="{{ msg.tags }}"{% endif %}>{{ msg }}</li> - {% endfor %} -</ul> -{% endif %} -{% if msgs %} - <form action="{% url 'messages-undelete_bulk' %}" method="post" name="messages_box_form" - onsubmit="return messages_confirm_undelete();">{% csrf_token %} - <table class="messages"> - <tr> - <th>From</th> - <th>To</th> - <th>Subject</th> - <th>Date</th> - <th><input type="checkbox" id="master_select" onclick="messages_master_click();" /></th> - </tr> - {% for msg in msgs %} - <tr> - <td><a href="{% url 'bio.views.view_profile' msg.sender.username %}"> - {{ msg.sender.username }}</a></td> - <td><a href="{% url 'bio.views.view_profile' msg.receiver.username %}"> - {{ msg.receiver.username }}</a></td> - <td> - {% if msg.unread %}<strong>{% endif %} - {% if msg.replied_to %}<em>{% endif %} - <a href="{{ msg.get_absolute_url }}?box=trash">{{ msg.subject }}</a> - {% if msg.replied_to %}</em>{% endif %} - {% if msg.unread %}</strong>{% endif %} - </td> - <td>{{ msg.send_date|date:"M j, Y g:i:s A T" }}</td> - <td><input type="checkbox" name="undelete_ids" value="{{ msg.id }}" - onclick="messages_set_master();" /></td> - </tr> - {% endfor %} - <tr><td colspan="5" align="center"><input type="submit" value="Undelete Checked Messages" /></td></tr> - </table> - </form> -{% else %} - <p><em>Your Trash is empty.</em></p> -{% endif %} -{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gpp/templates/messages/unread_messages_tag.html Tue May 03 02:56:58 2011 +0000 @@ -0,0 +1,10 @@ +{% load url from future %} +{% if unread_count == 0 %} +<a href="{% url 'messages-index' %}"><span id="unread_msg_text">Private Messages</span></a> +{% endif %} +{% if unread_count == 1 %} +<a href="{% url 'messages-index' %}"><span id="unread_msg_text">1 New Message</span></a> +{% endif %} +{% if unread_count > 1 %} +<a href="{% url 'messages-index' %}"><span id="unread_msg_text">{{ unread_count }} New Messages</span></a> +{% endif %}
--- a/gpp/templates/messages/view.html Sun May 01 02:11:48 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -{% extends 'messages/base.html' %} -{% load url from future %} -{% block title %}Messages: {{ msg.subject }}{% endblock %} -{% block messages_content %} -<h3>Viewing Message: {{ msg.subject }}</h3> -<table class="message-header"> - <tr><th>Subject:</th><td>{{ msg.subject }}</td></tr> - <tr><th>From:</th><td><a href="{% url 'bio-view_profile' username=msg.sender.username %}"> - {{ msg.sender }}</a></td></tr> - <tr><th>To:</th><td><a href="{% url 'bio-view_profile' username=msg.receiver.username %}"> - {{ msg.receiver }}</a></td></tr> - <tr><th>Date Sent:</th><td>{{ msg.send_date|date:"F d, Y g:i:s A T" }}</td></tr> - <tr><th>Date Received:</th> - <td>{% if msg.unread %}<em>Unread</em>{% else %}{{ msg.read_date|date:"F d, Y g:i:s A T" }}{% endif %}</td></tr> -</table> - -<div class="message-body"> - {{ msg.html|safe }} -</div> -{% if msg.signature_attached %} -<div class="message-hr"></div> -<div class="message-signature"> - {{ msg.sender.get_profile.signature_html|safe }} -</div> -{% endif %} -{% if is_deleted %} -<form action="{% url 'messages-undelete' msg.id %}" method="post">{% csrf_token %} - {% if box %}<input type="hidden" name="box" value="{{ box }}" />{% endif %} - <input type="submit" value="Undelete" /> -</form> -{% else %} -<a href="{% url 'messages-reply' msg.id %}{% if box %}?box={{ box }}{% endif %}">Reply</a> | -<form action="{% url 'messages-delete' msg.id %}" method="post" class="messages-button">{% csrf_token %} - {% if box %}<input type="hidden" name="box" value="{{ box }}" />{% endif %} - <input type="submit" value="Delete" /> -</form> -{% endif %} -{% endblock %}