[manual] Extend /api/v1/streams API endpoint.

Previously it only provided the list of all public streams; now it
allows one to specify any union of some of the following:
* all public streams
* all streams the user subscribed to

(the most relevant being the union of those two, which is what we want
for the "streams" page).

Or:
* all streams in realm (superuser only)

The manual task required is that when this is pushed to prod, we need
to also deploy the new sync-public-streams version to zmirror.

(imported from commit 27848b8bd136e2777f399b7d05b2fdcec35e4e21)
This commit is contained in:
Tim Abbott 2013-08-22 11:37:02 -04:00
parent 0a0cb9e70e
commit f5f95e5f43
6 changed files with 90 additions and 15 deletions

View File

@ -43,4 +43,4 @@ parser.add_option_group(zulip.generate_option_group(parser))
client = zulip.init_from_options(options)
print client.get_public_streams()
print client.get_streams(include_public=True, include_subscribed=False)

View File

@ -286,13 +286,16 @@ def _mk_events(event_types=None):
return dict()
return dict(event_types=event_types)
def _kwargs_to_dict(**kwargs):
return kwargs
Client._register('send_message', url='messages', make_request=(lambda request: request))
Client._register('update_message', method='PATCH', url='messages', make_request=(lambda request: request))
Client._register('get_messages', method='GET', url='messages/latest', longpolling=True)
Client._register('get_events', url='events', method='GET', longpolling=True, make_request=(lambda **kwargs: kwargs))
Client._register('register', make_request=_mk_events)
Client._register('get_profile', method='GET', url='users/me')
Client._register('get_public_streams', method='GET', url='streams')
Client._register('get_streams', method='GET', url='streams', make_request=_kwargs_to_dict)
Client._register('get_members', method='GET', url='users')
Client._register('list_subscriptions', method='GET', url='users/me/subscriptions')
Client._register('add_subscriptions', url='users/me/subscriptions', make_request=_mk_subs)

View File

@ -16,7 +16,7 @@ def fetch_public_streams():
public_streams = set()
try:
res = zulip_client.get_public_streams()
res = zulip_client.get_streams(include_all_active=True)
if res.get("result") == "success":
streams = res["streams"]
else:

View File

