[schema][manual] Automatically subscribe users to default streams only after tutorial

(imported from commit 6511851c0aee2628bef597bf1310d6f96b0fd1d4)
This commit is contained in:
Leo Franchi 2013-04-04 16:30:28 -04:00
parent 7ce6154464
commit 8fe82085c4
9 changed files with 505 additions and 61 deletions

View File

@ -89,6 +89,7 @@ urlpatterns += patterns('zephyr.views',
url(r'^json/update_active_status$', 'json_update_active_status'), url(r'^json/update_active_status$', 'json_update_active_status'),
url(r'^json/get_active_statuses$', 'json_get_active_statuses'), url(r'^json/get_active_statuses$', 'json_get_active_statuses'),
url(r'^json/tutorial_send_message$', 'json_tutorial_send_message'), url(r'^json/tutorial_send_message$', 'json_tutorial_send_message'),
url(r'^json/tutorial_status$', 'json_tutorial_status'),
url(r'^json/change_enter_sends$', 'json_change_enter_sends'), url(r'^json/change_enter_sends$', 'json_change_enter_sends'),
url(r'^json/get_profile$', 'json_get_profile'), url(r'^json/get_profile$', 'json_get_profile'),
url(r'^json/report_error$', 'json_report_error'), url(r'^json/report_error$', 'json_report_error'),

View File

@ -5,7 +5,7 @@ from zephyr.models import Realm, Stream, UserProfile, UserActivity, \
Subscription, Recipient, Message, UserMessage, valid_stream_name, \ Subscription, Recipient, Message, UserMessage, valid_stream_name, \
DefaultStream, StreamColor, UserPresence, MAX_SUBJECT_LENGTH, \ DefaultStream, StreamColor, UserPresence, MAX_SUBJECT_LENGTH, \
MAX_MESSAGE_LENGTH, get_client, get_stream, get_recipient, get_huddle, \ MAX_MESSAGE_LENGTH, get_client, get_stream, get_recipient, get_huddle, \
get_user_profile_by_id get_user_profile_by_id, PreregistrationUser
from django.db import transaction, IntegrityError from django.db import transaction, IntegrityError
from django.db.models import F from django.db.models import F
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -708,6 +708,23 @@ def subscribed_to_stream(user_profile, stream):
except Subscription.DoesNotExist: except Subscription.DoesNotExist:
return False return False
def do_finish_tutorial(user_profile):
user_profile.tutorial_status = UserProfile.TUTORIAL_FINISHED
user_profile.save()
# We want to add the default subs list iff there were no subs
try:
prereg_user = PreregistrationUser.objects.get(email=user_profile.email)
except PreregistrationUser.DoesNotExist:
return
streams = prereg_user.streams.all()
if len(streams) == 0:
add_default_subs(user_profile)
else:
for stream in streams:
do_add_subscription(user_profile, stream)
def gather_subscriptions(user_profile): def gather_subscriptions(user_profile):
# This is a little awkward because the StreamColor table has foreign keys # This is a little awkward because the StreamColor table has foreign keys
# to Subscription, but not vice versa, and not all Subscriptions have a # to Subscription, but not vice versa, and not all Subscriptions have a

View File

@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
from django.conf import settings
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'UserProfile.tutorial_status'
db.add_column('zephyr_userprofile', 'tutorial_status',
self.gf('django.db.models.fields.CharField')(default='W', max_length=1),
keep_default=False)
if "postgres" in settings.DATABASES["default"]["ENGINE"]:
db.execute("ALTER TABLE zephyr_userprofile ALTER COLUMN tutorial_status SET DEFAULT 'W'")
def backwards(self, orm):
# Deleting field 'UserProfile.tutorial_status'
db.delete_column('zephyr_userprofile', 'tutorial_status')
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'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'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'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'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': '30'})
},
'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'})
},
'zephyr.client': {
'Meta': {'object_name': 'Client'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30', 'db_index': 'True'})
},
'zephyr.defaultstream': {
'Meta': {'unique_together': "(('realm', 'stream'),)", 'object_name': 'DefaultStream'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Realm']"}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Stream']"})
},
'zephyr.huddle': {
'Meta': {'object_name': 'Huddle'},
'huddle_hash': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'zephyr.message': {
'Meta': {'object_name': 'Message'},
'content': ('django.db.models.fields.TextField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'pub_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Recipient']"}),
'rendered_content': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'rendered_content_version': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']"}),
'sending_client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Client']"}),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '60', 'db_index': 'True'})
},
'zephyr.mituser': {
'Meta': {'object_name': 'MitUser'},
'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'status': ('django.db.models.fields.IntegerField', [], {'default': '0'})
},
'zephyr.preregistrationuser': {
'Meta': {'object_name': 'PreregistrationUser'},
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'invited_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'referred_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']", 'null': 'True'}),
'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['zephyr.Stream']", 'null': 'True', 'symmetrical': 'False'})
},
'zephyr.realm': {
'Meta': {'object_name': 'Realm'},
'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'restricted_to_domain': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'zephyr.recipient': {
'Meta': {'unique_together': "(('type', 'type_id'),)", 'object_name': 'Recipient'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'db_index': 'True'}),
'type_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'})
},
'zephyr.stream': {
'Meta': {'unique_together': "(('name', 'realm'),)", 'object_name': 'Stream'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'invite_only': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'db_index': 'True'}),
'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Realm']"})
},
'zephyr.streamcolor': {
'Meta': {'object_name': 'StreamColor'},
'color': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Subscription']"})
},
'zephyr.subscription': {
'Meta': {'unique_together': "(('user_profile', 'recipient'),)", 'object_name': 'Subscription'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'in_home_view': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Recipient']"}),
'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']"})
},
'zephyr.useractivity': {
'Meta': {'unique_together': "(('user_profile', 'client', 'query'),)", 'object_name': 'UserActivity'},
'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Client']"}),
'count': ('django.db.models.fields.IntegerField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_visit': ('django.db.models.fields.DateTimeField', [], {}),
'query': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']"})
},
'zephyr.usermessage': {
'Meta': {'unique_together': "(('user_profile', 'message'),)", 'object_name': 'UserMessage'},
'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'flags': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Message']"}),
'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']"})
},
'zephyr.userpresence': {
'Meta': {'unique_together': "(('user_profile', 'client'),)", 'object_name': 'UserPresence'},
'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Client']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'status': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {}),
'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']"})
},
'zephyr.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'api_key': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'enable_desktop_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'enter_sends': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}),
'full_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_pointer_updater': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'pointer': ('django.db.models.fields.IntegerField', [], {}),
'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Realm']"}),
'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'tutorial_status': ('django.db.models.fields.CharField', [], {'default': "'W'", 'max_length': '1'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
}
}
complete_apps = ['zephyr']

