[schema] Add backend support for per-user alert words

(imported from commit 7a9c596a010cbedbddf594c5d9c68bb9ed46d122)
This commit is contained in:
Leo Franchi 2013-09-03 16:41:17 -04:00
parent 9428f40422
commit b2ddd670e4
8 changed files with 421 additions and 3 deletions

View File

@ -40,6 +40,8 @@ from zerver.decorator import get_user_profile_by_email, json_to_list, JsonableEr
from zerver.lib.event_queue import request_event_queue, get_user_events from zerver.lib.event_queue import request_event_queue, get_user_events
from zerver.lib.utils import log_statsd_event, statsd from zerver.lib.utils import log_statsd_event, statsd
from zerver.lib.html_diff import highlight_html_differences from zerver.lib.html_diff import highlight_html_differences
from zerver.lib.alert_words import user_alert_words, add_user_alert_words, \
remove_user_alert_words, set_user_alert_words
import confirmation.settings import confirmation.settings
@ -269,6 +271,7 @@ def do_send_messages(messages):
# Message.render_markdown by code in the bugdown inline patterns # Message.render_markdown by code in the bugdown inline patterns
wildcard = message['message'].mentions_wildcard wildcard = message['message'].mentions_wildcard
mentioned_ids = message['message'].mentions_user_ids mentioned_ids = message['message'].mentions_user_ids
ids_with_alert_words = message['message'].user_ids_with_alert_words
for um in ums_to_create: for um in ums_to_create:
sent_by_human = (message['message'].sending_client.name.lower() in \ sent_by_human = (message['message'].sending_client.name.lower() in \
@ -280,6 +283,8 @@ def do_send_messages(messages):
um.flags |= UserMessage.flags.wildcard_mentioned um.flags |= UserMessage.flags.wildcard_mentioned
if um.user_profile_id in mentioned_ids: if um.user_profile_id in mentioned_ids:
um.flags |= UserMessage.flags.mentioned um.flags |= UserMessage.flags.mentioned
if um.user_profile_id in ids_with_alert_words:
um.flags |= UserMessage.flags.has_alert_word
user_message_flags[message['message'].id][um.user_profile_id] = um.flags_list() user_message_flags[message['message'].id][um.user_profile_id] = um.flags_list()
ums.extend(ums_to_create) ums.extend(ums_to_create)
UserMessage.objects.bulk_create(ums) UserMessage.objects.bulk_create(ums)
@ -1253,6 +1258,8 @@ def do_events_register(user_profile, user_client, apply_markdown=True,
pass pass
if event_types is None or "realm_emoji" in event_types: if event_types is None or "realm_emoji" in event_types:
ret['realm_emoji'] = user_profile.realm.get_emoji() ret['realm_emoji'] = user_profile.realm.get_emoji()
if event_types is None or "alert_words" in event_types:
ret['alert_words'] = user_alert_words(user_profile)
# Apply events that came in while we were fetching initial data # Apply events that came in while we were fetching initial data
events = get_user_events(user_profile, queue_id, -1) events = get_user_events(user_profile, queue_id, -1)
@ -1306,6 +1313,8 @@ def do_events_register(user_profile, user_client, apply_markdown=True,
pass pass
elif event['type'] == "realm_emoji": elif event['type'] == "realm_emoji":
ret['realm_emoji'] = event['realm_emoji'] ret['realm_emoji'] = event['realm_emoji']
elif event['type'] == "alert_words":
ret['alert_words'].extend(event['alert_words'])
else: else:
raise ValueError("Unexpected event type %s" % (event['type'],)) raise ValueError("Unexpected event type %s" % (event['type'],))
@ -1600,3 +1609,22 @@ def do_add_realm_emoji(realm, name, img_url):
def do_remove_realm_emoji(realm, name): def do_remove_realm_emoji(realm, name):
RealmEmoji.objects.get(realm=realm, name=name).delete() RealmEmoji.objects.get(realm=realm, name=name).delete()
notify_realm_emoji(realm) notify_realm_emoji(realm)
def notify_alert_words(user_profile):
words = user_alert_words(user_profile)
notice = dict(event=dict(type="alert_words", alert_words=words),
users=[user_profile.id])
tornado_callbacks.send_notification(notice)
def do_add_alert_words(user_profile, alert_words):
add_user_alert_words(user_profile, alert_words)
notify_alert_words(user_profile)
def do_remove_alert_words(user_profile, alert_words):
remove_user_alert_words(user_profile, alert_words)
notify_alert_words(user_profile)
def do_set_alert_words(user_profile, alert_words):
set_user_alert_words(user_profile, alert_words)
notify_alert_words(user_profile)

34
zerver/lib/alert_words.py Normal file
View File

@ -0,0 +1,34 @@
import re
import zerver.models
import itertools
import logging
import ujson
def alert_words_in_realm(realm):
users = zerver.models.UserProfile.objects.filter(realm=realm, is_active=True)
all_user_words = dict((user, user_alert_words(user)) for user in users)
users_with_words = dict((u, w) for (u, w) in all_user_words.iteritems() if len(w))
return users_with_words
def user_alert_words(user_profile):
return ujson.loads(user_profile.alert_words)
def add_user_alert_words(user_profile, alert_words):
words = user_alert_words(user_profile)
new_words = [w for w in alert_words if not w in words]
words.extend(new_words)
set_user_alert_words(user_profile, words)
def remove_user_alert_words(user_profile, alert_words):
words = user_alert_words(user_profile)
words = [w for w in words if not w in alert_words]
set_user_alert_words(user_profile, words)
def set_user_alert_words(user_profile, alert_words):
user_profile.alert_words = ujson.dumps(alert_words)
user_profile.save(update_fields=['alert_words'])

View File

@ -21,6 +21,7 @@ from zerver.lib.bugdown import codehilite, fenced_code
from zerver.lib.bugdown.fenced_code import FENCE_RE from zerver.lib.bugdown.fenced_code import FENCE_RE
from zerver.lib.timeout import timeout, TimeoutExpired from zerver.lib.timeout import timeout, TimeoutExpired
from zerver.lib.cache import cache_with_key, cache_get_many, cache_set_many from zerver.lib.cache import cache_with_key, cache_get_many, cache_set_many
import zerver.lib.alert_words as alert_words
import zerver.lib.mention as mention import zerver.lib.mention as mention
@ -558,6 +559,22 @@ class UserMentionPattern(markdown.inlinepatterns.Pattern):
el.text = "@%s" % (name,) el.text = "@%s" % (name,)
return el return el
class AlertWordsNotificationProcessor(markdown.preprocessors.Preprocessor):
def run(self, lines):
if current_message:
# We check for a user's custom notifications here, as we want
# to check for plaintext words that depend on the recipient.
realm_words = alert_words.alert_words_in_realm(current_message.sender.realm)
for user, words in realm_words.iteritems():
for word in words:
escaped = re.escape(word)
match_re = re.compile(r'\b%s\b' % (escaped,))
content = '\n'.join(lines)
if re.search(match_re, content):
current_message.user_ids_with_alert_words.add(user.id)
return lines
# This prevents realm_filters from running on the content of a # This prevents realm_filters from running on the content of a
# Markdown link, breaking up the link. This is a monkey-patch, but it # Markdown link, breaking up the link. This is a monkey-patch, but it
# might be worth sending a version of this change upstream. # might be worth sending a version of this change upstream.
@ -578,6 +595,8 @@ class Bugdown(markdown.Extension):
'strong'): 'strong'):
del md.inlinePatterns[k] del md.inlinePatterns[k]
md.preprocessors.add("custom_text_notifications", AlertWordsNotificationProcessor(md), "_end")
# Custom bold syntax: **foo** but not __foo__ # Custom bold syntax: **foo** but not __foo__
md.inlinePatterns.add('strong', md.inlinePatterns.add('strong',
markdown.inlinepatterns.SimpleTagPattern(r'(\*\*)([^\n]+?)\2', 'strong'), markdown.inlinepatterns.SimpleTagPattern(r'(\*\*)([^\n]+?)\2', 'strong'),

View File

@ -0,0 +1,199 @@
# -*- 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 field 'UserProfile.alert_words'
db.add_column(u'zerver_userprofile', 'alert_words',
self.gf('django.db.models.fields.TextField')(default='[]'),
keep_default=True)
def backwards(self, orm):
# Deleting field 'UserProfile.alert_words'
db.delete_column(u'zerver_userprofile', 'alert_words')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'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'}),
u'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'})
},
u'zerver.client': {
'Meta': {'object_name': 'Client'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30', 'db_index': 'True'})
},
u'zerver.defaultstream': {
'Meta': {'unique_together': "(('realm', 'stream'),)", 'object_name': 'DefaultStream'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Realm']"}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Stream']"})
},
u'zerver.huddle': {
'Meta': {'object_name': 'Huddle'},
'huddle_hash': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
u'zerver.message': {
'Meta': {'object_name': 'Message'},
'content': ('django.db.models.fields.TextField', [], {}),
'edit_history': ('django.db.models.fields.TextField', [], {'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_edit_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'pub_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.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': u"orm['zerver.UserProfile']"}),
'sending_client': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Client']"}),
'subject': ('django.db.models.fields.CharField', [], {'max_length': '60', 'db_index': 'True'})
},
u'zerver.mituser': {
'Meta': {'object_name': 'MitUser'},
'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'status': ('django.db.models.fields.IntegerField', [], {'default': '0'})
},
u'zerver.preregistrationuser': {
'Meta': {'object_name': 'PreregistrationUser'},
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'invited_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Realm']", 'null': 'True'}),
'referred_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']", 'null': 'True'}),
'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['zerver.Stream']", 'null': 'True', 'symmetrical': 'False'})
},
u'zerver.realm': {
'Meta': {'object_name': 'Realm'},
'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'restricted_to_domain': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'zerver.realmemoji': {
'Meta': {'unique_together': "(('realm', 'name'),)", 'object_name': 'RealmEmoji'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'img_url': ('django.db.models.fields.TextField', [], {}),
'name': ('django.db.models.fields.TextField', [], {}),
'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Realm']"})
},
u'zerver.recipient': {
'Meta': {'unique_together': "(('type', 'type_id'),)", 'object_name': 'Recipient'},
u'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'})
},
u'zerver.referral': {
'Meta': {'object_name': 'Referral'},
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"})
},
u'zerver.stream': {
'Meta': {'unique_together': "(('name', 'realm'),)", 'object_name': 'Stream'},
'email_token': ('django.db.models.fields.CharField', [], {'default': "'f2f990dfcd10ae620b095ac8aff11c1f'", 'max_length': '32'}),
u'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': u"orm['zerver.Realm']"})
},
u'zerver.streamcolor': {
'Meta': {'object_name': 'StreamColor'},
'color': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Subscription']"})
},
u'zerver.subscription': {
'Meta': {'unique_together': "(('user_profile', 'recipient'),)", 'object_name': 'Subscription'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'color': ('django.db.models.fields.CharField', [], {'default': "'#c2c2c2'", 'max_length': '10'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'in_home_view': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}),
'notifications': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Recipient']"}),
'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"})
},
u'zerver.useractivity': {
'Meta': {'unique_together': "(('user_profile', 'client', 'query'),)", 'object_name': 'UserActivity'},
'client': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Client']"}),
'count': ('django.db.models.fields.IntegerField', [], {}),
u'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': u"orm['zerver.UserProfile']"})
},
u'zerver.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'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Message']"}),
'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"})
},
u'zerver.userpresence': {
'Meta': {'unique_together': "(('user_profile', 'client'),)", 'object_name': 'UserPresence'},
'client': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Client']"}),
u'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': u"orm['zerver.UserProfile']"})
},
u'zerver.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'alert_words': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
'api_key': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'avatar_source': ('django.db.models.fields.CharField', [], {'default': "'G'", 'max_length': '1'}),
'bot_owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'db_index': 'True'}),
'enable_desktop_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'enable_offline_email_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'enable_sounds': ('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'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'invites_granted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'invites_used': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_bot': ('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_pointer_updater': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'last_reminder': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True'}),
'onboarding_steps': ('django.db.models.fields.TextField', [], {'default': "'[]'"}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'pointer': ('django.db.models.fields.IntegerField', [], {}),
'rate_limits': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Realm']"}),
'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'tutorial_status': ('django.db.models.fields.CharField', [], {'default': "'W'", 'max_length': '1'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
}
}
complete_apps = ['zerver']

View File

@ -190,6 +190,8 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
invites_granted = models.IntegerField(default=0) invites_granted = models.IntegerField(default=0)
invites_used = models.IntegerField(default=0) invites_used = models.IntegerField(default=0)
alert_words = models.TextField(default=ujson.dumps([])) # json-serialized list of strings
objects = UserManager() objects = UserManager()
@property @property
@ -433,6 +435,7 @@ class Message(models.Model):
self.mentions_wildcard = False self.mentions_wildcard = False
self.mentions_user_ids = set() self.mentions_user_ids = set()
self.user_ids_with_alert_words = set()
domain = self.sender.realm.domain domain = self.sender.realm.domain
if self.sending_client.name == "zephyr_mirror" and domain == "mit.edu": if self.sending_client.name == "zephyr_mirror" and domain == "mit.edu":
@ -553,7 +556,8 @@ class UserMessage(models.Model):
# on later # on later
archived = models.BooleanField() archived = models.BooleanField()
ALL_FLAGS = ['read', 'starred', 'collapsed', 'mentioned', 'wildcard_mentioned', ALL_FLAGS = ['read', 'starred', 'collapsed', 'mentioned', 'wildcard_mentioned',
'summarize_in_home', 'summarize_in_stream', 'force_expand', 'force_collapse'] 'summarize_in_home', 'summarize_in_stream', 'force_expand', 'force_collapse',
'has_alert_word']
flags = BitField(flags=ALL_FLAGS, default=0) flags = BitField(flags=ALL_FLAGS, default=0)
class Meta: class Meta:

View File

@ -22,6 +22,8 @@ from zerver.lib.rate_limiter import add_ratelimit_rule, remove_ratelimit_rule
from zerver.lib import bugdown from zerver.lib import bugdown
from zerver.lib.cache import bounce_key_prefix_for_testing from zerver.lib.cache import bounce_key_prefix_for_testing
from zerver.lib.rate_limiter import clear_user_history from zerver.lib.rate_limiter import clear_user_history
from zerver.lib.alert_words import alert_words_in_realm, user_alert_words, \
add_user_alert_words, remove_user_alert_words
from zerver.forms import not_mit_mailing_list from zerver.forms import not_mit_mailing_list
import base64 import base64
@ -3394,6 +3396,101 @@ class RateLimitTests(AuthedTestCase):
self.assert_json_success(result) self.assert_json_success(result)
class AlertWordTests(AuthedTestCase):
def test_default_no_words(self):
email = "cordelia@zulip.com"
user = get_user_profile_by_email(email)
words = user_alert_words(user)
self.assertEqual(words, [])
def test_add_word(self):
email = "cordelia@zulip.com"
user = get_user_profile_by_email(email)
add_user_alert_words(user, ['alert', 'word'])
words = user_alert_words(user)
self.assertEqual(words, ['alert', 'word'])
def test_remove_word(self):
email = "cordelia@zulip.com"
user = get_user_profile_by_email(email)
add_user_alert_words(user, ['alert', 'word'])
remove_user_alert_words(user, ['alert'])
words = user_alert_words(user)
self.assertEqual(words, ['word'])
def test_realm_words(self):
email = "cordelia@zulip.com"
user1 = get_user_profile_by_email(email)
add_user_alert_words(user1, ['alert', 'word'])
email = "othello@zulip.com"
user2 = get_user_profile_by_email(email)
add_user_alert_words(user2, ['another'])
realm_words = alert_words_in_realm(user2.realm)
self.assertEqual(len(realm_words), 2)
self.assertEqual(realm_words.keys(), [user1, user2])
self.assertEqual(realm_words[user1], ['alert', 'word'])
self.assertEqual(realm_words[user2], ['another'])
def test_json_list_default(self):
self.login("hamlet@zulip.com")
result = self.client.get('/json/users/me/alert_words')
self.assert_json_success(result)
data = ujson.loads(result.content)
self.assertEqual(data['alert_words'], [])
def test_json_list_add(self):
self.login("hamlet@zulip.com")
result = self.client_patch('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one', 'two', 'three'])})
self.assert_json_success(result)
result = self.client.get('/json/users/me/alert_words')
self.assert_json_success(result)
data = ujson.loads(result.content)
self.assertEqual(data['alert_words'], ['one', 'two', 'three'])
def test_json_list_remove(self):
self.login("hamlet@zulip.com")
result = self.client_patch('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one', 'two', 'three'])})
self.assert_json_success(result)
data = urllib.urlencode({'alert_words': ujson.dumps(['one'])})
result = self.client.delete('/json/users/me/alert_words', data)
self.assert_json_success(result)
result = self.client.get('/json/users/me/alert_words')
self.assert_json_success(result)
data = ujson.loads(result.content)
self.assertEqual(data['alert_words'], ['two', 'three'])
def test_json_list_set(self):
self.login("hamlet@zulip.com")
result = self.client_patch('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one', 'two', 'three'])})
self.assert_json_success(result)
data = urllib.urlencode({'alert_words': ujson.dumps(['a', 'b', 'c'])})
result = self.client.put('/json/users/me/alert_words', data)
self.assert_json_success(result)
result = self.client.get('/json/users/me/alert_words')
self.assert_json_success(result)
data = ujson.loads(result.content)
self.assertEqual(data['alert_words'], ['a', 'b', 'c'])
def full_test_name(test): def full_test_name(test):
test_class = test.__class__.__name__ test_class = test.__class__.__name__
test_method = test._testMethodName test_method = test._testMethodName

