From 524a7d83c93ff4b671fe128efbb592b3a11f9507 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Tue, 18 Sep 2012 03:53:16 -0400 Subject: broken commit, start for the group messaging app --- group_messaging/__init__.py | 14 +++ group_messaging/migrations/0001_initial.py | 177 ++++++++++++++++++++++++++ group_messaging/migrations/__init__.py | 0 group_messaging/models.py | 196 +++++++++++++++++++++++++++++ group_messaging/tests.py | 108 ++++++++++++++++ group_messaging/views.py | 148 ++++++++++++++++++++++ 6 files changed, 643 insertions(+) create mode 100644 group_messaging/__init__.py create mode 100644 group_messaging/migrations/0001_initial.py create mode 100644 group_messaging/migrations/__init__.py create mode 100644 group_messaging/models.py create mode 100644 group_messaging/tests.py create mode 100644 group_messaging/views.py (limited to 'group_messaging') diff --git a/group_messaging/__init__.py b/group_messaging/__init__.py new file mode 100644 index 00000000..642ad5c8 --- /dev/null +++ b/group_messaging/__init__.py @@ -0,0 +1,14 @@ +"""`group_messages` is a django application +which allows users send messages to other users +and groups (instances of :class:`django.contrib.auth.models.Group`) + +The same methods are used are used to send messages +to users as to groups - achieved via special "personal groups". + +By convention - personal groups have names formatted as follows: +_personal_, for example for the user whose `id == 1`, +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. +""" diff --git a/group_messaging/migrations/0001_initial.py b/group_messaging/migrations/0001_initial.py new file mode 100644 index 00000000..7d907dc1 --- /dev/null +++ b/group_messaging/migrations/0001_initial.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'SenderList' + db.create_table('group_messaging_senderlist', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('recipient', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.Group'], unique=True)), + )) + db.send_create_signal('group_messaging', ['SenderList']) + + # Adding M2M table for field senders on 'SenderList' + db.create_table('group_messaging_senderlist_senders', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('senderlist', models.ForeignKey(orm['group_messaging.senderlist'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique('group_messaging_senderlist_senders', ['senderlist_id', 'user_id']) + + # Adding model 'MessageMemo' + db.create_table('group_messaging_messagememo', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('message', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group_messaging.Message'])), + ('status', self.gf('django.db.models.fields.SmallIntegerField')(default=0)), + )) + db.send_create_signal('group_messaging', ['MessageMemo']) + + # Adding unique constraint on 'MessageMemo', fields ['user', 'message'] + db.create_unique('group_messaging_messagememo', ['user_id', 'message_id']) + + # Adding model 'Message' + db.create_table('group_messaging_message', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('message_type', self.gf('django.db.models.fields.SmallIntegerField')(default=0)), + ('sender', self.gf('django.db.models.fields.related.ForeignKey')(related_name='sent_messages', to=orm['auth.User'])), + ('root', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='descendants', null=True, to=orm['group_messaging.Message'])), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['group_messaging.Message'])), + ('headline', self.gf('django.db.models.fields.CharField')(max_length=80)), + ('text', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('sent_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('last_active_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('active_until', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), + )) + db.send_create_signal('group_messaging', ['Message']) + + # Adding M2M table for field recipients on 'Message' + db.create_table('group_messaging_message_recipients', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('message', models.ForeignKey(orm['group_messaging.message'], null=False)), + ('group', models.ForeignKey(orm['auth.group'], null=False)) + )) + db.create_unique('group_messaging_message_recipients', ['message_id', 'group_id']) + + def backwards(self, orm): + # Removing unique constraint on 'MessageMemo', fields ['user', 'message'] + db.delete_unique('group_messaging_messagememo', ['user_id', 'message_id']) + + # Deleting model 'SenderList' + db.delete_table('group_messaging_senderlist') + + # Removing M2M table for field senders on 'SenderList' + db.delete_table('group_messaging_senderlist_senders') + + # Deleting model 'MessageMemo' + db.delete_table('group_messaging_messagememo') + + # Deleting model 'Message' + db.delete_table('group_messaging_message') + + # Removing M2M table for field recipients on 'Message' + db.delete_table('group_messaging_message_recipients') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_fake': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'show_marked_tags': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'subscribed_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'group_messaging.message': { + 'Meta': {'object_name': 'Message'}, + 'active_until': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'headline': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_active_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'message_type': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['group_messaging.Message']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False'}), + 'root': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'descendants'", 'null': 'True', 'to': "orm['group_messaging.Message']"}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sent_messages'", 'to': "orm['auth.User']"}), + 'sent_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + 'group_messaging.messagememo': { + 'Meta': {'unique_together': "(('user', 'message'),)", 'object_name': 'MessageMemo'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group_messaging.Message']"}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'group_messaging.senderlist': { + 'Meta': {'object_name': 'SenderList'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'recipient': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'unique': 'True'}), + 'senders': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) + } + } + + complete_apps = ['group_messaging'] \ No newline at end of file diff --git a/group_messaging/migrations/__init__.py b/group_messaging/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/group_messaging/models.py b/group_messaging/models.py new file mode 100644 index 00000000..838134e7 --- /dev/null +++ b/group_messaging/models.py @@ -0,0 +1,196 @@ +from django.db import models +from django.contrib.auth.models import Group +from django.contrib.auth.models import User + +MAX_TITLE_LENGTH = 80 + +#dummy parse message function +parse_message = lambda v: v + +def get_personal_group_by_user_id(user_id): + return Group.objects.get(name='_personal_%s' % user_id) + + +def get_personal_group(user): + """returns personal group for the user""" + return get_personal_group_by_user_id(user.id) + + +def create_personal_group(user): + """creates a personal group for the user""" + group = Group(name='_personal_%s' % user.id) + group.save() + return group + + +class SenderListManager(models.Manager): + """model manager for the :class:`SenderList`""" + + def get_senders_for_user(self, user=None): + """returns query set of :class:`User`""" + user_groups = user.groups.all() + lists = self.filter(recipient__in=user_groups) + user_ids = lists.values_list( + 'senders__id', flat=True + ).distinct() + return User.objects.filter(id__in=user_ids) + +class SenderList(models.Model): + """a model to store denormalized data + about who sends messages to any given person + sender list is populated automatically + as new messages are created + """ + recipient = models.ForeignKey(Group, unique=True) + senders = models.ManyToManyField(User) + objects = SenderListManager() + + +class MessageMemo(models.Model): + """A bridge between message recipients and messages + these records are only created when user sees a message. + The idea is that using groups as recipients, we can send + messages to massive numbers of users, without cluttering + the database. + + Instead we'll be creating a "seen" message after user + reads the message. + """ + SEEN = 0 + ARCHIVED = 1 + STATUS_CHOICES = ( + (SEEN, 'seen'), + (ARCHIVED, 'archived') + + ) + user = models.ForeignKey(User) + message = models.ForeignKey('Message') + status = models.SmallIntegerField( + choices=STATUS_CHOICES, default=SEEN + ) + + class Meta: + unique_together = ('user', 'message') + + +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 create(self, **kwargs): + """creates a message""" + root = kwargs.get('root', None) + if root is None: + parent = kwargs.get('parent', None) + if parent: + if parent.root: + root = parent.root + else: + root = parent + kwargs['root'] = root + + headline = kwargs.get('headline', kwargs['text']) + kwargs['headline'] = headline[:MAX_TITLE_LENGTH] + kwargs['html'] = parse_message(kwargs['text']) + return super(MessageManager, self).create(**kwargs) + + def create_thread(self, sender=None, recipients=None, text=None): + """creates a stored message and adds recipients""" + message = self.create( + message_type=Message.STORED, + sender=sender, + text=text, + ) + message.add_recipients(recipients) + return message + + def create_response(self, sender=None, text=None, parent=None): + message = self.create( + parent=parent, + message_type=Message.STORED, + sender=sender, + text=text, + ) + #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) + message.add_recipients(recipients, ignore_user=sender) + #add author of the parent as a recipient to parent + #but make sure to mute the message + parent.add_recipients([senders_group], ignore_user=parent.sender) + return message + + +class Message(models.Model): + """the message model allowing users to send + messages to other users and groups, via + personal groups. + """ + STORED = 0 + TEMPORARY = 1 + ONE_TIME = 2 + MESSAGE_TYPE_CHOICES = ( + (STORED, 'email-like message, stored in the inbox'), + (ONE_TIME, 'will be shown just once'), + (TEMPORARY, 'will be shown until certain time') + ) + + message_type = models.SmallIntegerField( + choices=MESSAGE_TYPE_CHOICES, + default=STORED, + ) + + sender = models.ForeignKey(User, related_name='sent_messages') + recipients = models.ManyToManyField(Group) + root = models.ForeignKey( + 'self', null=True, + blank=True, related_name='descendants' + ) + parent = models.ForeignKey( + 'self', null=True, + blank=True, related_name='children' + ) + headline = models.CharField(max_length=MAX_TITLE_LENGTH) + text = models.TextField( + null=True, blank=True, + help_text='source text for the message, e.g. in markdown format' + ) + html = models.TextField( + null=True, blank=True, + help_text='rendered html of the message' + ) + sent_at = models.DateTimeField(auto_now_add=True) + last_active_at = models.DateTimeField(auto_now_add=True) + active_until = models.DateTimeField(blank=True, null=True) + + objects = MessageManager() + + def add_recipients(self, recipients, ignore_user=None): + """adds recipients to the message + and updates the sender lists for all recipients + todo: sender lists may be updated in a lazy way - per user + + `ignore_user` parameter is used to mark a specific user + as not needing to receive a message as new, even if that + user is a member of any of the recipient groups + """ + if ignore_user: + #crate a "seen" memo for the sender, because we + #don't want to inform the user about his/her own post + MessageMemo.objects.create( + message=self, user=self.sender, status=MessageMemo.SEEN + ) + + self.recipients.add(*recipients) + for recipient in recipients: + sender_list, created = SenderList.objects.get_or_create(recipient=recipient) + sender_list.senders.add(self.sender) diff --git a/group_messaging/tests.py b/group_messaging/tests.py new file mode 100644 index 00000000..80f6f792 --- /dev/null +++ b/group_messaging/tests.py @@ -0,0 +1,108 @@ +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 get_personal_group +from group_messaging.models import create_personal_group + +MESSAGE_TEXT = 'test message text' + +def create_user(name): + """creates a user and a personal group, + returns the created user""" + user = User.objects.create_user(name, name + '@example.com') + #note that askbot will take care of three lines below automatically + try: + group = get_personal_group(user) + except Group.DoesNotExist: + group = create_personal_group(user) + group_name = '_personal_%d' % user.id + group, created = Group.objects.get_or_create(name=group_name) + user.groups.add(group) + return user + +class ModelTests(TestCase): + """test cases for the `private_messaging` models""" + + def setUp(self): + self.sender = create_user('sender') + self.recipient = create_user('recipient') + + def create_thread(self, recipients): + return Message.objects.create_thread( + sender=self.sender, recipients=recipients, + text=MESSAGE_TEXT + ) + + def create_thread_for_user(self, user): + group = get_personal_group(user) + return self.create_thread([group]) + + 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 type is stored + self.assertEqual(message.message_type, Message.STORED) + #recipient is in the list of recipients + recipients = set(message.recipients.all()) + recipient_group = get_personal_group(self.recipient) + #sender_group = get_personal_group(self.sender) #maybe add this too + expected_recipients = set([recipient_group]) + self.assertEqual(recipients, expected_recipients) + self.assertRaises( + MessageMemo.DoesNotExist, + MessageMemo.objects.get, + message=message + ) + + def test_get_senders_for_user(self): + """this time send thread to a real group test that + 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]) + senders = SenderList.objects.get_senders_for_user(self.recipient) + self.assertEqual(set(senders), set([self.sender])) + + def test_create_thread_response(self): + """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) + response = Message.objects.create_response( + sender=self.recipient, + text='some response', + parent=root_message + ) + self.assertEqual(response.message_type, Message.STORED) + #assert that there is only one "seen" memo for the response + memos = MessageMemo.objects.filter(message=response) + self.assertEqual(memos.count(), 1) + self.assertEqual(memos[0].user, self.recipient) + self.assertEqual(memos[0].status, MessageMemo.SEEN) + #assert that recipients are the two people who are part of + #this conversation + recipients = set(response.recipients.all()) + sender_group = get_personal_group(self.sender) + recipient_group = get_personal_group(self.recipient) + 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)) + self.assertEqual(threads, set([])) + threads = set(Message.objects.get_threads_for_user(self.recipient)) + self.assertEqual(threads, set([root_message])) + + response = Message.objects.create_response( + sender=self.recipient, + text='some response', + parent=root_message + ) + threads = set(Message.objects.get_threads_for_user(self.sender)) + self.assertEqual(threads, set([root_message])) + threads = set(Message.objects.get_threads_for_user(self.recipient)) + self.assertEqual(threads, set([root_message])) diff --git a/group_messaging/views.py b/group_messaging/views.py new file mode 100644 index 00000000..d24833f2 --- /dev/null +++ b/group_messaging/views.py @@ -0,0 +1,148 @@ +"""semi-views for the `group_messaging` application +These are not really views - rather context generator +functions, to be used separately, when needed. + +For example, some other application can call these +in order to render messages within the page. + +Notice that :mod:`urls` module decorates all these functions +and turns them into complete views +""" +import functools +from django.contrib.auth.models import Group +from django.core.exceptions import PermissionDenied +from django.forms import IntegerField +from django.http import HttpResponse +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.views.decorators.http import require_GET +from django.views.decorators.http import require_POST +from django.utils import simplejson +from group_messages.models import Message +from group_messages.models import MessageMemo +from group_messages.models import SenderList +from group_messages.models import get_personal_group_by_id + +class InboxView(object): + """custom class-based view + to be used for pjax use and for generation + of content in the traditional way""" + template_name = None #this needs to be set + + def get(self, request, *args, **kwargs): + context = self.get_context(request, *args, **kwargs) + #todo: load template with Coffin and render it + return HttpResponse(json, mimetype='application/json') + + def get_context(self, request, *args, **kwargs): + """Should return the context dictionary""" + return {} + + def as_pjax(self): + """returns the view function - for the urls.py""" + def view_function(request, *args, **kwargs): + """the actual view function""" + if request.user.is_anonymous(): + raise PermissionDenied() + if request.is_ajax() is False: + raise PermissionDenied() + + if request.method == 'GET': + return self.get(request, *args, **kwargs) + elif request.method == 'POST': + return self.post(request, *args, **kwargs) + else: + raise NotImplementedError + return view_function + + +def require_login(view_func): + @functools.wraps(view_func) + def wrapped(request, *args, **kwargs): + if request.user.is_authenticated(): + return view_func(request, *args, **kwargs) + else: + raise PermissionDenied() + return wrapped + + +def ajax(view_func): + @functools.wraps(view_func) + def wrapped(request, *args, **kwargs): + if request.is_ajax(): + result = view_func(request, *args, **kwargs) + json = simplejson.dumps(result) + return HttpResponse(json, mimetype='application/json') + else: + raise PermissionDenied() + + +class NewThread(InboxView): + template_name = 'new_thread.html' + + def post(self, request): + recipient_id = IntegerField().clean(request.POST['recipient_id']) + recipient = get_personal_group_by_id(recipient_id) + message = Message.objects.create_thread( + sender=request.user, + recipients=[recipient], + text=request.POST['text'] + ) + return {'message_id': message.id} + + +class NewResponse(InboxView): + def get(self, request): + raise PermissionDenied() + + def post(self, request): + parent_id = IntegerField().clean(request.POST['parent_id']) + parent = Message.objects.get(id=parent_id) + message = Message.objects.create_response( + sender=request.user, + text=request.POST['text'], + parent=parent + ) + +class ThreadsList(InboxView): + """shows list of threads for a given user""" + template_name = 'threads_list.html' + + def get_context(self, request): + """returns thread list data""" + if request.method != 'GET': + raise PermissionDenied() + + threads = Message.objects.get_threads_for_user(request.user) + threads = threads.values('id', 'headline', 'is_read') + return {'threads': threads} + + +class SendersList(InboxView): + """shows list of senders for a user""" + template_name = 'senders_list.html' + + def get_context(request): + """get data about senders for the user""" + if request.method != 'GET': + raise PermissionDenied() + + senders = SenderList.objects.get_senders_for_user(request.user) + senders = senders.values('id', 'username') + return {'senders': senders} + + +class ThreadDetails(InboxView): + """shows entire thread in the unfolded form""" + template_name = 'thread_details.html' + + def get_context(request): + """shows individual thread""" + if request.method != 'GET': + raise PermissionDenied() + + thread_id = IntegerField().clean(request.GET['thread_id']) + #todo: assert that current thread is the root + messages = Message.objects.filter(root__id=thread_id) + messages = messages.values('html') + return {'messages': messages} -- cgit v1.2.3-1-g7c22