View File

@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
orm.UserProfile.objects.all().update(tutorial_status="F")
def backwards(self, orm):
"Write your backwards methods here."
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'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'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'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'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': '30'})
},
'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'})
},
'zephyr.client': {
'Meta': {'object_name': 'Client'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30', 'db_index': 'True'})
},
'zephyr.defaultstream': {
'Meta': {'unique_together': "(('realm', 'stream'),)", 'object_name': 'DefaultStream'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Realm']"}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Stream']"})
},
'zephyr.huddle': {
'Meta': {'object_name': 'Huddle'},
'huddle_hash': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'zephyr.message': {
'Meta': {'object_name': 'Message'},
'content': ('django.db.models.fields.TextField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'pub_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Recipient']"}),
'rendered_content': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'rendered_content_version': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']"}),
'sending_client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Client']"}),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '60', 'db_index': 'True'})
},
'zephyr.mituser': {
'Meta': {'object_name': 'MitUser'},
'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'status': ('django.db.models.fields.IntegerField', [], {'default': '0'})
},
'zephyr.preregistrationuser': {
'Meta': {'object_name': 'PreregistrationUser'},
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'invited_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'referred_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']", 'null': 'True'}),
'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['zephyr.Stream']", 'null': 'True', 'symmetrical': 'False'})
},
'zephyr.realm': {
'Meta': {'object_name': 'Realm'},
'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'restricted_to_domain': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'zephyr.recipient': {
'Meta': {'unique_together': "(('type', 'type_id'),)", 'object_name': 'Recipient'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'db_index': 'True'}),
'type_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'})
},
'zephyr.stream': {
'Meta': {'unique_together': "(('name', 'realm'),)", 'object_name': 'Stream'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'invite_only': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'db_index': 'True'}),
'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Realm']"})
},
'zephyr.streamcolor': {
'Meta': {'object_name': 'StreamColor'},
'color': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Subscription']"})
},
'zephyr.subscription': {
'Meta': {'unique_together': "(('user_profile', 'recipient'),)", 'object_name': 'Subscription'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'in_home_view': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Recipient']"}),
'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']"})
},
'zephyr.useractivity': {
'Meta': {'unique_together': "(('user_profile', 'client', 'query'),)", 'object_name': 'UserActivity'},
'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Client']"}),
'count': ('django.db.models.fields.IntegerField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_visit': ('django.db.models.fields.DateTimeField', [], {}),
'query': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']"})
},
'zephyr.usermessage': {
'Meta': {'unique_together': "(('user_profile', 'message'),)", 'object_name': 'UserMessage'},
'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'flags': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Message']"}),
'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']"})
},
'zephyr.userpresence': {
'Meta': {'unique_together': "(('user_profile', 'client'),)", 'object_name': 'UserPresence'},
'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Client']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'status': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {}),
'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.UserProfile']"})
},
'zephyr.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'api_key': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'enable_desktop_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'enter_sends': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}),
'full_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_pointer_updater': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'pointer': ('django.db.models.fields.IntegerField', [], {}),
'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['zephyr.Realm']"}),
'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'tutorial_status': ('django.db.models.fields.CharField', [], {'default': "'W'", 'max_length': '1'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
}
}
complete_apps = ['zephyr']
symmetrical = True