View File

@ -35,7 +35,8 @@ from zerver.lib.actions import do_remove_subscription, bulk_remove_subscriptions
get_status_dict, do_change_enable_offline_email_notifications, \ get_status_dict, do_change_enable_offline_email_notifications, \
do_update_onboarding_steps, do_update_message, internal_prep_message, \ do_update_onboarding_steps, do_update_message, internal_prep_message, \
do_send_messages, do_add_subscription, get_default_subs, do_deactivate, \ do_send_messages, do_add_subscription, get_default_subs, do_deactivate, \
user_email_is_unique, do_invite_users, do_refer_friend, compute_mit_user_fullname user_email_is_unique, do_invite_users, do_refer_friend, compute_mit_user_fullname, \
do_add_alert_words, do_remove_alert_words, do_set_alert_words
from zerver.lib.create_user import random_api_key from zerver.lib.create_user import random_api_key
from zerver.forms import RegistrationForm, HomepageForm, ToSForm, CreateBotForm, \ from zerver.forms import RegistrationForm, HomepageForm, ToSForm, CreateBotForm, \
is_inactive, isnt_mit, not_mit_mailing_list is_inactive, isnt_mit, not_mit_mailing_list
@ -44,6 +45,7 @@ from django_openid_auth.views import default_render_failure, login_complete
from openid.consumer.consumer import SUCCESS as openid_SUCCESS from openid.consumer.consumer import SUCCESS as openid_SUCCESS
from openid.extensions import ax from openid.extensions import ax
from zerver.lib import bugdown from zerver.lib import bugdown
from zerver.lib.alert_words import user_alert_words
from zerver.decorator import require_post, \ from zerver.decorator import require_post, \
authenticated_api_view, authenticated_json_post_view, \ authenticated_api_view, authenticated_json_post_view, \
@ -705,7 +707,8 @@ def home(request):
latest_read), latest_read),
furthest_read_time = sent_time_in_epoch_seconds(latest_read), furthest_read_time = sent_time_in_epoch_seconds(latest_read),
onboarding_steps = ujson.loads(user_profile.onboarding_steps), onboarding_steps = ujson.loads(user_profile.onboarding_steps),
staging = settings.STAGING_DEPLOYED or not settings.DEPLOYED staging = settings.STAGING_DEPLOYED or not settings.DEPLOYED,
alert_words = register_ret['alert_words']
)) ))
statsd.incr('views.home') statsd.incr('views.home')
@ -2075,3 +2078,31 @@ def json_refer_friend(request, user_profile, email=REQ):
do_refer_friend(user_profile, email); do_refer_friend(user_profile, email);
return json_success() return json_success()
def list_alert_words(request, user_profile):
return json_success({'alert_words': user_alert_words(user_profile)})
@authenticated_json_post_view
@has_request_variables
def json_set_alert_words(request, user_profile,
alert_words=REQ(converter=json_to_list, default=[])):
do_set_alert_words(user_profile, alert_words)
return json_success()
@has_request_variables
def set_alert_words(request, user_profile,
alert_words=REQ(converter=json_to_list, default=[])):
do_set_alert_words(user_profile, alert_words)
return json_success()
@has_request_variables
def add_alert_words(request, user_profile,
alert_words=REQ(converter=json_to_list, default=[])):
do_add_alert_words(user_profile, alert_words)
return json_success()
@has_request_variables
def remove_alert_words(request, user_profile,
alert_words=REQ(converter=json_to_list, default=[])):
do_remove_alert_words(user_profile, alert_words)
return json_success()

