From f72212e8dc33ef21c1c21ccbac232b944255b0d0 Mon Sep 17 00:00:00 2001 From: vaibhav Date: Wed, 25 Jan 2017 18:48:00 +0530 Subject: [PATCH] Users sidebar should show a highlighted buddy list (Backend). This adds support for displaying a buddy list and adding and removing users to it. The buddy list also displays users in alphabetically increasing order. For moving users to and from buddy list, user handlebar is re-rendered at the appropriate place. A new endpoint has been created which manipulates the buddy list and a backend test for the same has also been added. Fixes: #236. --- static/js/activity.js | 5 +++ zerver/lib/actions.py | 20 ++++++++++- zerver/migrations/0050_auto_20170112_0752.py | 29 +++++++++++++++ zerver/models.py | 7 ++++ zerver/tests/test_buddy_list.py | 37 ++++++++++++++++++++ zerver/tests/tests.py | 1 + zerver/views/home.py | 1 + zerver/views/users.py | 22 ++++++++++-- zproject/urls.py | 4 +-- 9 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 zerver/migrations/0050_auto_20170112_0752.py create mode 100644 zerver/tests/test_buddy_list.py diff --git a/static/js/activity.js b/static/js/activity.js index 81ca4176be97a..fc5a3edb03a3f 100644 --- a/static/js/activity.js +++ b/static/js/activity.js @@ -280,6 +280,10 @@ function get_num_unread(user_id) { function info_for(user_id) { var presence = exports.presence_info[user_id].status; var person = people.get_person_from_user_id(user_id); + var bool_buddy = false; + if (exports.buddy_list.indexOf(parseInt(user_id, 10)) > -1) { + bool_buddy = true; + } return { href: narrow.pm_with_uri(person.email), name: person.full_name, @@ -288,6 +292,7 @@ function info_for(user_id) { type: presence, type_desc: presence_descriptions[presence], mobile: exports.presence_info[user_id].mobile, + bool_buddy: bool_buddy.toString(), }; } diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 4594a08484ac5..c1ec12cedfc28 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -37,7 +37,7 @@ realm_filters_for_realm, RealmFilter, receives_offline_notifications, \ ScheduledJob, get_owned_bot_dicts, \ get_old_unclaimed_attachments, get_cross_realm_emails, receives_online_notifications, \ - Reaction + Reaction, BuddyList from zerver.lib.alert_words import alert_words_in_realm from zerver.lib.avatar import get_avatar_url, avatar_url @@ -2663,6 +2663,22 @@ def truncate_topic(topic): # type: (Text) -> Text return truncate_content(topic, MAX_SUBJECT_LENGTH, "...") +def do_update_buddy_list(user_profile, buddy_profile, should_add): + # type: (UserProfile, str, bool) -> None + + record = BuddyList.objects.get_or_create(user=user_profile, buddy=buddy_profile) + if should_add == False: + record[0].delete() + +def get_buddy_list(user_profile): + # type: (UserProfile) -> List[int] + + records = BuddyList.objects.filter(user=user_profile) + user_list = [] + for record in records: + user_list.append(record.buddy.id) + + return user_list def update_user_message_flags(message, ums): # type: (Message, Iterable[UserMessage]) -> None @@ -3125,6 +3141,8 @@ def fetch_initial_state_data(user_profile, event_types, queue_id): # get any updates during a session from get_events() pass + if want('buddy_list'): + state['buddy_list'] = get_buddy_list(user_profile) if want('stream'): state['streams'] = do_get_streams(user_profile) if want('default_streams'): diff --git a/zerver/migrations/0050_auto_20170112_0752.py b/zerver/migrations/0050_auto_20170112_0752.py new file mode 100644 index 0000000000000..aa949acb9a167 --- /dev/null +++ b/zerver/migrations/0050_auto_20170112_0752.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-01-12 12:52 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('zerver', '0049_userprofile_pm_content_in_desktop_notifications'), + ] + + operations = [ + migrations.CreateModel( + name='BuddyList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('buddy', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='buddy', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AlterUniqueTogether( + name='buddylist', + unique_together=set([('user', 'buddy')]), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 3dcc7b2cc3fa8..38b77f201bae4 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -1390,3 +1390,10 @@ class ScheduledJob(models.Model): # Kind if like a ForeignKey, but table is determined by type. filter_id = models.IntegerField(null=True) # type: Optional[int] filter_string = models.CharField(max_length=100) # type: Text + +class BuddyList(models.Model): + user = models.ForeignKey(UserProfile, related_name="user") # type: UserProfile + buddy = models.ForeignKey(UserProfile, related_name="buddy") # type: UserProfile + + class Meta(object): + unique_together = ("user", "buddy") diff --git a/zerver/tests/test_buddy_list.py b/zerver/tests/test_buddy_list.py new file mode 100644 index 0000000000000..6a095436e1b46 --- /dev/null +++ b/zerver/tests/test_buddy_list.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from __future__ import print_function + +from zerver.lib.test_classes import ( + ZulipTestCase, +) + +from zerver.models import ( + get_user_profile_by_email, +) + +import ujson + +class BuddyListUpdateTest(ZulipTestCase): + def test_update_buddy_list(self): + # type: () -> None + user_email = "hamlet@zulip.com" + buddy_email = "iago@zulip.com" + + self.login(user_email) + user_profile = get_user_profile_by_email(user_email) + buddy_profile = get_user_profile_by_email(buddy_email) + + result = self.client_patch("/json/users/me/buddy", { + 'user_id': ujson.dumps(str(user_profile.id)), + 'buddy_id': ujson.dumps(str(buddy_profile.id)), + 'should_add': ujson.dumps(str(True)), + }) + self.assert_json_success(result) + + result = self.client_patch("/json/users/me/buddy", { + 'user_id': ujson.dumps(str(user_profile.id)), + 'buddy_id': ujson.dumps(str(buddy_profile.id)), + 'should_add': ujson.dumps(str(False)), + }) + self.assert_json_success(result) diff --git a/zerver/tests/tests.py b/zerver/tests/tests.py index 0377605f8d8cf..6f144dba6451d 100644 --- a/zerver/tests/tests.py +++ b/zerver/tests/tests.py @@ -1850,6 +1850,7 @@ def test_home(self): "avatar_url", "avatar_url_medium", "bot_list", + "buddy_list", "can_create_streams", "cross_realm_bots", "debug_mode", diff --git a/zerver/views/home.py b/zerver/views/home.py index 72eec9d20d8d3..4af53e7f6cb05 100644 --- a/zerver/views/home.py +++ b/zerver/views/home.py @@ -241,6 +241,7 @@ def home_real(request): notifications_stream = notifications_stream, cross_realm_bots = list(get_cross_realm_dicts()), use_websockets = settings.USE_WEBSOCKETS, + buddy_list = register_ret['buddy_list'], # Stream message notification settings: stream_desktop_notifications_enabled = user_profile.enable_stream_desktop_notifications, diff --git a/zerver/views/users.py b/zerver/views/users.py index d935cda608b75..902fcd70e8aa9 100644 --- a/zerver/views/users.py +++ b/zerver/views/users.py @@ -17,14 +17,15 @@ from zerver.lib.actions import do_change_full_name, do_change_is_admin, \ do_create_user, subscribed_to_stream, do_deactivate_user, do_reactivate_user, \ do_change_default_events_register_stream, do_change_default_sending_stream, \ - do_change_default_all_public_streams, do_regenerate_api_key, do_change_avatar_source + do_change_default_all_public_streams, do_regenerate_api_key, do_change_avatar_source, \ + do_update_buddy_list from zerver.lib.avatar import avatar_url, get_avatar_url from zerver.lib.response import json_error, json_success from zerver.lib.upload import upload_avatar_image from zerver.lib.validator import check_bool, check_string from zerver.lib.utils import generate_random_token from zerver.models import UserProfile, Stream, Realm, Message, get_user_profile_by_email, \ - get_stream, email_allowed_for_realm + get_stream, email_allowed_for_realm, get_user_profile_by_id from zproject.jinja2 import render_to_response @@ -113,6 +114,23 @@ def update_user_backend(request, user_profile, email, return json_success() +@has_request_variables +def update_buddy_list(request, user_profile, user_id=REQ(validator=check_string), + buddy_id=REQ(validator=check_string), should_add=REQ(validator=check_string)): + # type: (HttpRequest, UserProfile, str, str, str) -> HttpResponse + try: + user_profile = get_user_profile_by_id(user_id) + except UserProfile.DoesNotExist: + return json_error(_('No such user with email id ' + user_id)) + + try: + buddy_profile = get_user_profile_by_id(buddy_id) + except UserProfile.DoesNotExist: + return json_error(_('No such user with user id ' + buddy_id)) + do_update_buddy_list(user_profile, buddy_profile, (should_add == "true")) + + return json_success() + def avatar(request, email): # type: (HttpRequest, str) -> HttpResponse try: diff --git a/zproject/urls.py b/zproject/urls.py index b3fcd50c55032..0429f3f858e3b 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -238,8 +238,8 @@ url(r'^users/me$', rest_dispatch, {'GET': 'zerver.views.users.get_profile_backend', 'DELETE': 'zerver.views.users.deactivate_user_own_backend'}), - # PUT is currently used by mobile apps, we intend to remove the PUT version - # as soon as possible. POST exists to correct the erroneous use of PUT. + url(r'^users/me/buddy$', rest_dispatch, + {'PATCH': 'zerver.views.users.update_buddy_list'}), url(r'^users/me/pointer$', rest_dispatch, {'GET': 'zerver.views.pointer.get_pointer_backend', 'PUT': 'zerver.views.pointer.update_pointer_backend',