summaryrefslogtreecommitdiffstats
path: root/group_messaging
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-11-18 04:28:39 -0300
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-11-18 04:28:39 -0300
commitba1ef99a5e55d2a08bb1ed7176dff6fb9abd9134 (patch)
treee8325dfb18d98fd4719babd16c6c6d6ad68edb77 /group_messaging
parent93019fdd50974f3034b944e74953c4380319f65c (diff)
downloadaskbot-ba1ef99a5e55d2a08bb1ed7176dff6fb9abd9134.tar.gz
askbot-ba1ef99a5e55d2a08bb1ed7176dff6fb9abd9134.tar.bz2
askbot-ba1ef99a5e55d2a08bb1ed7176dff6fb9abd9134.zip
temporarily moved group_messaging files to askbot/deps
Diffstat (limited to 'group_messaging')
-rw-r--r--group_messaging/__init__.py17
-rw-r--r--group_messaging/migrations/0001_initial.py177
-rw-r--r--group_messaging/migrations/0002_auto__add_lastvisittime__add_unique_lastvisittime_user_message__add_fi.py142
-rw-r--r--group_messaging/migrations/__init__.py0
-rw-r--r--group_messaging/models.py438
-rw-r--r--group_messaging/tests.py363
-rw-r--r--group_messaging/urls.py37
-rw-r--r--group_messaging/views.py286
8 files changed, 0 insertions, 1460 deletions
diff --git a/group_messaging/__init__.py b/group_messaging/__init__.py
deleted file mode 100644
index ed3d73ff..00000000
--- a/group_messaging/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""`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_<user id>, 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.
-
-TODO: decouple this application
-first step is to package send_mail separately
-"""
diff --git a/group_messaging/migrations/0001_initial.py b/group_messaging/migrations/0001_initial.py
deleted file mode 100644
index 7d907dc1..00000000
--- a/group_messaging/migrations/0001_initial.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# -*- 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/0002_auto__add_lastvisittime__add_unique_lastvisittime_user_message__add_fi.py b/group_messaging/migrations/0002_auto__add_lastvisittime__add_unique_lastvisittime_user_message__add_fi.py
deleted file mode 100644
index 5e92ef2b..00000000
--- a/group_messaging/migrations/0002_auto__add_lastvisittime__add_unique_lastvisittime_user_message__add_fi.py
+++ /dev/null
@@ -1,142 +0,0 @@
-# -*- 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 'LastVisitTime'
- db.create_table('group_messaging_lastvisittime', (
- ('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'])),
- ('at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
- ))
- db.send_create_signal('group_messaging', ['LastVisitTime'])
-
- # Adding unique constraint on 'LastVisitTime', fields ['user', 'message']
- db.create_unique('group_messaging_lastvisittime', ['user_id', 'message_id'])
-
- # Adding field 'Message.senders_info'
- db.add_column('group_messaging_message', 'senders_info',
- self.gf('django.db.models.fields.CharField')(default='', max_length=64),
- keep_default=False)
-
- def backwards(self, orm):
- # Removing unique constraint on 'LastVisitTime', fields ['user', 'message']
- db.delete_unique('group_messaging_lastvisittime', ['user_id', 'message_id'])
-
- # Deleting model 'LastVisitTime'
- db.delete_table('group_messaging_lastvisittime')
-
- # Deleting field 'Message.senders_info'
- db.delete_column('group_messaging_message', 'senders_info')
-
- 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.lastvisittime': {
- 'Meta': {'unique_together': "(('user', 'message'),)", 'object_name': 'LastVisitTime'},
- 'at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group_messaging.Message']"}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
- },
- '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']"}),
- 'senders_info': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64'}),
- '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
deleted file mode 100644
index e69de29b..00000000
--- a/group_messaging/migrations/__init__.py
+++ /dev/null
diff --git a/group_messaging/models.py b/group_messaging/models.py
deleted file mode 100644
index 5d43baf8..00000000
--- a/group_messaging/models.py
+++ /dev/null
@@ -1,438 +0,0 @@
-"""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_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)
-
-
-def get_personal_groups_for_users(users):
- """for a given list of users return their personal groups"""
- group_names = [(GROUP_NAME_TPL % user.id) for user in users]
- return Group.objects.filter(name__in=group_names)
-
-
-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=GROUP_NAME_TPL % user.id)
- group.save()
- return group
-
-
-class LastVisitTime(models.Model):
- """just remembers when a user has
- last visited a given thread
- """
- user = models.ForeignKey(User)
- message = models.ForeignKey('Message')
- at = models.DateTimeField(auto_now_add=True)
-
- class Meta:
- unique_together = ('user', 'message')
-
-
-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', related_name='memos')
- 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_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"""
- 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_HEADLINE_LENGTH]
- kwargs['html'] = parse_message(kwargs['text'])
-
- message = super(MessageManager, self).create(**kwargs)
- #creator of message saw it by definition
- #crate a "seen" memo for the sender, because we
- #don't want to inform the user about his/her own post
- sender = kwargs['sender']
- MessageMemo.objects.create(
- message=message, user=sender, status=MessageMemo.SEEN
- )
- return message
-
- 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,
- 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):
- 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())
-
- 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
- #update headline
- message.root.headline = text[:MAX_HEADLINE_LENGTH]
- #mark last active timestamp for the root message
- 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
-
-
-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')
-
- senders_info = models.CharField(
- max_length=MAX_SENDERS_INFO_LENGTH,
- default=''
- )#comma-separated list of a few names
-
- 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_HEADLINE_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_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
- todo: sender lists may be updated in a lazy way - per user
- """
- self.recipients.add(*recipients)
- for recipient in recipients:
- sender_list, created = SenderList.objects.get_or_create(recipient=recipient)
- sender_list.senders.add(self.sender)
-
- 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('&', '&amp;')
- #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
- """
- 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.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
deleted file mode 100644
index bcf764db..00000000
--- a/group_messaging/tests.py
+++ /dev/null
@@ -1,363 +0,0 @@
-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'
-
-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
-
-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, sender, recipient_groups):
- return Message.objects.create_thread(
- sender=sender, recipients=recipient_groups,
- text=MESSAGE_TEXT
- )
-
- 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('&amp;', '&')
- 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.sender, 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
- #)
- #make sure that the original senders memo to the root
- #message is marke ad seen
- memos = MessageMemo.objects.filter(
- message=message,
- user=self.sender
- )
- self.assertEquals(memos.count(), 1)
- self.assertEqual(memos[0].status, MessageMemo.SEEN)
-
- 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(self.sender, [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.sender, 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(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(recipient=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(recipient=self.sender))
- self.assertEqual(threads, set([root_message]))
- 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('&amp;', '&'))
- 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
deleted file mode 100644
index 19ee35bb..00000000
--- a/group_messaging/urls.py
+++ /dev/null
@@ -1,37 +0,0 @@
-"""url configuration for the group_messaging application"""
-from django.conf.urls.defaults import patterns
-from django.conf.urls.defaults import url
-from group_messaging import views
-
-urlpatterns = patterns('',
- url(
- '^threads/$',
- views.ThreadsList().as_view(),
- name='get_threads'
- ),
- url(
- '^threads/(?P<thread_id>\d+)/$',
- views.ThreadDetails().as_view(),
- 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'
- ),
- url(
- '^senders/$',
- views.SendersList().as_view(),
- name='get_senders'
- ),
- url(
- '^post-reply/$',
- views.PostReply().as_view(),
- name='post_reply'
- )
-)
diff --git a/group_messaging/views.py b/group_messaging/views.py
deleted file mode 100644
index 244762d1..00000000
--- a/group_messaging/views.py
+++ /dev/null
@@ -1,286 +0,0 @@
-"""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 copy
-import datetime
-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
-from group_messaging.models import get_personal_groups_for_users
-
-class InboxView(object):
- """custom class-based view
- to be used for pjax use and for generation
- of content in the traditional way, where
- the only the :method:`get_context` would be used.
- """
- template_name = None #used only for the "GET" method
- http_method_names = ('GET', 'POST')
-
- def render_to_response(self, context, template_name=None):
- """like a django's shortcut, except will use
- template_name from self, if `template_name` is not given.
- Also, response is packaged as json with an html fragment
- for the pjax consumption
- """
- if template_name is None:
- template_name = self.template_name
- template = get_template(template_name)
- html = template.render(context)
- json = simplejson.dumps({'html': html, 'success': True})
- return HttpResponse(json, mimetype='application/json')
-
-
- def get(self, request, *args, **kwargs):
- """view function for the "GET" method"""
- context = self.get_context(request, *args, **kwargs)
- return self.render_to_response(context)
-
- def post(self, request, *args, **kwargs):
- """view function for the "POST" method"""
- pass
-
- def dispatch(self, request, *args, **kwargs):
- """checks that the current request method is allowed
- and calls the corresponding view function"""
- if request.method not in self.http_method_names:
- return HttpResponseNotAllowed()
- view_func = getattr(self, request.method.lower())
- return view_func(request, *args, **kwargs)
-
- def get_context(self, request, *args, **kwargs):
- """Returns the context dictionary for the "get"
- method only"""
- return {}
-
- def as_view(self):
- """returns the view function - for the urls.py"""
- def view_function(request, *args, **kwargs):
- """the actual view function"""
- if request.user.is_authenticated() and request.is_ajax():
- view_method = getattr(self, request.method.lower())
- return view_method(request, *args, **kwargs)
- else:
- return HttpResponseForbidden()
-
- return view_function
-
-
-class NewThread(InboxView):
- """view for creation of new thread"""
- http_method_list = ('POST',)
-
- def post(self, request):
- """creates a new thread on behalf of the user
- response is blank, because on the client side we just
- need to go back to the thread listing view whose
- content should be cached in the client'
- """
- usernames = request.POST['to_usernames']
- usernames = map(lambda v: v.strip(), usernames.split(','))
- users = User.objects.filter(username__in=usernames)
-
- missing = copy.copy(usernames)
- for user in users:
- if user.username in missing:
- missing.remove(user.username)
-
- result = dict()
- if missing:
- result['success'] = False
- result['missing_users'] = missing
-
- 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,
- recipients=recipients,
- text=request.POST['text']
- )
- result['success'] = True
- result['message_id'] = message.id
- return HttpResponse(simplejson.dumps(result), mimetype='application/json')
-
-
-class PostReply(InboxView):
- """view to create a new response"""
- http_method_list = ('POST',)
-
- 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
- )
- last_visit = LastVisitTime.objects.get(
- message=message.root,
- user=request.user
- )
- last_visit.at = datetime.datetime.now()
- last_visit.save()
- return self.render_to_response(
- {'post': message, 'user': request.user},
- 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'
- http_method_list = ('GET',)
-
- def get_context(self, request):
- """returns thread list data"""
- #get threads and the last visit time
- 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
- )
-
- 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
- threads_data = dict()
- for thread in threads:
- thread_data = dict()
- #determine status
- thread_data['status'] = 'new'
- #determine the senders info
- senders_names = thread.senders_info.split(',')
- if request.user.username in senders_names:
- senders_names.remove(request.user.username)
- thread_data['senders_info'] = ', '.join(senders_names)
- 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
- )
- for last_visit in last_visit_times:
- thread_data = threads_data[last_visit.message_id]
- if thread_data['thread'].last_active_at <= last_visit.at:
- thread_data['status'] = 'seen'
-
- 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):
- """shows list of senders for a user"""
- template_name = 'group_messaging/senders_list.html'
- http_method_names = ('GET',)
-
- def get_context(self, request):
- """get data about senders for the user"""
- senders = SenderList.objects.get_senders_for_user(request.user)
- senders = senders.values('id', 'username')
- return {'senders': senders, 'request_user_id': request.user.id}
-
-
-class ThreadDetails(InboxView):
- """shows entire thread in the unfolded form"""
- template_name = 'group_messaging/thread_details.html'
- http_method_names = ('GET',)
-
- def get_context(self, request, thread_id=None):
- """shows individual thread"""
- #todo: assert that current thread is the root
- root = Message.objects.get(id=thread_id)
- responses = Message.objects.filter(root__id=thread_id)
- last_visit, created = LastVisitTime.objects.get_or_create(
- message=root,
- user=request.user
- )
- if created is False:
- last_visit.at = datetime.datetime.now()
- last_visit.save()
- return {'root_message': root, 'responses': responses, 'request': request}