diff options
Diffstat (limited to 'group_messaging')
-rw-r--r-- | group_messaging/models.py | 95 | ||||
-rw-r--r-- | group_messaging/tests.py | 221 | ||||
-rw-r--r-- | group_messaging/views.py | 23 |
3 files changed, 234 insertions, 105 deletions
diff --git a/group_messaging/models.py b/group_messaging/models.py index 12fe3620..a34e4690 100644 --- a/group_messaging/models.py +++ b/group_messaging/models.py @@ -17,6 +17,18 @@ 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) @@ -103,13 +115,44 @@ class MessageMemo(models.Model): class MessageManager(models.Manager): """model manager for the :class:`Message`""" + 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): - user_groups = recipient.groups.all() - user_thread_filter = models.Q( - root=None, - message_type=Message.STORED, - recipients__in=user_groups - ) + """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: @@ -161,7 +204,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( @@ -170,6 +212,9 @@ class MessageManager(models.Manager): senders_info=sender.username, text=text, ) + 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 @@ -184,11 +229,14 @@ 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 @@ -257,6 +305,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 @@ -323,7 +377,24 @@ class Message(models.Model): self.senders_info = (','.join(senders_names))[:64] self.save() - def unarchive(self): + def unarchive(self, user=None): """unarchive message for all recipients""" - memos = self.memos.filter(status=MessageMemo.ARCHIVED) + 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 9cc69fb8..f0a2dc5c 100644 --- a/group_messaging/tests.py +++ b/group_messaging/tests.py @@ -26,23 +26,43 @@ def create_user(name): user.groups.add(group) return user -class ModelTests(TestCase): - """test cases for the `private_messaging` models""" +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'] @@ -54,25 +74,87 @@ class ModelTests(TestCase): request.user = user return view_class().get_context(request) - def setup_three_message_thread(self): - """talk in this order: sender, recipient, sender""" - root_message = self.create_thread_for_user(self.recipient) + 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 + last_visit_time = LastVisitTime.objects.create( + user=self.sender, + message=root + ) + 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 ) - return root_message, response, response2 + + 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') + + +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 @@ -100,7 +182,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])) @@ -108,7 +190,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', @@ -131,7 +213,7 @@ class ModelTests(TestCase): self.assertEqual(recipients, expected_recipients) def test_get_threads(self): - root_message = self.create_thread_for_user(self.recipient) + 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(recipient=self.recipient)) @@ -147,37 +229,6 @@ class ModelTests(TestCase): threads = set(Message.objects.get_threads(recipient=self.recipient)) self.assertEqual(threads, set([root_message])) - def test_answer_to_deleted_thread_undeletes_thread(self): - #setup: message, reply, responder deletes thread - root_message = self.create_thread_for_user(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_deleting_thread_is_user_specific(self): """when one user deletes thread, that same thread should not end up deleted by another user @@ -205,7 +256,7 @@ class ModelTests(TestCase): self.assertEquals(threads.count(), 1) def test_user_specific_inboxes(self): - self.create_thread_for_user(self.recipient) + self.create_thread_for_user(self.sender, self.recipient) threads = Message.objects.get_threads( recipient=self.recipient, sender=self.sender @@ -216,50 +267,8 @@ class ModelTests(TestCase): ) self.assertEqual(threads.count(), 0) - def test_new_response_marks_thread_heading_as_new(self): - root = self.create_thread_for_user(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 - last_visit_time = LastVisitTime.objects.create( - user=self.sender, - message=root - ) - 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_response_updates_thread_headline(self): - root = self.create_thread_for_user(self.recipient) + root = self.create_thread_for_user(self.sender, self.recipient) response = Message.objects.create_response( sender=self.recipient, text='some response', @@ -268,8 +277,38 @@ class ModelTests(TestCase): self.assertEqual(root.headline, 'some response') def test_email_alert_sent(self): - root = self.create_thread_for_user(self.recipient) + 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) + + 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) diff --git a/group_messaging/views.py b/group_messaging/views.py index 3d973dbe..cd1a74d3 100644 --- a/group_messaging/views.py +++ b/group_messaging/views.py @@ -12,6 +12,7 @@ import copy import datetime from coffin.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 @@ -105,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, @@ -157,6 +163,8 @@ class ThreadsList(InboxView): ) 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( @@ -181,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 @@ -243,7 +262,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): |