├── directmessages ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── signals.py ├── admin.py ├── __init__.py ├── apps.py ├── models.py ├── services.py └── tests.py ├── requirements.txt ├── MANIFEST.in ├── .travis.yml ├── CHANGELOG.rst ├── .gitignore ├── runtests.py ├── setup.py ├── LICENSE.txt └── README.rst /directmessages/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | coverage==3.7.1 2 | django-coverage==1.2.4 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include setup.py README.rst MANIFEST.in LICENSE.txt requirements.txt 2 | recursive-exclude * __pycache__ 3 | recursive-exclude * *.py[co] 4 | -------------------------------------------------------------------------------- /directmessages/signals.py: -------------------------------------------------------------------------------- 1 | from django.dispatch import Signal 2 | 3 | message_sent = Signal(providing_args=['from_user', 'to']) 4 | message_read = Signal(providing_args=['from_user', 'to']) -------------------------------------------------------------------------------- /directmessages/admin.py: -------------------------------------------------------------------------------- 1 | from .models import Message 2 | from django.contrib import admin 3 | 4 | class MessageAdmin(admin.ModelAdmin): 5 | model = Message 6 | list_display = ('id', 'sender', 'content', ) 7 | 8 | admin.site.register(Message, MessageAdmin) -------------------------------------------------------------------------------- /directmessages/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.9.7' 2 | 3 | try: 4 | from django import VERSION as DJANGO_VERSION 5 | if DJANGO_VERSION >= (1, 7): 6 | default_app_config = 'directmessages.apps.DirectmessagesConfig' 7 | else: 8 | from directmessages.apps import populateInbox 9 | populateInbox() 10 | except ImportError: 11 | pass 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.3" 5 | - "3.4" 6 | 7 | install: 8 | - pip install -e . 9 | - pip install $DJANGO 10 | - pip install -r requirements.txt 11 | script: 12 | - python runtests.py 13 | env: 14 | - DJANGO="Django==1.5.*" 15 | - DJANGO="Django==1.6.*" 16 | - DJANGO="Django==1.7.*" 17 | - DJANGO="Django==1.8.*" 18 | - DJANGO="Django==1.9.*" 19 | - DJANGO="Django==1.10.*" 20 | 21 | matrix: 22 | exclude: 23 | # Django 1.9 doesn't support Python 3.3 24 | - python: "3.3" 25 | env: DJANGO="Django==1.9.*" 26 | - python: "3.3" 27 | env: DJANGO="Django==1.10.*" 28 | -------------------------------------------------------------------------------- /directmessages/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from django import VERSION as DJANGO_VERSION 3 | 4 | Inbox = None 5 | 6 | if DJANGO_VERSION >= (1, 7): 7 | from django.apps import AppConfig 8 | class DirectmessagesConfig(AppConfig): 9 | name = 'directmessages' 10 | label = 'directmessaging' 11 | 12 | def ready(self): 13 | # For convenience 14 | from directmessages.services import MessagingService 15 | global Inbox 16 | Inbox = MessagingService() 17 | 18 | else: 19 | def populateInbox(): 20 | from directmessages.services import MessagingService 21 | global Inbox 22 | Inbox = MessagingService() 23 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | VERSION 0.9.7 (released 2016-09-16) 2 | =================================== 3 | 4 | - Django<1.10 constraint removed 5 | 6 | VERSION 0.9.6 (released 2016-09-16) 7 | =================================== 8 | 9 | - Added 'mark_read'-flag to conversation check 10 | 11 | VERSION 0.9.5 (released 2016-08-18) 12 | =================================== 13 | 14 | - Label re-change 15 | - Django 1.10 support 16 | 17 | VERSION 0.9.4 (released 2016-08-18) 18 | =================================== 19 | 20 | - Changed App Label 21 | - Fixed str function 22 | 23 | VERSION 0.9.2 (released 2016-07-26) 24 | =================================== 25 | 26 | - Travis Configurations 27 | - Added message_id as param instead of message object 28 | - Fixed several typos 29 | - Added ToDo-Plan 30 | - Ensured 1.9 compatibility through AppConfig 31 | 32 | VERSION 0.9.1 (released 2016-07-24) 33 | =================================== 34 | 35 | - Initial Release 36 | - Travis Integration 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | *.sqlite3 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | # IDE & Editors 61 | .idea/ 62 | *.sw[po] 63 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from django.conf import global_settings, settings 4 | 5 | OUR_MIDDLEWARE = [] 6 | OUR_MIDDLEWARE.extend(global_settings.MIDDLEWARE_CLASSES) 7 | OUR_MIDDLEWARE.extend([ 8 | 'django.contrib.sessions.middleware.SessionMiddleware', 9 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 10 | ]) 11 | 12 | 13 | settings.configure( 14 | DATABASES={ 15 | 'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory;'} 16 | }, 17 | INSTALLED_APPS=[ 18 | 'django.contrib.auth', 19 | 'django.contrib.sessions', 20 | 'django.contrib.contenttypes', 21 | 'directmessages', 22 | ], 23 | ROOT_URLCONF='directmessages.urls', 24 | MIDDLEWARE_CLASSES=OUR_MIDDLEWARE, 25 | ) 26 | 27 | 28 | def runtests(*test_args): 29 | import django.test.utils 30 | 31 | if django.VERSION[0:2] >= (1, 7): 32 | django.setup() 33 | 34 | runner_class = django.test.utils.get_runner(settings) 35 | test_runner = runner_class(verbosity=1, interactive=True, failfast=False) 36 | failures = test_runner.run_tests(['directmessages']) 37 | sys.exit(failures) 38 | 39 | 40 | if __name__ == '__main__': 41 | runtests() 42 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import os 3 | import directmessages 4 | 5 | REQUIREMENTS = [ 6 | 'Django>=1.5', 7 | ] 8 | 9 | CLASSIFIERS = [ 10 | 'Environment :: Web Environment', 11 | 'Framework :: Django', 12 | 'Intended Audience :: Developers', 13 | 'License :: OSI Approved :: BSD License', 14 | 'Operating System :: OS Independent', 15 | 'Programming Language :: Python', 16 | 'Programming Language :: Python :: 2', 17 | 'Programming Language :: Python :: 3', 18 | 'Topic :: Software Development :: Libraries :: Application Frameworks', 19 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 20 | ] 21 | 22 | setup( 23 | author="Dominic Monn", 24 | author_email="monn.dominic@gmail.com", 25 | name='django-directmessages', 26 | version=directmessages.__version__, 27 | description='Django-Directmessages is a low-level and easy-to-use Django App to manage simple directmessages.', 28 | long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst')).read(), 29 | url='https://github.com/dmonn/django-directmessages/', 30 | license='BSD License', 31 | platforms=['OS Independent'], 32 | classifiers=CLASSIFIERS, 33 | install_requires=REQUIREMENTS, 34 | packages=find_packages(), 35 | include_package_data=True, 36 | zip_safe=False, 37 | ) 38 | -------------------------------------------------------------------------------- /directmessages/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.5 on 2016-07-24 19:41 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Message', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('content', models.TextField(verbose_name='Content')), 24 | ('sent_at', models.DateTimeField(blank=True, null=True, verbose_name='sent at')), 25 | ('read_at', models.DateTimeField(blank=True, null=True, verbose_name='read at')), 26 | ('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_dm', to=settings.AUTH_USER_MODEL, verbose_name='Recipient')), 27 | ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_dm', to=settings.AUTH_USER_MODEL, verbose_name='Sender')), 28 | ], 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /directmessages/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.core.exceptions import ValidationError 4 | from django.db import models 5 | from django.utils import timezone 6 | 7 | from django.utils.encoding import python_2_unicode_compatible 8 | from django.conf import settings 9 | from django.utils.translation import ugettext_lazy as _ 10 | 11 | 12 | AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') 13 | 14 | 15 | @python_2_unicode_compatible 16 | class Message(models.Model): 17 | """ 18 | A private directmessage 19 | """ 20 | content = models.TextField(_('Content')) 21 | sender = models.ForeignKey(AUTH_USER_MODEL, related_name='sent_dm', verbose_name=_("Sender")) 22 | recipient = models.ForeignKey(AUTH_USER_MODEL, related_name='received_dm', verbose_name=_("Recipient")) 23 | sent_at = models.DateTimeField(_("sent at"), null=True, blank=True) 24 | read_at = models.DateTimeField(_("read at"), null=True, blank=True) 25 | 26 | @property 27 | def unread(self): 28 | """returns whether the message was read or not""" 29 | if self.read_at is not None: 30 | return False 31 | return True 32 | 33 | def __str__(self): 34 | return self.content 35 | 36 | def save(self, **kwargs): 37 | if self.sender == self.recipient: 38 | raise ValidationError("You can't send messages to yourself") 39 | 40 | if not self.id: 41 | self.sent_at = timezone.now() 42 | super(Message, self).save(**kwargs) -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Dominic Monn 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name Dominic Monn nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL DIVIO AG BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 23 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django-Directmessages 2 | ===================== 3 | 4 | .. image:: https://travis-ci.org/dmonn/django-directmessages.svg?branch=master 5 | :target: https://travis-ci.org/dmonn/django-directmessages 6 | 7 | Django-Directmessages is a low-level and easy-to-use Django App to manage simple directmessages. 8 | In contrast to other Django Apps for messaging, Django-Directmessages doesn't use any type of pre-built templates and is concentrated on the programmatic usage. 9 | 10 | Django-Directmessage is thought to be used with APIs or small apps, but can be used for any type of messaging. It featues: 11 | 12 | * Sending of private 1-to-1 messages between users. 13 | * Listing unread messages for a given user. 14 | * Read a given message 15 | * Get all conversation partners/contacted users for a given user 16 | * Read a whole conversation between two users. 17 | 18 | Requirements 19 | ============ 20 | 21 | *Django >= 1.5* is supported 22 | 23 | Installation 24 | ============ 25 | 26 | 1. ``pip install django-directmessages`` 27 | 2. add ``"directmessages"`` to ``INSTALLED_APPS`` and run ``python manage.py migrate``. 28 | 29 | Usage 30 | ===== 31 | 32 | Import the Message Management API on top of your ``views.py`` :: 33 | 34 | from directmessages.apps import Inbox 35 | 36 | * Send message: ``Inbox.send_message(from_user, to_user, message)`` 37 | * List all unread messages: ``Inbox.get_unread_messages(user)`` 38 | * Read a message (and mark as read): ``Inbox.read_message(message)`` 39 | * Print a message as : : ``Inbox.read_message_formatted(message)`` 40 | * Print a list of all conversation partners for a user: ``Inbox.get_conversations(users)`` 41 | * Get a conversation between two users: ``Inbox.get_conversation(user1, user2, _limit_, _reversed_, _mark_read_)`` 42 | - Limit (Int: optional): Instead of getting the whole conversation, get the first 50 (depends on reversed) 43 | - Reversed (Bool: optional): Usually the 'limit'-param gives back the first x messages, if you put Reversed to True, limit will give back the x latest messages. 44 | - Mark_Read (Bool: optional): Mark all messages in conversation as read 45 | 46 | Signals 47 | ======= 48 | 49 | You can use the following signals to extend the app for your needs 50 | 51 | * message_sent: 52 | Gets called as soon as a message is sent. 53 | Provides the Message object, the sender and the recipient as params. 54 | 55 | * message_read: 56 | Gets called as soon as a message is read: 57 | Provides the Message object, the sender and the recipient as params. 58 | 59 | Contributing 60 | ============ 61 | 62 | Bug reports, patches and fixes are always welcome! 63 | 64 | 65 | To Do 66 | ===== 67 | 68 | * Add some security functions (e.g checking if user is allowed to read a message) 69 | * Add some custom exceptions (e.g. when no message was found) 70 | -------------------------------------------------------------------------------- /directmessages/services.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from .models import Message 4 | from .signals import message_read, message_sent 5 | from django.core.exceptions import ValidationError 6 | from django.utils import timezone 7 | from django.db.models import Q 8 | 9 | 10 | class MessagingService(object): 11 | """ 12 | A object to manage all messages and conversations 13 | """ 14 | 15 | # Message creation 16 | 17 | def send_message(self, sender, recipient, message): 18 | """ 19 | Send a new message 20 | :param sender: user 21 | :param recipient: user 22 | :param message: String 23 | :return: Message and status code 24 | """ 25 | 26 | if sender == recipient: 27 | raise ValidationError("You can't send messages to yourself.") 28 | 29 | message = Message(sender=sender, recipient=recipient, content=str(message)) 30 | message.save() 31 | 32 | message_sent.send(sender=message, from_user=message.sender, to=message.recipient) 33 | 34 | # The second value acts as a status value 35 | return message, 200 36 | 37 | # Message reading 38 | def get_unread_messages(self, user): 39 | """ 40 | List of unread messages for a specific user 41 | :param user: user 42 | :return: messages 43 | """ 44 | return Message.objects.all().filter(recipient=user, read_at=None) 45 | 46 | def read_message(self, message_id): 47 | """ 48 | Read specific message 49 | :param message_id: Integer 50 | :return: Message Text 51 | """ 52 | try: 53 | message = Message.objects.get(id=message_id) 54 | self.mark_as_read(message) 55 | return message.content 56 | except Message.DoesNotExist: 57 | return "" 58 | 59 | def read_message_formatted(self, message_id): 60 | """ 61 | Read a message in the format : 62 | :param message_id: Id 63 | :return: Formatted Message Text 64 | """ 65 | try: 66 | message = Message.objects.get(id=message_id) 67 | self.mark_as_read(message) 68 | return message.sender.username + ": "+message.content 69 | except Message.DoesNotExist: 70 | return "" 71 | 72 | # Conversation management 73 | 74 | def get_conversations(self, user): 75 | """ 76 | Lists all conversation-partners for a specific user 77 | :param user: User 78 | :return: Conversation list 79 | """ 80 | all_conversations = Message.objects.all().filter(Q(sender=user) | Q(recipient=user)) 81 | 82 | contacts = [] 83 | for conversation in all_conversations: 84 | if conversation.sender != user: 85 | contacts.append(conversation.sender) 86 | elif conversation.recipient != user: 87 | contacts.append(conversation.recipient) 88 | 89 | # To abolish duplicates 90 | return list(set(contacts)) 91 | 92 | def get_conversation(self, user1, user2, limit=None, reversed=False, mark_read=False): 93 | """ 94 | List of messages between two users 95 | :param user1: User 96 | :param user2: User 97 | :param limit: int 98 | :param reversed: Boolean - Makes the newest message be at index 0 99 | :return: messages 100 | """ 101 | users = [user1, user2] 102 | 103 | # Newest message first if it's reversed (index 0) 104 | if reversed: 105 | order = '-pk' 106 | else: 107 | order = 'pk' 108 | 109 | conversation = Message.objects.all().filter(sender__in=users, recipient__in=users).order_by(order) 110 | 111 | if limit: 112 | # Limit number of messages to the x newest 113 | conversation = conversation[:limit] 114 | 115 | if mark_read: 116 | for message in conversation: 117 | # Just to be sure, everything is read 118 | self.mark_as_read(message) 119 | 120 | return conversation 121 | 122 | # Helper methods 123 | def mark_as_read(self, message): 124 | """ 125 | Marks a message as read, if it hasn't been read before 126 | :param message: Message 127 | """ 128 | 129 | if message.read_at is None: 130 | message.read_at = timezone.now() 131 | message_read.send(sender=message, from_user=message.sender, to=message.recipient) 132 | message.save() 133 | 134 | -------------------------------------------------------------------------------- /directmessages/tests.py: -------------------------------------------------------------------------------- 1 | from .apps import Inbox 2 | from .models import Message 3 | from django.contrib.auth.models import User 4 | from django.test import TestCase 5 | 6 | 7 | class MessageSendTestCase(TestCase): 8 | def setUp(self): 9 | self.u1 = User.objects.create(username='someuser') 10 | self.u2 = User.objects.create(username='someotheruser') 11 | 12 | def test_send_message(self): 13 | init_value = Message.objects.all().count() 14 | 15 | message, status = Inbox.send_message(self.u1, self.u2, "This is a message") 16 | 17 | after_value = Message.objects.all().count() 18 | 19 | self.assertEqual(init_value + 1, after_value) 20 | self.assertEqual(status, 200) 21 | self.assertEqual(message.content, "This is a message") 22 | 23 | 24 | class MessageReadingTestCase(TestCase): 25 | def setUp(self): 26 | self.u1 = User.objects.create(username='someuser') 27 | self.u2 = User.objects.create(username='someotheruser') 28 | 29 | def test_unread_messages(self): 30 | Inbox.send_message(self.u1, self.u2, "This is a message") 31 | 32 | unread_messages = Inbox.get_unread_messages(self.u1) 33 | unread_messages2 = Inbox.get_unread_messages(self.u2) 34 | 35 | self.assertEqual(unread_messages.count(), 0) 36 | self.assertEqual(unread_messages2.count(), 1) 37 | 38 | def test_reading_messages(self): 39 | Inbox.send_message(self.u2, self.u1, "This is another message") 40 | 41 | unread_messages = Inbox.get_unread_messages(self.u1) 42 | self.assertEqual(unread_messages.count(), 1) 43 | 44 | message = Inbox.read_message(unread_messages[0].id) 45 | unread_messages_after = Inbox.get_unread_messages(self.u1) 46 | 47 | self.assertEqual(message, "This is another message") 48 | self.assertEqual(unread_messages_after.count(), 0) 49 | 50 | def test_reading_formatted(self): 51 | message, status = Inbox.send_message(self.u2, self.u1, "This is just another message") 52 | 53 | unread_messages = Inbox.get_unread_messages(self.u1) 54 | self.assertEqual(unread_messages.count(), 1) 55 | 56 | message = Inbox.read_message_formatted(message.id) 57 | unread_messages_after = Inbox.get_unread_messages(self.u1) 58 | 59 | self.assertEqual(message, self.u2.username + ": This is just another message") 60 | self.assertEqual(unread_messages_after.count(), 0) 61 | 62 | 63 | class ConversationTestCase(TestCase): 64 | def setUp(self): 65 | self.u1 = User.objects.create(username='User') 66 | self.u2 = User.objects.create(username='Admin') 67 | self.u3 = User.objects.create(username='Postman') 68 | self.u4 = User.objects.create(username='Chef') 69 | 70 | # Sender U1 71 | Inbox.send_message(self.u1, self.u2, "This is a message to User 2") 72 | Inbox.send_message(self.u1, self.u3, "This is a message to User 3") 73 | Inbox.send_message(self.u1, self.u4, "This is a message to User 4") 74 | 75 | # Sender U2 76 | Inbox.send_message(self.u2, self.u1, "This is a message to User 1") 77 | Inbox.send_message(self.u2, self.u3, "This is a message to User 3") 78 | Inbox.send_message(self.u2, self.u4, "This is a message to User 4") 79 | 80 | # Some more message between U1 and U2 81 | Inbox.send_message(self.u1, self.u2, "Hey, thanks for sending this message back") 82 | Inbox.send_message(self.u2, self.u1, "No problem") 83 | 84 | def test_all_conversations(self): 85 | conversation_partners = Inbox.get_conversations(self.u1) 86 | 87 | self.assertEqual(len(conversation_partners), 3) 88 | self.assertIn(self.u2, conversation_partners) 89 | self.assertIn(self.u3, conversation_partners) 90 | self.assertIn(self.u4, conversation_partners) 91 | 92 | self.assertNotIn(self.u1, conversation_partners) 93 | 94 | def test_single_conversation(self): 95 | unread_messages = Inbox.get_unread_messages(self.u1) 96 | 97 | self.assertEqual(unread_messages.count(), 2) 98 | 99 | conversation = Inbox.get_conversation(self.u1, self.u2) 100 | unread_messages_after = Inbox.get_unread_messages(self.u1) 101 | 102 | self.assertEqual(conversation.count(), 4) 103 | self.assertEqual(unread_messages_after.count(), 2) 104 | 105 | conversation_limited = Inbox.get_conversation(self.u1, self.u2, limit=2, reversed=True) 106 | self.assertEqual(conversation_limited.count(), 2) 107 | 108 | self.assertEqual(conversation[0].content, "This is a message to User 2") 109 | self.assertEqual(conversation[len(conversation) - 1].content, "No problem") 110 | self.assertEqual(conversation_limited[0].content, "No problem") 111 | self.assertEqual(conversation_limited[len(conversation_limited) - 1].content, 112 | "Hey, thanks for sending this message back") 113 | --------------------------------------------------------------------------------