@ -22,6 +22,7 @@ from zerver.lib.cache import bounce_key_prefix_for_testing
from zerver.lib.rate_limiter import clear_user_history
from zerver.forms import not_mit_mailing_list
import base64
from django.conf import settings
import os
import random
@ -136,6 +137,12 @@ class AuthedTestCase(TestCase):
API_KEYS[email] = get_user_profile_by_email(email).api_key
return API_KEYS[email]
def api_auth(self, email):
credentials = "%s:%s" % (email, self.get_api_key(email))
return {
'HTTP_AUTHORIZATION': 'Basic ' + base64.b64encode(credentials)
}
def get_streams(self, email):
"""
Helper function to get the stream names for a user
@ -1950,8 +1957,7 @@ class GetPublicStreamsTest(AuthedTestCase):
email = 'hamlet@zulip.com'
self.login(email)
api_key = self.get_api_key(email)
result = self.client.post("/json/get_public_streams", {'email': email, 'api-key': api_key})
result = self.client.post("/json/get_public_streams")
self.assert_json_success(result)
json = ujson.loads(result.content)
@ -1959,6 +1965,45 @@ class GetPublicStreamsTest(AuthedTestCase):
self.assertIn("streams", json)
self.assertIsInstance(json["streams"], list)
def test_public_streams_api(self):
"""
Ensure that get_public_streams successfully returns a list of streams
"""
email = 'hamlet@zulip.com'
self.login(email)
# Check it correctly lists the user's subs with include_public=false
result = self.client.get("/api/v1/streams?include_public=false", **self.api_auth(email))
result2 = self.client.post("/json/subscriptions/list", {})
self.assert_json_success(result)
json = ujson.loads(result.content)
self.assertIn("streams", json)
self.assertIsInstance(json["streams"], list)
self.assert_json_success(result2)
json2 = ujson.loads(result2.content)
self.assertEqual(sorted([s["name"] for s in json["streams"]]),
sorted([s["name"] for s in json2["subscriptions"]]))
# Check it correctly lists all public streams with include_subscribed=false
result = self.client.get("/api/v1/streams?include_public=true&include_subscribed=false",
**self.api_auth(email))
self.assert_json_success(result)
json = ujson.loads(result.content)
all_streams = [stream.name for stream in
Stream.objects.filter(realm=get_user_profile_by_email(email).realm)]
self.assertEqual(sorted(s["name"] for s in json["streams"]),
sorted(all_streams))
# Check non-superuser can't use include_all_active
result = self.client.get("/api/v1/streams?include_all_active=true",
**self.api_auth(email))
self.assertEqual(result.status_code, 400)
class InviteOnlyStreamTest(AuthedTestCase):
def test_list_respects_invite_only_bit(self):

View File

@ -1293,25 +1293,52 @@ def api_get_public_streams(request, user_profile):
def json_get_public_streams(request, user_profile):
return get_public_streams_backend(request, user_profile)
def get_public_streams_backend(request, user_profile):
if user_profile.realm.domain == "mit.edu" and not is_super_user_api(request):
return json_error("User not authorized for this query")
# By default, lists all streams that the user has access to --
# i.e. public streams plus invite-only streams that the user is on
@has_request_variables
def get_streams_backend(request, user_profile,
include_public=REQ(converter=json_to_bool, default=True),
include_subscribed=REQ(converter=json_to_bool, default=True),
include_all_active=REQ(converter=json_to_bool, default=False)):
if include_all_active or (include_public and user_profile.realm.domain == "mit.edu"):
if not is_super_user_api(request):
return json_error("User not authorized for this query")
# Only get streams someone is currently subscribed to
subs_filter = Subscription.objects.filter(active=True).values('recipient_id')
stream_ids = Recipient.objects.filter(
type=Recipient.STREAM, id__in=subs_filter).values('type_id')
# Start out with all active streams in the realm
query = Stream.objects.filter(id__in = stream_ids, realm=user_profile.realm)
if not (user_profile.realm.domain == "mit.edu" and is_super_user_api(request)):
# We don't apply the `invite_only=False` filter when answering
# the mit.edu API superuser, because the list of streams is
# used as the list of all Zephyr classes to mirror, and we
# want to include invite-only streams (aka zcrypted classes) in that
query = query.filter(invite_only=False)
if not include_all_active:
user_subs = Subscription.objects.select_related("recipient").filter(
active=True, user_profile=user_profile,
recipient__type=Recipient.STREAM)
if include_subscribed:
recipient_check = Q(id__in=[sub.recipient.type_id for sub in user_subs])
if include_public:
invite_only_check = Q(invite_only=False)
if include_subscribed and include_public:
query = query.filter(recipient_check | invite_only_check)
elif include_public:
query = query.filter(invite_only_check)
elif include_subscribed:
query = query.filter(recipient_check)
else:
# We're including nothing, so don't bother hitting the DB.
query = []
streams = sorted({"name": stream.name} for stream in query)
return json_success({"streams": streams})
def get_public_streams_backend(request, user_profile):
return get_streams_backend(request, user_profile, include_public=True,
include_subscribed=False, include_all_active=False)
@authenticated_api_view
def api_list_subscriptions(request, user_profile):
return list_subscriptions_backend(request, user_profile)

View File

@ -156,7 +156,7 @@ v1_api_and_json_patterns = patterns('zerver.views',
url(r'^messages/flags$', 'rest_dispatch',
{'POST': 'update_message_flags'}),
url(r'^streams$', 'rest_dispatch',
{'GET': 'get_public_streams_backend'}),
{'GET': 'get_streams_backend'}),
# GET returns "stream info" (undefined currently?), HEAD returns whether stream exists (200 or 404)
url(r'^streams/(?P<stream_name>.*)/members$', 'rest_dispatch',
{'GET': 'get_subscribers_backend'}),