diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-11-18 04:27:59 -0300 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-11-18 04:27:59 -0300 |
commit | 93019fdd50974f3034b944e74953c4380319f65c (patch) | |
tree | 7f3f30043512c385eaa0acbd1a968950bb913446 /group_messaging | |
parent | cfc7add4e295ab6efefe048ea0b09676cfb314be (diff) | |
parent | 2214ac955d9c7e38e8078a35998fa33b82d89df3 (diff) | |
download | askbot-93019fdd50974f3034b944e74953c4380319f65c.tar.gz askbot-93019fdd50974f3034b944e74953c4380319f65c.tar.bz2 askbot-93019fdd50974f3034b944e74953c4380319f65c.zip |
merged with the group-messaging branch
Diffstat (limited to 'group_messaging')
-rw-r--r-- | group_messaging/__init__.py | 3 | ||||
-rw-r--r-- | group_messaging/models.py | 239 | ||||
-rw-r--r-- | group_messaging/tests.py | 277 | ||||
-rw-r--r-- | group_messaging/urls.py | 5 | ||||
-rw-r--r-- | group_messaging/views.py | 92 |
5 files changed, 565 insertions, 51 deletions
diff --git a/group_messaging/__init__.py b/group_messaging/__init__.py index 642ad5c8..ed3d73ff 100644 --- a/group_messaging/__init__.py +++ b/group_messaging/__init__.py @@ -11,4 +11,7 @@ the group should be named `'_personal_1'`. Only one person must be a member of a personal group and each user must have such group. + +TODO: decouple this application +first step is to package send_mail separately """ diff --git a/group_messaging/models.py b/group_messaging/models.py index 62f720cf..5d43baf8 100644 --- a/group_messaging/models.py +++ b/group_messaging/models.py @@ -1,18 +1,40 @@ """models for the ``group_messaging`` app """ +import copy import datetime +import urllib +from askbot.mail import send_mail #todo: remove dependency? +from django.template.loader import get_template from django.db import models +from django.db.models import signals +from django.conf import settings as django_settings from django.contrib.auth.models import Group from django.contrib.auth.models import User +from django.contrib.sites.models import Site +from django.utils.importlib import import_module +from django.utils.translation import ugettext as _ -MAX_TITLE_LENGTH = 80 +MAX_HEADLINE_LENGTH = 80 MAX_SENDERS_INFO_LENGTH = 64 +MAX_SUBJECT_LINE_LENGTH = 30 #dummy parse message function parse_message = lambda v: v GROUP_NAME_TPL = '_personal_%s' +def get_recipient_names(recipient_groups): + """returns list of user names if groups are private, + or group names, otherwise""" + names = set() + for group in recipient_groups: + if group.name.startswith('_personal_'): + names.add(group.user_set.all()[0].username) + else: + names.add(group.name) + return names + + def get_personal_group_by_user_id(user_id): return Group.objects.get(name=GROUP_NAME_TPL % user_id) @@ -87,7 +109,7 @@ class MessageMemo(models.Model): (ARCHIVED, 'archived') ) user = models.ForeignKey(User) - message = models.ForeignKey('Message') + message = models.ForeignKey('Message', related_name='memos') status = models.SmallIntegerField( choices=STATUS_CHOICES, default=SEEN ) @@ -99,13 +121,68 @@ class MessageMemo(models.Model): class MessageManager(models.Manager): """model manager for the :class:`Message`""" - def get_threads_for_user(self, user): - user_groups = user.groups.all() - return self.filter( - root=None, - message_type=Message.STORED, - recipients__in=user_groups + def get_sent_threads(self, sender=None): + """returns list of threads for the "sent" mailbox + this function does not deal with deleted=True + """ + responses = self.filter(sender=sender) + responded_to = models.Q(descendants__in=responses, root=None) + seen_filter = models.Q( + memos__status=MessageMemo.SEEN, + memos__user=sender ) + seen_responses = self.filter(responded_to & seen_filter) + unseen_responses = self.filter(responded_to & ~models.Q(memos__user=sender)) + return ( + self.get_threads(sender=sender) \ + | seen_responses.distinct() \ + | unseen_responses.distinct() + ).distinct() + + def get_threads(self, recipient=None, sender=None, deleted=False): + """returns query set of first messages in conversations, + based on recipient, sender and whether to + load deleted messages or not""" + + if sender and sender == recipient: + raise ValueError('sender cannot be the same as recipient') + + filter_kwargs = { + 'root': None, + 'message_type': Message.STORED + } + if recipient: + filter_kwargs['recipients__in'] = recipient.groups.all() + else: + #todo: possibly a confusing hack - for this branch - + #sender but no recipient in the args - we need "sent" origin threads + recipient = sender + + user_thread_filter = models.Q(**filter_kwargs) + + filter = user_thread_filter + if sender: + filter = filter & models.Q(sender=sender) + + if deleted: + deleted_filter = models.Q( + memos__status=MessageMemo.ARCHIVED, + memos__user=recipient + ) + return self.filter(filter & deleted_filter) + else: + #rather a tricky query (may need to change the idea to get rid of this) + #select threads that have a memo for the user, but the memo is not ARCHIVED + #in addition, select threads that have zero memos for the user + marked_as_non_deleted_filter = models.Q( + memos__status=MessageMemo.SEEN, + memos__user=recipient + ) + #part1 - marked as non-archived + part1 = self.filter(filter & marked_as_non_deleted_filter) + #part2 - messages for the user without an attached memo + part2 = self.filter(filter & ~models.Q(memos__user=recipient)) + return (part1 | part2).distinct() def create(self, **kwargs): """creates a message""" @@ -120,7 +197,7 @@ class MessageManager(models.Manager): kwargs['root'] = root headline = kwargs.get('headline', kwargs['text']) - kwargs['headline'] = headline[:MAX_TITLE_LENGTH] + kwargs['headline'] = headline[:MAX_HEADLINE_LENGTH] kwargs['html'] = parse_message(kwargs['text']) message = super(MessageManager, self).create(**kwargs) @@ -133,7 +210,6 @@ class MessageManager(models.Manager): ) return message - def create_thread(self, sender=None, recipients=None, text=None): """creates a stored message and adds recipients""" message = self.create( @@ -142,7 +218,13 @@ class MessageManager(models.Manager): senders_info=sender.username, text=text, ) + now = datetime.datetime.now() + LastVisitTime.objects.create(message=message, user=sender, at=now) + names = get_recipient_names(recipients) + message.add_recipient_names_to_senders_info(recipients) + message.save() message.add_recipients(recipients) + message.send_email_alert() return message def create_response(self, sender=None, text=None, parent=None): @@ -155,15 +237,23 @@ class MessageManager(models.Manager): #recipients are parent's recipients + sender #creator of response gets memo in the "read" status recipients = set(parent.recipients.all()) - senders_group = get_personal_group(parent.sender) - recipients.add(senders_group) + + if sender != parent.sender: + senders_group = get_personal_group(parent.sender) + parent.add_recipients([senders_group]) + recipients.add(senders_group) + message.add_recipients(recipients) #add author of the parent as a recipient to parent - parent.add_recipients([senders_group]) + #update headline + message.root.headline = text[:MAX_HEADLINE_LENGTH] #mark last active timestamp for the root message - #so that we know that this thread was most recently - #updated - message.update_root_info() + message.root.last_active_at = datetime.datetime.now() + #update senders info - stuff that is shown in the thread heading + message.root.update_senders_info() + #unarchive the thread for all recipients + message.root.unarchive() + message.send_email_alert() return message @@ -205,7 +295,7 @@ class Message(models.Model): blank=True, related_name='children' ) - headline = models.CharField(max_length=MAX_TITLE_LENGTH) + headline = models.CharField(max_length=MAX_HEADLINE_LENGTH) text = models.TextField( null=True, blank=True, @@ -223,6 +313,12 @@ class Message(models.Model): objects = MessageManager() + def add_recipient_names_to_senders_info(self, recipient_groups): + names = get_recipient_names(recipient_groups) + old_names = set(self.senders_info.split(',')) + names |= old_names + self.senders_info = ','.join(names) + def add_recipients(self, recipients): """adds recipients to the message and updates the sender lists for all recipients @@ -233,17 +329,110 @@ class Message(models.Model): sender_list, created = SenderList.objects.get_or_create(recipient=recipient) sender_list.senders.add(self.sender) - def update_root_info(self): - """Update the last active at timestamp and - the contributors info, if relevant. - Root object will be saved to the database. + def get_absolute_url(self, user=None): + """returns absolute url to the thread""" + assert(user != None) + settings = django_settings.GROUP_MESSAGING + func_path = settings['base_url_getter_function'] + path_bits = func_path.split('.') + url_getter = getattr( + import_module('.'.join(path_bits[:-1])), + path_bits[-1] + ) + params = copy.copy(settings['base_url_params']) + params['thread_id'] = self.id + url = url_getter(user) + '?' + urllib.urlencode(params) + #if include_domain_name: #don't need this b/c + # site = Site.objects.get_current() + # url = 'http://' + site.domain + url + return url + + def get_email_subject_line(self): + """forms subject line based on the root message + and prepends 'Re': if message is non-root + """ + subject = self.get_root_message().text[:MAX_SUBJECT_LINE_LENGTH] + if self.root: + subject = _('Re: ') + subject + return subject + + def get_root_message(self): + """returns root message or self + if current message is root + """ + return self.root or self + + def get_recipients_users(self): + """returns query set of users""" + groups = self.recipients.all() + return User.objects.filter( + groups__in=groups + ).exclude( + id=self.sender.id + ).distinct() + + def get_timeline(self): + """returns ordered query set of messages in the thread + with the newest first""" + root = self.get_root_message() + root_qs = Message.objects.filter(id=root.id) + return (root.descendants.all() | root_qs).order_by('-sent_at') + + + def send_email_alert(self): + """signal handler for the message post-save""" + root_message = self.get_root_message() + data = {'messages': self.get_timeline()} + template = get_template('group_messaging/email_alert.html') + body_text = template.render(data) + subject = self.get_email_subject_line() + for user in self.get_recipients_users(): + #todo change url scheme so that all users have the same + #urls within their personal areas of the user profile + #so that we don't need to have loops like this one + thread_url = root_message.get_absolute_url(user) + thread_url = thread_url.replace('&', '&') + #in the template we have a placeholder to be replaced like this: + body_text = body_text.replace('THREAD_URL_HOLE', thread_url) + send_mail( + subject, + body_text, + django_settings.DEFAULT_FROM_EMAIL, + [user.email,], + ) + + + def update_senders_info(self): + """update the contributors info, + meant to be used on a root message only """ - self.root.last_active_at = datetime.datetime.now() - senders_names = self.root.senders_info.split(',') + senders_names = self.senders_info.split(',') if self.sender.username in senders_names: senders_names.remove(self.sender.username) senders_names.insert(0, self.sender.username) - self.root.senders_info = (','.join(senders_names))[:64] - self.root.save() + self.senders_info = (','.join(senders_names))[:64] + self.save() + + def unarchive(self, user=None): + """unarchive message for all recipients""" + archived_filter = {'status': MessageMemo.ARCHIVED} + if user: + archived_filter['user'] = user + memos = self.memos.filter(**archived_filter) + memos.update(status=MessageMemo.SEEN) + + def set_status_for_user(self, status, user): + """set specific status to the message for the user""" + memo, created = MessageMemo.objects.get_or_create(user=user, message=self) + memo.status = status + memo.save() + + def archive(self, user): + """mark message as archived""" + self.set_status_for_user(MessageMemo.ARCHIVED, user) + + def mark_as_seen(self, user): + """mark message as seen""" + self.set_status_for_user(MessageMemo.SEEN, user) diff --git a/group_messaging/tests.py b/group_messaging/tests.py index c8401dc1..bcf764db 100644 --- a/group_messaging/tests.py +++ b/group_messaging/tests.py @@ -1,10 +1,17 @@ +import datetime +import time +import urlparse +from bs4 import BeautifulSoup from django.test import TestCase from django.contrib.auth.models import User, Group from group_messaging.models import Message from group_messaging.models import MessageMemo from group_messaging.models import SenderList +from group_messaging.models import LastVisitTime from group_messaging.models import get_personal_group from group_messaging.models import create_personal_group +from group_messaging.views import ThreadsList +from mock import Mock MESSAGE_TEXT = 'test message text' @@ -22,27 +29,166 @@ def create_user(name): user.groups.add(group) return user -class ModelTests(TestCase): - """test cases for the `private_messaging` models""" +def get_html_message(mail_message): + """mail message is an item from the django.core.mail.outbox""" + return mail_message.alternatives[0][0] + +class GroupMessagingTests(TestCase): + """base class for the test cases in this app""" def setUp(self): self.sender = create_user('sender') self.recipient = create_user('recipient') - def create_thread(self, recipients): + def create_thread(self, sender, recipient_groups): return Message.objects.create_thread( - sender=self.sender, recipients=recipients, + sender=sender, recipients=recipient_groups, text=MESSAGE_TEXT ) - def create_thread_for_user(self, user): - group = get_personal_group(user) - return self.create_thread([group]) + def create_thread_for_user(self, sender, recipient): + group = get_personal_group(recipient) + return self.create_thread(sender, [group]) + + def setup_three_message_thread(self, original_poster=None, responder=None): + """talk in this order: sender, recipient, sender""" + original_poster = original_poster or self.sender + responder = responder or self.recipient + + root_message = self.create_thread_for_user(original_poster, responder) + response = Message.objects.create_response( + sender=responder, + text='some response', + parent=root_message + ) + response2 = Message.objects.create_response( + sender=original_poster, + text='some response2', + parent=response + ) + return root_message, response, response2 + + +class ViewsTests(GroupMessagingTests): + + def get_view_context(self, view_class, data=None, user=None, method='GET'): + spec = ['REQUEST', 'user'] + assert(method in ('GET', 'POST')) + spec.append(method) + request = Mock(spec=spec) + request.REQUEST = data + setattr(request, method, data) + request.user = user + return view_class().get_context(request) + + def test_new_response_marks_thread_heading_as_new(self): + root = self.create_thread_for_user(self.sender, self.recipient) + response = Message.objects.create_response( + sender=self.recipient, + text='some response', + parent=root + ) + #response must show as "new" to the self.sender + context = self.get_view_context( + ThreadsList, + data={'sender_id': '-1'}, + user=self.sender + ) + self.assertEqual(context['threads_data'][root.id]['status'], 'new') + #"visit" the thread: todo - make a method + last_visit_time, created = LastVisitTime.objects.get_or_create( + user=self.sender, + message=root + ) + last_visit_time.at = datetime.datetime.now() + last_visit_time.save() + time.sleep(1.5) + + #response must show as "seen" + context = self.get_view_context( + ThreadsList, + data={'sender_id': '-1'}, + user=self.sender + ) + self.assertEqual(context['threads_data'][root.id]['status'], 'seen') + #self.recipient makes another response + response = Message.objects.create_response( + sender=self.recipient, + text='some response', + parent=response + ) + #thread must be "new" again + context = self.get_view_context( + ThreadsList, + data={'sender_id': '-1'}, + user=self.sender + ) + self.assertEqual(context['threads_data'][root.id]['status'], 'new') + + def test_answer_to_deleted_thread_undeletes_thread(self): + #setup: message, reply, responder deletes thread + root_message = self.create_thread_for_user(self.sender, self.recipient) + response = Message.objects.create_response( + sender=self.recipient, + text='some response', + parent=root_message + ) + memo1, created = MessageMemo.objects.get_or_create( + message=root_message, + user=self.recipient, + status=MessageMemo.ARCHIVED + ) + #OP sends reply to reply + response2 = Message.objects.create_response( + sender=self.sender, + text='some response2', + parent=response + ) + + context = self.get_view_context( + ThreadsList, + data={'sender_id': '-1'}, + user=self.recipient + ) + + self.assertEqual(len(context['threads']), 1) + thread_id = context['threads'][0].id + thread_data = context['threads_data'][thread_id] + self.assertEqual(thread_data['status'], 'new') + + def test_emailed_message_url_works_for_post_recipient(self): + root = self.create_thread_for_user(self.sender, self.recipient) + from django.core.mail import outbox + html_message = get_html_message(outbox[0]) + link = BeautifulSoup(html_message).find('a', attrs={'class': 'thread-link'}) + url = link['href'].replace('&', '&') + parsed_url = urlparse.urlparse(url) + url_data = urlparse.parse_qsl(parsed_url.query) + self.client.login(user_id=self.recipient.id, method='force') + response = self.client.get(parsed_url.path, url_data) + dom = BeautifulSoup(response.content) + threads = dom.find_all('ul', attrs={'class': 'thread'}) + self.assertEquals(len(threads), 1) + thread_lists = dom.find_all('table', attrs={'class': 'threads-list'}) + self.assertEquals(len(thread_lists), 0) + + def test_sent_thread_is_visited_by_sender(self): + root = self.create_thread_for_user(self.sender, self.recipient) + context = self.get_view_context( + ThreadsList, + data={'sender_id': str(self.sender.id)}, + user=self.sender + ) + thread_data = context['threads_data'][root.id] + self.assertEqual(thread_data['status'], 'seen') + +class ModelsTests(GroupMessagingTests): + """test cases for the `private_messaging` models""" def test_create_thread_for_user(self): """the basic create thread with one recipient tests that the recipient is there""" - message = self.create_thread_for_user(self.recipient) + message = self.create_thread_for_user(self.sender, self.recipient) #message type is stored self.assertEqual(message.message_type, Message.STORED) #recipient is in the list of recipients @@ -70,7 +216,7 @@ class ModelTests(TestCase): member of the group has updated the sender list""" group = Group.objects.create(name='somegroup') self.recipient.groups.add(group) - message = self.create_thread([group]) + message = self.create_thread(self.sender, [group]) senders = SenderList.objects.get_senders_for_user(self.recipient) self.assertEqual(set(senders), set([self.sender])) @@ -78,7 +224,7 @@ class ModelTests(TestCase): """create a thread with one response, then load thread for the user test that only the root message is retrieved""" - root_message = self.create_thread_for_user(self.recipient) + root_message = self.create_thread_for_user(self.sender, self.recipient) response = Message.objects.create_response( sender=self.recipient, text='some response', @@ -100,11 +246,11 @@ class ModelTests(TestCase): expected_recipients = set([sender_group, recipient_group]) self.assertEqual(recipients, expected_recipients) - def test_get_threads_for_user(self): - root_message = self.create_thread_for_user(self.recipient) - threads = set(Message.objects.get_threads_for_user(self.sender)) + def test_get_threads(self): + root_message = self.create_thread_for_user(self.sender, self.recipient) + threads = set(Message.objects.get_threads(recipient=self.sender)) self.assertEqual(threads, set([])) - threads = set(Message.objects.get_threads_for_user(self.recipient)) + threads = set(Message.objects.get_threads(recipient=self.recipient)) self.assertEqual(threads, set([root_message])) response = Message.objects.create_response( @@ -112,7 +258,106 @@ class ModelTests(TestCase): text='some response', parent=root_message ) - threads = set(Message.objects.get_threads_for_user(self.sender)) + threads = set(Message.objects.get_threads(recipient=self.sender)) self.assertEqual(threads, set([root_message])) - threads = set(Message.objects.get_threads_for_user(self.recipient)) + threads = set(Message.objects.get_threads(recipient=self.recipient)) self.assertEqual(threads, set([root_message])) + + def test_deleting_thread_is_user_specific(self): + """when one user deletes thread, that same thread + should not end up deleted by another user + """ + root, response, response2 = self.setup_three_message_thread() + + threads = Message.objects.get_threads(recipient=self.sender) + self.assertEquals(threads.count(), 1) + threads = Message.objects.get_threads(recipient=self.recipient) + self.assertEquals(threads.count(), 1) + + memo1, created = MessageMemo.objects.get_or_create( + message=root, + user=self.recipient, + status=MessageMemo.ARCHIVED + ) + + threads = Message.objects.get_threads(recipient=self.sender) + self.assertEquals(threads.count(), 1) + threads = Message.objects.get_threads(recipient=self.recipient) + self.assertEquals(threads.count(), 0) + threads = Message.objects.get_threads( + recipient=self.recipient, deleted=True + ) + self.assertEquals(threads.count(), 1) + + def test_user_specific_inboxes(self): + self.create_thread_for_user(self.sender, self.recipient) + + threads = Message.objects.get_threads( + recipient=self.recipient, sender=self.sender + ) + self.assertEqual(threads.count(), 1) + threads = Message.objects.get_threads( + recipient=self.sender, sender=self.recipient + ) + self.assertEqual(threads.count(), 0) + + def test_response_updates_thread_headline(self): + root = self.create_thread_for_user(self.sender, self.recipient) + response = Message.objects.create_response( + sender=self.recipient, + text='some response', + parent=root + ) + self.assertEqual(root.headline, 'some response') + + def test_email_alert_sent(self): + root = self.create_thread_for_user(self.sender, self.recipient) + from django.core.mail import outbox + self.assertEqual(len(outbox), 1) + self.assertEqual(len(outbox[0].recipients()), 1) + self.assertEqual(outbox[0].recipients()[0], self.recipient.email) + html_message = get_html_message(outbox[0]) + self.assertTrue(root.text in html_message) + soup = BeautifulSoup(html_message) + links = soup.find_all('a', attrs={'class': 'thread-link'}) + self.assertEqual(len(links), 1) + parse_result = urlparse.urlparse(links[0]['href']) + query = urlparse.parse_qs(parse_result.query.replace('&', '&')) + self.assertEqual(query['thread_id'][0], str(root.id)) + + def test_get_sent_threads(self): + root1, re11, re12 = self.setup_three_message_thread() + root2, re21, re22 = self.setup_three_message_thread( + original_poster=self.recipient, responder=self.sender + ) + root3, re31, re32 = self.setup_three_message_thread() + + #mark root2 as seen + root2.mark_as_seen(self.sender) + #mark root3 as deleted + root3.archive(self.sender) + + threads = Message.objects.get_sent_threads(sender=self.sender) + self.assertEqual(threads.count(), 2) + self.assertEqual(set(threads), set([root1, root2]))#root3 is deleted + + def test_recipient_lists_are_in_senders_info(self): + thread = self.create_thread_for_user(self.sender, self.recipient) + self.assertTrue(self.recipient.username in thread.senders_info) + + def test_self_response_not_in_senders_inbox(self): + root = self.create_thread_for_user(self.sender, self.recipient) + response = Message.objects.create_response( + sender=self.sender, + text='some response', + parent=root + ) + threads = Message.objects.get_threads(recipient=self.sender) + self.assertEqual(threads.count(), 0) + + def test_sent_message_is_seen_by_the_sender(self): + root = self.create_thread_for_user(self.sender, self.recipient) + time.sleep(1.5) + last_visits = LastVisitTime.objects.filter(message=root, user=self.sender) + self.assertEqual(last_visits.count(), 1) + diff --git a/group_messaging/urls.py b/group_messaging/urls.py index 30002bf3..19ee35bb 100644 --- a/group_messaging/urls.py +++ b/group_messaging/urls.py @@ -15,6 +15,11 @@ urlpatterns = patterns('', name='thread_details' ), url( + '^threads/(?P<thread_id>\d+)/delete-or-restore/$', + views.DeleteOrRestoreThread().as_view(), + name='delete_or_restore_thread' + ), + url( '^threads/create/$', views.NewThread().as_view(), name='create_thread' diff --git a/group_messaging/views.py b/group_messaging/views.py index 289961ff..244762d1 100644 --- a/group_messaging/views.py +++ b/group_messaging/views.py @@ -10,14 +10,16 @@ and turns them into complete views """ import copy import datetime -from coffin.template.loader import get_template +from django.template.loader import get_template from django.contrib.auth.models import User +from django.db import models from django.forms import IntegerField from django.http import HttpResponse from django.http import HttpResponseNotAllowed from django.http import HttpResponseForbidden from django.utils import simplejson from group_messaging.models import Message +from group_messaging.models import MessageMemo from group_messaging.models import SenderList from group_messaging.models import LastVisitTime from group_messaging.models import get_personal_group_by_user_id @@ -104,7 +106,12 @@ class NewThread(InboxView): if missing: result['success'] = False result['missing_users'] = missing - else: + + if request.user.username in usernames: + result['success'] = False + result['self_message'] = True + + if result.get('success', True): recipients = get_personal_groups_for_users(users) message = Message.objects.create_thread( sender=request.user, @@ -139,6 +146,7 @@ class PostReply(InboxView): template_name='group_messaging/stored_message.html' ) + class ThreadsList(InboxView): """shows list of threads for a given user""" template_name = 'group_messaging/threads_list.html' @@ -147,11 +155,24 @@ class ThreadsList(InboxView): def get_context(self, request): """returns thread list data""" #get threads and the last visit time - threads = Message.objects.get_threads_for_user(request.user) + sender_id = IntegerField().clean(request.REQUEST.get('sender_id', '-1')) + if sender_id == -2: + threads = Message.objects.get_threads( + recipient=request.user, + deleted=True + ) + elif sender_id == -1: + threads = Message.objects.get_threads(recipient=request.user) + elif sender_id == request.user.id: + threads = Message.objects.get_sent_threads(sender=request.user) + else: + sender = User.objects.get(id=sender_id) + threads = Message.objects.get_threads( + recipient=request.user, + sender=sender + ) - sender_id = IntegerField().clean(request.GET.get('sender_id', '-1')) - if sender_id != -1: - threads = threads.filter(sender__id=sender_id) + threads = threads.order_by('-last_active_at') #for each thread we need to know if there is something #unread for the user - to mark "new" threads as bold @@ -168,6 +189,17 @@ class ThreadsList(InboxView): thread_data['thread'] = thread threads_data[thread.id] = thread_data + ids = [thread.id for thread in threads] + counts = Message.objects.filter( + id__in=ids + ).annotate( + responses_count=models.Count('descendants') + ).values('id', 'responses_count') + for count in counts: + thread_id = count['id'] + responses_count = count['responses_count'] + threads_data[thread_id]['responses_count'] = responses_count + last_visit_times = LastVisitTime.objects.filter( user=request.user, message__in=threads @@ -177,9 +209,49 @@ class ThreadsList(InboxView): if thread_data['thread'].last_active_at <= last_visit.at: thread_data['status'] = 'seen' - #after we have all the data - update the last visit time - last_visit_times.update(at=datetime.datetime.now()) - return {'threads': threads, 'threads_data': threads_data} + return { + 'threads': threads, + 'threads_count': threads.count(), + 'threads_data': threads_data, + 'sender_id': sender_id + } + + +class DeleteOrRestoreThread(ThreadsList): + """subclassing :class:`ThreadsList`, because deletion + or restoring of thread needs subsequent refreshing + of the threads list""" + + http_method_list = ('POST',) + + def post(self, request, thread_id=None): + """process the post request: + * delete or restore thread + * recalculate the threads list and return it for display + by reusing the threads list "get" function + """ + #part of the threads list context + sender_id = IntegerField().clean(request.POST['sender_id']) + + #a little cryptic, but works - sender_id==-2 means deleted post + if sender_id == -2: + action = 'restore' + else: + action = 'delete' + + thread = Message.objects.get(id=thread_id) + memo, created = MessageMemo.objects.get_or_create( + user=request.user, + message=thread + ) + if action == 'delete': + memo.status = MessageMemo.ARCHIVED + else: + memo.status = MessageMemo.SEEN + memo.save() + + context = self.get_context(request) + return self.render_to_response(context) class SendersList(InboxView): @@ -191,7 +263,7 @@ class SendersList(InboxView): """get data about senders for the user""" senders = SenderList.objects.get_senders_for_user(request.user) senders = senders.values('id', 'username') - return {'senders': senders} + return {'senders': senders, 'request_user_id': request.user.id} class ThreadDetails(InboxView): |