users: Add is_owner field to user objects returned by get endpoints.

This commit adds 'is_owner' field to the user object returned by
'/users', 'users/{user_id}', and '/users/me' endpoints.
This commit is contained in:
sahil839 2020-06-02 01:17:18 +05:30 committed by Tim Abbott
parent cf8c1cb357
commit 9ef1c5b1a6
9 changed files with 50 additions and 6 deletions

View File

@ -10,6 +10,11 @@ below features are supported.
## Changes in Zulip 2.2 ## Changes in Zulip 2.2
**Feature level 8**
* [`GET /users`](/api/get-all-users), [`GET /users/{user_id}`](/api/get-user)
and [`GET /users/me`](/api/get-profile): User objects now contain the
`is_owner` field as well.
**Feature level 7** **Feature level 7**
* [`GET /events`](/api/get-events-from-queue): `realm_user` and * [`GET /events`](/api/get-events-from-queue): `realm_user` and
`realm_bot` events no longer contain an `email` field to identify `realm_bot` events no longer contain an `email` field to identify

View File

@ -29,7 +29,7 @@ DESKTOP_WARNING_VERSION = "5.2.0"
# #
# Changes should be accompanied by documentation explaining what the # Changes should be accompanied by documentation explaining what the
# new level means in templates/zerver/api/changelog.md. # new level means in templates/zerver/api/changelog.md.
API_FEATURE_LEVEL = 7 API_FEATURE_LEVEL = 8
# Bump the minor PROVISION_VERSION to indicate that folks should provision # Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump # only when going from an old version of the code to a newer version. Bump

View File