View File

@ -69,6 +69,19 @@ class UserProfile(AbstractBaseUser):
enable_desktop_notifications = models.BooleanField(default=True) enable_desktop_notifications = models.BooleanField(default=True)
enter_sends = models.NullBooleanField(default=False) enter_sends = models.NullBooleanField(default=False)
TUTORIAL_WAITING = 'W'
TUTORIAL_STARTED = 'S'
TUTORIAL_FINISHED = 'F'
TUTORIAL_STATES = ((TUTORIAL_WAITING, "Waiting"),
(TUTORIAL_STARTED, "Started"),
(TUTORIAL_FINISHED, "Finished"))
tutorial_status = models.CharField(default=TUTORIAL_WAITING, choices=TUTORIAL_STATES, max_length=1)
def tutorial_stream_name(self):
return "tutorial-%s" % \
(self.email.split('@')[0],)[:Stream.MAX_NAME_LENGTH]
objects = UserManager() objects = UserManager()
def __repr__(self): def __repr__(self):

View File

@ -438,29 +438,66 @@ exports.get_invite_only = function (stream_name) {
return sub.invite_only; return sub.invite_only;
}; };
function populate_subscriptions(subs) {
var sub_rows = [];
subs.sort(function (a, b) {
return a.name.localeCompare(b.name);
});
subs.forEach(function (elem) {
var stream_name = elem.name;
var sub = create_sub(stream_name, {color: elem.color, in_home_view: elem.in_home_view,
invite_only: elem.invite_only, subscribed: true});
add_sub(stream_name, sub);
sub_rows.push(sub);
});
return sub_rows;
}
exports.reload_subscriptions = function (opts) {
var on_success;
opts = $.extend({}, {clear_first: false, custom_callbacks: false}, opts);
if (! opts.custom_callbacks) {
on_success = function (data) {
if (data) {
populate_subscriptions(data.subscriptions);
}
};
}
if (opts.clear_first) {
stream_info = [];
ui.remove_all_narrow_filters();
}
return $.ajax({
type: 'POST',
url: '/json/subscriptions/list',
dataType: 'json',
timeout: 10*1000,
success: on_success
});
};
exports.setup_page = function () { exports.setup_page = function () {
util.make_loading_indicator($('#subs_page_loading_indicator')); util.make_loading_indicator($('#subs_page_loading_indicator'));
var our_subs; function populate_and_fill(stream_data, subscription_data) {
var all_streams; var all_streams = [];
var our_subs = [];
function maybe_populate_subscriptions() { /* arguments are [ "success", statusText, jqXHR ] */
// We only execute if both asynchronous queries have returned if (stream_data.length > 2 && stream_data[2]) {
if (our_subs === undefined || all_streams === undefined) { var stream_response = JSON.parse(stream_data[2].responseText);
return; all_streams = stream_response.streams;
}
if (subscription_data.length > 2 && subscription_data[2]) {
var subs_response = JSON.parse(subscription_data[2].responseText);
our_subs = subs_response.subscriptions;
} }
var sub_rows = []; var sub_rows = populate_subscriptions(our_subs);
our_subs.sort(function (a, b) {
return a.name.localeCompare(b.name);
});
our_subs.forEach(function (elem) {
var stream_name = elem.name;
var sub = create_sub(stream_name, {color: elem.color, in_home_view: elem.in_home_view,
invite_only: elem.invite_only, subscribed: true});
add_sub(stream_name, sub);
sub_rows.push(sub);
});
all_streams.sort(); all_streams.sort();
all_streams.forEach(function (stream) { all_streams.forEach(function (stream) {
@ -480,46 +517,35 @@ exports.setup_page = function () {
$('#create_stream_name').focus().select(); $('#create_stream_name').focus().select();
} }
function failed_listing(xhr, error) {
util.destroy_loading_indicator($('#subs_page_loading_indicator'));
ui.report_error("Error listing streams or subscriptions", xhr, $("#subscriptions-status"));
}
var requests = [];
if (should_list_all_streams()) { if (should_list_all_streams()) {
// This query must go first to prevent a race when we are not // This query must go first to prevent a race when we are not
// listing all streams // listing all streams
$.ajax({ var req = $.ajax({
type: 'POST', type: 'POST',
url: '/json/get_public_streams', url: '/json/get_public_streams',
dataType: 'json', dataType: 'json',
timeout: 10*1000, timeout: 10*1000
success: function (data) {
if (data) {
all_streams = data.streams;
maybe_populate_subscriptions();
}
},
error: function (xhr) {
util.destroy_loading_indicator($('#subs_page_loading_indicator'));
ui.report_error("Error listing subscriptions", xhr, $("#subscriptions-status"));
}
}); });
requests.push(req);
} else { } else {
all_streams = []; // Handing an object to $.when() means that it counts as a 'success' with the
// object delivered directly to the callback
requests.push({streams: []});
$('#create_stream_button').val("Subscribe"); $('#create_stream_button').val("Subscribe");
} }
$.ajax({ requests.push(exports.reload_subscriptions({custom_callbacks: true}));
type: 'POST',
url: '/json/subscriptions/list', // Trigger finished callback when:
dataType: 'json', // * Both AJAX requests are finished, if we sent themm both
timeout: 10*1000, // * Just one AJAX is finished if should_list_all_streams() is false
success: function (data) { $.when.apply(this, requests).then(populate_and_fill, failed_listing);
if (data) {
our_subs = data.subscriptions;
maybe_populate_subscriptions();
}
},
error: function (xhr) {
util.destroy_loading_indicator($('#subs_page_loading_indicator'));
ui.report_error("Error listing subscriptions", xhr, $("#subscriptions-status"));
}
});
}; };
exports.have = function (stream_name) { exports.have = function (stream_name) {

View File

@ -240,6 +240,13 @@ exports.start = function () {
tutorial_running = true; tutorial_running = true;
add_to_tutorial_stream(); add_to_tutorial_stream();
run_tutorial(0); run_tutorial(0);
$.ajax({
type: 'POST',
url: '/json/tutorial_status',
data: {status: 'started'}
});
}; };
// This technique is not actually that awesome, because it's pretty // This technique is not actually that awesome, because it's pretty
@ -256,6 +263,17 @@ exports.stop = function () {
if (tutorial_running) { if (tutorial_running) {
subs.tutorial_unsubscribe_me_from(my_tutorial_stream); subs.tutorial_unsubscribe_me_from(my_tutorial_stream);
tutorial_running = false; tutorial_running = false;
$.ajax({
type: 'POST',
url: '/json/tutorial_status',
data: {status: 'finished'},
success: function () {
// We need to reload the streams list so the sidebar is populated
// with the new streams
subs.reload_subscriptions({clear_first: true});
}
});
} }
}; };

View File

@ -1167,6 +1167,10 @@ exports.remove_narrow_filter = function (name, type) {
exports.get_filter_li(type, name).remove(); exports.get_filter_li(type, name).remove();
}; };
exports.remove_all_narrow_filters = function () {
$("#stream_filters").children().remove();
};
var presence_descriptions = { var presence_descriptions = {
active: ' is active', active: ' is active',
away: ' was recently active', away: ' was recently active',

View File

@ -25,7 +25,7 @@ from zephyr.lib.actions import do_add_subscription, do_remove_subscription, \
log_subscription_property_change, internal_send_message, \ log_subscription_property_change, internal_send_message, \
create_stream_if_needed, gather_subscriptions, subscribed_to_stream, \ create_stream_if_needed, gather_subscriptions, subscribed_to_stream, \
update_user_presence, set_stream_color, get_stream_colors, update_message_flags, \ update_user_presence, set_stream_color, get_stream_colors, update_message_flags, \
recipient_for_emails, extract_recipients, do_events_register recipient_for_emails, extract_recipients, do_events_register, do_finish_tutorial
from zephyr.forms import RegistrationForm, HomepageForm, ToSForm, is_unique, \ from zephyr.forms import RegistrationForm, HomepageForm, ToSForm, is_unique, \
is_inactive, isnt_mit is_inactive, isnt_mit
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@ -212,14 +212,6 @@ def accounts_register(request):
do_change_full_name(user_profile, full_name) do_change_full_name(user_profile, full_name)
else: else:
user_profile = do_create_user(email, password, realm, full_name, short_name) user_profile = do_create_user(email, password, realm, full_name, short_name)
# We want to add the default subs list iff there were no subs
# specified when the user was invited.
streams = prereg_user.streams.all()
if len(streams) == 0:
add_default_subs(user_profile)
else:
for stream in streams:
do_add_subscription(user_profile, stream)
if prereg_user.referred_by is not None: if prereg_user.referred_by is not None:
# This is a cross-realm private message. # This is a cross-realm private message.
internal_send_message("humbug+signups@humbughq.com", internal_send_message("humbug+signups@humbughq.com",
@ -401,9 +393,21 @@ def home(request):
register_ret = do_events_register(user_profile, apply_markdown=True) register_ret = do_events_register(user_profile, apply_markdown=True)
user_has_messages = (register_ret['max_message_id'] != -1) user_has_messages = (register_ret['max_message_id'] != -1)
# Brand new users get the tutorial. # Brand new users get the tutorial
# Compute this here, before we set user_profile.pointer below. needs_tutorial = settings.TUTORIAL_ENABLED and \
needs_tutorial = settings.TUTORIAL_ENABLED and user_profile.pointer == -1 user_profile.tutorial_status == UserProfile.TUTORIAL_WAITING
# If the user has previously started (but not completed) the tutorial,
# finish it for her and subscribe her to the default streams
if user_profile.tutorial_status == UserProfile.TUTORIAL_STARTED:
tutorial_stream = user_profile.tutorial_stream_name()
try:
stream = Stream.objects.get(realm=user_profile.realm, name=tutorial_stream)
do_remove_subscription(user_profile, stream)
except Stream.DoesNotExist:
pass
do_finish_tutorial(user_profile)
if user_profile.pointer == -1 and user_has_messages: if user_profile.pointer == -1 and user_has_messages:
# Put the new user's pointer at the bottom # Put the new user's pointer at the bottom
@ -769,8 +773,7 @@ def json_tutorial_send_message(request, user_profile,
realm=user_profile.realm) realm=user_profile.realm)
return json_success() return json_success()
elif message_type_name == 'stream': elif message_type_name == 'stream':
tutorial_stream_name = 'tutorial-%s' % user_profile.email.split('@')[0] tutorial_stream_name = user_profile.tutorial_stream_name()
tutorial_stream_name = tutorial_stream_name[:Stream.MAX_NAME_LENGTH]
## TODO: For open realms, we need to use the full name here, ## TODO: For open realms, we need to use the full name here,
## so that me@gmail.com and me@hotmail.com don't get the same stream. ## so that me@gmail.com and me@hotmail.com don't get the same stream.
internal_send_message(sender_name, internal_send_message(sender_name,
@ -782,6 +785,18 @@ def json_tutorial_send_message(request, user_profile,
return json_success() return json_success()
return json_error('Bad data passed in to tutorial_send_message') return json_error('Bad data passed in to tutorial_send_message')
@authenticated_json_post_view
@has_request_variables
def json_tutorial_status(request, user_profile, status=POST('status')):
if status == 'started':
user_profile.tutorial_status = UserProfile.TUTORIAL_STARTED
user_profile.save()
elif status == 'finished':
do_finish_tutorial(user_profile)
return json_success()
# We do not @require_login for send_message_backend, since it is used # We do not @require_login for send_message_backend, since it is used
# both from the API and the web service. Code calling # both from the API and the web service. Code calling
# send_message_backend should either check the API key or check that # send_message_backend should either check the API key or check that