View File

@ -122,6 +122,7 @@ urlpatterns += patterns('zerver.views',
url(r'^json/update_message$', 'json_update_message'), url(r'^json/update_message$', 'json_update_message'),
url(r'^json/fetch_raw_message$', 'json_fetch_raw_message'), url(r'^json/fetch_raw_message$', 'json_fetch_raw_message'),
url(r'^json/refer_friend$', 'json_refer_friend'), url(r'^json/refer_friend$', 'json_refer_friend'),
url(r'^json/set_alert_words$', 'json_set_alert_words'),
# These are json format views used by the API. They require an API key. # These are json format views used by the API. They require an API key.
url(r'^api/v1/get_profile$', 'api_get_profile'), url(r'^api/v1/get_profile$', 'api_get_profile'),
@ -181,6 +182,11 @@ v1_api_and_json_patterns = patterns('zerver.views',
{'GET': 'list_subscriptions_backend', {'GET': 'list_subscriptions_backend',
'POST': 'add_subscriptions_backend', 'POST': 'add_subscriptions_backend',
'PATCH': 'update_subscriptions_backend'}), 'PATCH': 'update_subscriptions_backend'}),
url(r'^users/me/alert_words$', 'rest_dispatch',
{'GET': 'list_alert_words',
'PUT': 'set_alert_words',
'PATCH': 'add_alert_words',
'DELETE': 'remove_alert_words'}),
url(r'^users/me/api_key/regenerate$', 'rest_dispatch', url(r'^users/me/api_key/regenerate$', 'rest_dispatch',
{'POST': 'regenerate_api_key'}), {'POST': 'regenerate_api_key'}),
url(r'^users/(?P<email>.*)$', 'rest_dispatch', url(r'^users/(?P<email>.*)$', 'rest_dispatch',