@ -311,6 +311,7 @@ def format_user_row(realm: Realm, acting_user: UserProfile, row: Dict[str, Any],
client_gravatar=client_gravatar,) client_gravatar=client_gravatar,)
is_admin = is_administrator_role(row['role']) is_admin = is_administrator_role(row['role'])
is_owner = row['role'] == UserProfile.ROLE_REALM_OWNER
is_guest = row['role'] == UserProfile.ROLE_GUEST is_guest = row['role'] == UserProfile.ROLE_GUEST
is_bot = row['is_bot'] is_bot = row['is_bot']
# This format should align with get_cross_realm_dicts() and notify_created_user # This format should align with get_cross_realm_dicts() and notify_created_user
@ -320,6 +321,7 @@ def format_user_row(realm: Realm, acting_user: UserProfile, row: Dict[str, Any],
avatar_url=avatar_url, avatar_url=avatar_url,
avatar_version=row['avatar_version'], avatar_version=row['avatar_version'],
is_admin=is_admin, is_admin=is_admin,
is_owner=is_owner,
is_guest=is_guest, is_guest=is_guest,
is_bot=is_bot, is_bot=is_bot,
full_name=row['full_name'], full_name=row['full_name'],

View File

@ -1353,6 +1353,7 @@ paths:
"is_active": true, "is_active": true,
"email": "AARON@zulip.com", "email": "AARON@zulip.com",
"is_admin": false, "is_admin": false,
"is_owner": false,
"avatar_url": "https://secure.gravatar.com/avatar/818c212b9f8830dfef491b3f7da99a14?d=identicon&version=1", "avatar_url": "https://secure.gravatar.com/avatar/818c212b9f8830dfef491b3f7da99a14?d=identicon&version=1",
"bot_type": null, "bot_type": null,
"timezone": "", "timezone": "",
@ -1401,6 +1402,7 @@ paths:
"bot_type": null, "bot_type": null,
"timezone": "", "timezone": "",
"is_admin": false, "is_admin": false,
"is_owner": false,
"avatar_url": "https://secure.gravatar.com/avatar/6d8cad0fd00256e7b40691d27ddfd466?d=identicon&version=1", "avatar_url": "https://secure.gravatar.com/avatar/6d8cad0fd00256e7b40691d27ddfd466?d=identicon&version=1",
"is_active": true, "is_active": true,
"email": "hamlet@zulip.com" "email": "hamlet@zulip.com"
@ -1414,6 +1416,7 @@ paths:
"is_active": true, "is_active": true,
"avatar_url": "https://secure.gravatar.com/avatar/7328586831cdbb1627649bd857b1ee8c?d=identicon&version=1", "avatar_url": "https://secure.gravatar.com/avatar/7328586831cdbb1627649bd857b1ee8c?d=identicon&version=1",
"is_admin": false, "is_admin": false,
"is_owner": false,
"user_id": 23, "user_id": 23,
"bot_type": 1, "bot_type": 1,
"timezone": "", "timezone": "",
@ -1550,6 +1553,7 @@ paths:
"bot_type": null, "bot_type": null,
"timezone": "", "timezone": "",
"is_admin": false, "is_admin": false,
"is_owner": false,
"avatar_url": "https://secure.gravatar.com/avatar/6d8cad0fd00256e7b40691d27ddfd466?d=identicon&version=1", "avatar_url": "https://secure.gravatar.com/avatar/6d8cad0fd00256e7b40691d27ddfd466?d=identicon&version=1",
"is_active": true, "is_active": true,
"email": "hamlet@zulip.com" "email": "hamlet@zulip.com"
@ -1830,6 +1834,14 @@ paths:
description: | description: |
A boolean indicating if the requesting user is an admin. A boolean indicating if the requesting user is an admin.
example: true example: true
is_owner:
type: boolean
description: |
A boolean indicating if the requesting user is
an organization owner.
**Changes**: New in Zulip 2.2 (feature level 8).
example: false
is_bot: is_bot:
type: boolean type: boolean
description: | description: |
@ -1875,6 +1887,7 @@ paths:
"email": "iago@zulip.com", "email": "iago@zulip.com",
"full_name": "Iago", "full_name": "Iago",
"is_admin": true, "is_admin": true,
"is_owner": false,
"is_bot": false, "is_bot": false,
"max_message_id": 30, "max_message_id": 30,
"msg": "", "msg": "",
@ -3966,6 +3979,13 @@ components:
type: boolean type: boolean
description: | description: |
A boolean specifying whether the user is an organization administrator. A boolean specifying whether the user is an organization administrator.
is_owner:
type: boolean
description: |
A boolean specifying whether the user is an organization owner.
If true, is_admin will also be true.
**Changes**: New in Zulip 2.2 (feature level 8).
bot_type: bot_type:
type: integer type: integer
nullable: true nullable: true

View File

@ -672,15 +672,15 @@ class ListCustomProfileFieldTest(CustomProfileFieldTestCase):
expected_keys_for_iago = { expected_keys_for_iago = {
"delivery_email", "delivery_email",
"email", "user_id", "avatar_url", "avatar_version", "is_admin", "is_guest", "is_bot", "email", "user_id", "avatar_url", "avatar_version", "is_admin", "is_guest", "is_bot", "is_owner",
"full_name", "timezone", "is_active", "date_joined", "profile_data"} "full_name", "timezone", "is_active", "date_joined", "profile_data"}
self.assertEqual(set(iago_raw_data.keys()), expected_keys_for_iago) self.assertEqual(set(iago_raw_data.keys()), expected_keys_for_iago)
self.assertNotEqual(iago_raw_data["profile_data"], {}) self.assertNotEqual(iago_raw_data["profile_data"], {})
expected_keys_for_test_bot = { expected_keys_for_test_bot = {
"delivery_email", "delivery_email",
"email", "user_id", "avatar_url", "avatar_version", "is_admin", "is_guest", "is_bot", "full_name", "email", "user_id", "avatar_url", "avatar_version", "is_admin", "is_guest", "is_bot", "is_owner",
"timezone", "is_active", "date_joined", "bot_type", "bot_owner_id"} "full_name", "timezone", "is_active", "date_joined", "bot_type", "bot_owner_id"}
self.assertEqual(set(test_bot_raw_data.keys()), expected_keys_for_test_bot) self.assertEqual(set(test_bot_raw_data.keys()), expected_keys_for_test_bot)
self.assertEqual(test_bot_raw_data["bot_type"], 1) self.assertEqual(test_bot_raw_data["bot_type"], 1)
self.assertEqual(test_bot_raw_data["bot_owner_id"], iago_raw_data["user_id"]) self.assertEqual(test_bot_raw_data["bot_owner_id"], iago_raw_data["user_id"])
@ -697,8 +697,8 @@ class ListCustomProfileFieldTest(CustomProfileFieldTestCase):
self.login('iago') self.login('iago')
expected_keys = { expected_keys = {
"result", "msg", "pointer", "client_id", "max_message_id", "user_id", "result", "msg", "pointer", "client_id", "max_message_id", "user_id",
"avatar_url", "full_name", "email", "is_bot", "is_admin", "short_name", "avatar_url", "full_name", "email", "is_bot", "is_admin", "is_owner",
"profile_data"} "short_name", "profile_data"}
url = "/json/users/me" url = "/json/users/me"
response = self.client_get(url) response = self.client_get(url)

View File

@ -1248,6 +1248,7 @@ class EventsRegisterTest(ZulipTestCase):
('avatar_version', check_int), ('avatar_version', check_int),
('full_name', check_string), ('full_name', check_string),
('is_admin', check_bool), ('is_admin', check_bool),
('is_owner', check_bool),
('is_bot', check_bool), ('is_bot', check_bool),
('is_guest', check_bool), ('is_guest', check_bool),
('is_active', check_bool), ('is_active', check_bool),
@ -1276,6 +1277,7 @@ class EventsRegisterTest(ZulipTestCase):
('full_name', check_string), ('full_name', check_string),
('is_active', check_bool), ('is_active', check_bool),
('is_admin', check_bool), ('is_admin', check_bool),
('is_owner', check_bool),
('is_bot', check_bool), ('is_bot', check_bool),
('is_guest', check_bool), ('is_guest', check_bool),
('profile_data', check_dict_only([])), ('profile_data', check_dict_only([])),

View File

@ -614,6 +614,7 @@ class HomeTest(ZulipTestCase):
is_active=True, is_active=True,
is_bot=True, is_bot=True,
is_admin=False, is_admin=False,
is_owner=False,
is_cross_realm_bot=True, is_cross_realm_bot=True,
is_guest=False is_guest=False
), ),
@ -627,6 +628,7 @@ class HomeTest(ZulipTestCase):
is_active=True, is_active=True,
is_bot=True, is_bot=True,
is_admin=False, is_admin=False,
is_owner=False,
is_cross_realm_bot=True, is_cross_realm_bot=True,
is_guest=False is_guest=False
), ),
@ -640,6 +642,7 @@ class HomeTest(ZulipTestCase):
is_active=True, is_active=True,
is_bot=True, is_bot=True,
is_admin=False, is_admin=False,
is_owner=False,
is_cross_realm_bot=True, is_cross_realm_bot=True,
is_guest=False is_guest=False
), ),

