diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-11-18 04:28:39 -0300 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-11-18 04:28:39 -0300 |
commit | ba1ef99a5e55d2a08bb1ed7176dff6fb9abd9134 (patch) | |
tree | e8325dfb18d98fd4719babd16c6c6d6ad68edb77 /group_messaging | |
parent | 93019fdd50974f3034b944e74953c4380319f65c (diff) | |
download | askbot-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__.py | 17 | ||||
-rw-r--r-- | group_messaging/migrations/0001_initial.py | 177 | ||||
-rw-r--r-- | group_messaging/migrations/0002_auto__add_lastvisittime__add_unique_lastvisittime_user_message__add_fi.py | 142 | ||||
-rw-r--r-- | group_messaging/migrations/__init__.py | 0 | ||||
-rw-r--r-- | group_messaging/models.py | 438 | ||||
-rw-r--r-- | group_messaging/tests.py | 363 | ||||
-rw-r--r-- | group_messaging/urls.py | 37 | ||||
-rw-r--r-- | group_messaging/views.py | 286 |
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('&', '&') - #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('&', '&') - 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('&', '&')) - 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} |