View File

@ -1371,6 +1371,7 @@ class GetProfileTest(ZulipTestCase):
def test_get_user_profile(self) -> None: def test_get_user_profile(self) -> None:
hamlet = self.example_user('hamlet') hamlet = self.example_user('hamlet')
iago = self.example_user('iago') iago = self.example_user('iago')
desdemona = self.example_user('desdemona')
self.login('hamlet') self.login('hamlet')
result = ujson.loads(self.client_get('/json/users/me').content) result = ujson.loads(self.client_get('/json/users/me').content)
@ -1380,6 +1381,7 @@ class GetProfileTest(ZulipTestCase):
self.assertIn("user_id", result) self.assertIn("user_id", result)
self.assertFalse(result['is_bot']) self.assertFalse(result['is_bot'])
self.assertFalse(result['is_admin']) self.assertFalse(result['is_admin'])
self.assertFalse(result['is_owner'])
self.assertFalse('delivery_email' in result) self.assertFalse('delivery_email' in result)
self.login('iago') self.login('iago')
result = ujson.loads(self.client_get('/json/users/me').content) result = ujson.loads(self.client_get('/json/users/me').content)
@ -1388,6 +1390,14 @@ class GetProfileTest(ZulipTestCase):
self.assertEqual(result['full_name'], 'Iago') self.assertEqual(result['full_name'], 'Iago')
self.assertFalse(result['is_bot']) self.assertFalse(result['is_bot'])
self.assertTrue(result['is_admin']) self.assertTrue(result['is_admin'])
self.assertFalse(result['is_owner'])
self.login('desdemona')
result = ujson.loads(self.client_get('/json/users/me').content)
self.assertEqual(result['short_name'], 'desdemona')
self.assertEqual(result['email'], desdemona.email)
self.assertFalse(result['is_bot'])
self.assertTrue(result['is_admin'])
self.assertTrue(result['is_owner'])
# Tests the GET ../users/{id} api endpoint. # Tests the GET ../users/{id} api endpoint.
user = self.example_user('hamlet') user = self.example_user('hamlet')
@ -1398,6 +1408,7 @@ class GetProfileTest(ZulipTestCase):
self.assertNotIn("profile_data", result['user']) self.assertNotIn("profile_data", result['user'])
self.assertFalse(result['user']['is_bot']) self.assertFalse(result['user']['is_bot'])
self.assertFalse(result['user']['is_admin']) self.assertFalse(result['user']['is_admin'])
self.assertFalse(result['user']['is_owner'])
result = ujson.loads(self.client_get('/json/users/{}?include_custom_profile_fields=true'.format(user.id)).content) result = ujson.loads(self.client_get('/json/users/{}?include_custom_profile_fields=true'.format(user.id)).content)

View File

@ -467,6 +467,7 @@ def get_profile_backend(request: HttpRequest, user_profile: UserProfile) -> Http
email = user_profile.email, email = user_profile.email,
is_bot = user_profile.is_bot, is_bot = user_profile.is_bot,
is_admin = user_profile.is_realm_admin, is_admin = user_profile.is_realm_admin,
is_owner = user_profile.is_realm_owner,
short_name = user_profile.short_name) short_name = user_profile.short_name)
if not user_profile.is_bot: if not user_profile.is_bot: