mirror of https://github.com/zulip/zulip.git
custom fields: Add default external account custom fields.
This commit is contained in:
parent
e8ee71e9a2
commit
e05429b6a9
|
@ -40,6 +40,7 @@ from zerver.lib.emoji import emoji_name_to_emoji_code, get_emoji_file_name
|
||||||
from zerver.lib.exceptions import StreamDoesNotExistError, \
|
from zerver.lib.exceptions import StreamDoesNotExistError, \
|
||||||
StreamWithIDDoesNotExistError
|
StreamWithIDDoesNotExistError
|
||||||
from zerver.lib.export import get_realm_exports_serialized
|
from zerver.lib.export import get_realm_exports_serialized
|
||||||
|
from zerver.lib.external_accounts import DEFAULT_EXTERNAL_ACCOUNTS
|
||||||
from zerver.lib.hotspots import get_next_hotspots
|
from zerver.lib.hotspots import get_next_hotspots
|
||||||
from zerver.lib.message import (
|
from zerver.lib.message import (
|
||||||
access_message,
|
access_message,
|
||||||
|
@ -5376,6 +5377,19 @@ def notify_realm_custom_profile_fields(realm: Realm, operation: str) -> None:
|
||||||
fields=[f.as_dict() for f in fields])
|
fields=[f.as_dict() for f in fields])
|
||||||
send_event(realm, event, active_user_ids(realm.id))
|
send_event(realm, event, active_user_ids(realm.id))
|
||||||
|
|
||||||
|
def try_add_realm_default_custom_profile_field(realm: Realm,
|
||||||
|
field_subtype: str) -> CustomProfileField:
|
||||||
|
field_data = DEFAULT_EXTERNAL_ACCOUNTS[field_subtype]
|
||||||
|
field = CustomProfileField(realm=realm, name=field_data['name'],
|
||||||
|
field_type=CustomProfileField.EXTERNAL_ACCOUNT,
|
||||||
|
hint=field_data['hint'],
|
||||||
|
field_data=ujson.dumps(dict(subtype=field_subtype)))
|
||||||
|
field.save()
|
||||||
|
field.order = field.id
|
||||||
|
field.save(update_fields=['order'])
|
||||||
|
notify_realm_custom_profile_fields(realm, 'add')
|
||||||
|
return field
|
||||||
|
|
||||||
def try_add_realm_custom_profile_field(realm: Realm, name: str, field_type: int,
|
def try_add_realm_custom_profile_field(realm: Realm, name: str, field_type: int,
|
||||||
hint: str='',
|
hint: str='',
|
||||||
field_data: Optional[ProfileFieldData]=None) -> CustomProfileField:
|
field_data: Optional[ProfileFieldData]=None) -> CustomProfileField:
|
||||||
|
|
|
@ -8,14 +8,26 @@ from zerver.lib.validator import check_required_string, \
|
||||||
check_external_account_url_pattern, check_dict_only
|
check_external_account_url_pattern, check_dict_only
|
||||||
from zerver.lib.types import ProfileFieldData
|
from zerver.lib.types import ProfileFieldData
|
||||||
|
|
||||||
|
# Default external account fields are by default avaliable
|
||||||
|
# to realm admins, where realm admin only need to select
|
||||||
|
# the default field and other values(i.e. name, url) will be
|
||||||
|
# fetch from this dictionary.
|
||||||
|
# text: Field text for admins - custom profile field in org settngs view
|
||||||
|
# name: Field label or name - user profile in user settings view
|
||||||
|
# hint: Field hint for realm users
|
||||||
|
# url_patter: Field url linkifier
|
||||||
DEFAULT_EXTERNAL_ACCOUNTS = {
|
DEFAULT_EXTERNAL_ACCOUNTS = {
|
||||||
"twitter": {
|
"twitter": {
|
||||||
"text": "Twitter",
|
"text": "Twitter",
|
||||||
"url_pattern": "https://twitter.com/%(username)s"
|
"url_pattern": "https://twitter.com/%(username)s",
|
||||||
|
"name": "Twitter",
|
||||||
|
"hint": "Enter your Twitter username",
|
||||||
},
|
},
|
||||||
"github": {
|
"github": {
|
||||||
"text": 'GitHub',
|
"text": 'GitHub',
|
||||||
"url_pattern": "https://github.com/%(username)s"
|
"url_pattern": "https://github.com/%(username)s",
|
||||||
|
"name": "GitHub",
|
||||||
|
"hint": "Enter your GitHub username",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ from zerver.lib.test_classes import ZulipTestCase
|
||||||
from zerver.lib.bugdown import convert as bugdown_convert
|
from zerver.lib.bugdown import convert as bugdown_convert
|
||||||
from zerver.models import CustomProfileField, \
|
from zerver.models import CustomProfileField, \
|
||||||
custom_profile_fields_for_realm, CustomProfileFieldValue, get_realm
|
custom_profile_fields_for_realm, CustomProfileFieldValue, get_realm
|
||||||
|
from zerver.lib.external_accounts import DEFAULT_EXTERNAL_ACCOUNTS
|
||||||
import ujson
|
import ujson
|
||||||
|
|
||||||
class CustomProfileFieldTestCase(ZulipTestCase):
|
class CustomProfileFieldTestCase(ZulipTestCase):
|
||||||
|
@ -120,6 +121,53 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
|
||||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
|
||||||
|
def test_create_default_external_account_field(self) -> None:
|
||||||
|
self.login(self.example_email("iago"))
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
field_type = CustomProfileField.EXTERNAL_ACCOUNT # type: int
|
||||||
|
field_data = ujson.dumps({
|
||||||
|
'subtype': 'twitter'
|
||||||
|
}) # type: str
|
||||||
|
invalid_field_name = "Not required field name" # type: str
|
||||||
|
invalid_field_hint = "Not required field hint" # type: str
|
||||||
|
|
||||||
|
result = self.client_post("/json/realm/profile_fields",
|
||||||
|
info=dict(
|
||||||
|
field_type=field_type,
|
||||||
|
field_data=field_data,
|
||||||
|
hint=invalid_field_hint,
|
||||||
|
name=invalid_field_name,
|
||||||
|
))
|
||||||
|
self.assert_json_success(result)
|
||||||
|
# Sliently overwrite name and hint with values set in default fields dict
|
||||||
|
# for default custom external account fields.
|
||||||
|
with self.assertRaises(CustomProfileField.DoesNotExist):
|
||||||
|
field = CustomProfileField.objects.get(name=invalid_field_name, realm=realm)
|
||||||
|
# The field is created with 'Twitter' name as per values in default fields dict
|
||||||
|
field = CustomProfileField.objects.get(name='Twitter')
|
||||||
|
self.assertEqual(field.name, DEFAULT_EXTERNAL_ACCOUNTS['twitter']['name'])
|
||||||
|
self.assertEqual(field.hint, DEFAULT_EXTERNAL_ACCOUNTS['twitter']['hint'])
|
||||||
|
|
||||||
|
result = self.client_delete("/json/realm/profile_fields/{}".format(field.id))
|
||||||
|
self.assert_json_success(result)
|
||||||
|
|
||||||
|
# Should also work without name or hint and only external field type and subtype data
|
||||||
|
result = self.client_post("/json/realm/profile_fields",
|
||||||
|
info=dict(field_type=field_type, field_data=field_data))
|
||||||
|
self.assert_json_success(result)
|
||||||
|
|
||||||
|
# Default external account field data cannot be updated
|
||||||
|
field = CustomProfileField.objects.get(name="Twitter", realm=realm)
|
||||||
|
result = self.client_patch(
|
||||||
|
"/json/realm/profile_fields/{}".format(field.id),
|
||||||
|
info={'name': 'Twitter username',
|
||||||
|
'field_type': CustomProfileField.EXTERNAL_ACCOUNT}
|
||||||
|
)
|
||||||
|
self.assert_json_error(result, 'Default custom field cannot be updated.')
|
||||||
|
|
||||||
|
result = self.client_delete("/json/realm/profile_fields/{}".format(field.id))
|
||||||
|
self.assert_json_success(result)
|
||||||
|
|
||||||
def test_create_external_account_field(self) -> None:
|
def test_create_external_account_field(self) -> None:
|
||||||
self.login(self.example_email("iago"))
|
self.login(self.example_email("iago"))
|
||||||
realm = get_realm('zulip')
|
realm = get_realm('zulip')
|
||||||
|
@ -297,7 +345,6 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
|
||||||
def test_update(self) -> None:
|
def test_update(self) -> None:
|
||||||
self.login(self.example_email("iago"))
|
self.login(self.example_email("iago"))
|
||||||
realm = get_realm('zulip')
|
realm = get_realm('zulip')
|
||||||
|
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
"/json/realm/profile_fields/100",
|
"/json/realm/profile_fields/100",
|
||||||
info={'name': 'Phone Number',
|
info={'name': 'Phone Number',
|
||||||
|
|
|
@ -12,6 +12,7 @@ from zerver.lib.actions import (try_add_realm_custom_profile_field,
|
||||||
try_update_realm_custom_profile_field,
|
try_update_realm_custom_profile_field,
|
||||||
do_update_user_custom_profile_data,
|
do_update_user_custom_profile_data,
|
||||||
try_reorder_realm_custom_profile_fields,
|
try_reorder_realm_custom_profile_fields,
|
||||||
|
try_add_realm_default_custom_profile_field,
|
||||||
check_remove_custom_profile_field_value)
|
check_remove_custom_profile_field_value)
|
||||||
from zerver.lib.response import json_success, json_error
|
from zerver.lib.response import json_success, json_error
|
||||||
from zerver.lib.types import ProfileFieldData
|
from zerver.lib.types import ProfileFieldData
|
||||||
|
@ -57,13 +58,24 @@ def validate_custom_field_data(field_type: int,
|
||||||
if error:
|
if error:
|
||||||
raise JsonableError(error)
|
raise JsonableError(error)
|
||||||
|
|
||||||
|
def is_default_external_field(field_type: int, field_data: ProfileFieldData) -> bool:
|
||||||
|
if field_type != CustomProfileField.EXTERNAL_ACCOUNT:
|
||||||
|
return False
|
||||||
|
if field_data['subtype'] == 'custom':
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def validate_custom_profile_field(name: str, hint: str, field_type: int,
|
def validate_custom_profile_field(name: str, hint: str, field_type: int,
|
||||||
field_data: ProfileFieldData) -> None:
|
field_data: ProfileFieldData) -> None:
|
||||||
# Validate field data
|
# Validate field data
|
||||||
validate_custom_field_data(field_type, field_data)
|
validate_custom_field_data(field_type, field_data)
|
||||||
|
|
||||||
# Validate field name, hint and type
|
if not is_default_external_field(field_type, field_data):
|
||||||
|
# If field is default external field then we will fetch all data
|
||||||
|
# from our default field dictionary, so no need to validate name or hint
|
||||||
|
# Validate field name, hint if not default external account field
|
||||||
validate_field_name_and_hint(name, hint)
|
validate_field_name_and_hint(name, hint)
|
||||||
|
|
||||||
field_types = [i[0] for i in CustomProfileField.FIELD_TYPE_CHOICES]
|
field_types = [i[0] for i in CustomProfileField.FIELD_TYPE_CHOICES]
|
||||||
if field_type not in field_types:
|
if field_type not in field_types:
|
||||||
raise JsonableError(_("Invalid field type."))
|
raise JsonableError(_("Invalid field type."))
|
||||||
|
@ -72,14 +84,22 @@ def validate_custom_profile_field(name: str, hint: str, field_type: int,
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
def create_realm_custom_profile_field(request: HttpRequest,
|
def create_realm_custom_profile_field(request: HttpRequest,
|
||||||
user_profile: UserProfile,
|
user_profile: UserProfile,
|
||||||
name: str=REQ(),
|
name: str=REQ(default=''),
|
||||||
hint: str=REQ(default=''),
|
hint: str=REQ(default=''),
|
||||||
field_data: ProfileFieldData=REQ(default={},
|
field_data: ProfileFieldData=REQ(default={},
|
||||||
converter=ujson.loads),
|
converter=ujson.loads),
|
||||||
field_type: int=REQ(validator=check_int)) -> HttpResponse:
|
field_type: int=REQ(validator=check_int)) -> HttpResponse:
|
||||||
|
|
||||||
validate_custom_profile_field(name, hint, field_type, field_data)
|
validate_custom_profile_field(name, hint, field_type, field_data)
|
||||||
try:
|
try:
|
||||||
|
if is_default_external_field(field_type, field_data):
|
||||||
|
field_subtype = '' # type: str
|
||||||
|
field_subtype = field_data['subtype'] # type: ignore # key for "Union[Dict[str, str], str]" can be str
|
||||||
|
field = try_add_realm_default_custom_profile_field(
|
||||||
|
realm=user_profile.realm,
|
||||||
|
field_subtype=field_subtype,
|
||||||
|
)
|
||||||
|
return json_success({'id': field.id})
|
||||||
|
else:
|
||||||
field = try_add_realm_custom_profile_field(
|
field = try_add_realm_custom_profile_field(
|
||||||
realm=user_profile.realm,
|
realm=user_profile.realm,
|
||||||
name=name,
|
name=name,
|
||||||
|
@ -107,7 +127,7 @@ def delete_realm_custom_profile_field(request: HttpRequest, user_profile: UserPr
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
def update_realm_custom_profile_field(request: HttpRequest, user_profile: UserProfile,
|
def update_realm_custom_profile_field(request: HttpRequest, user_profile: UserProfile,
|
||||||
field_id: int,
|
field_id: int,
|
||||||
name: str=REQ(),
|
name: str=REQ(default=''),
|
||||||
hint: str=REQ(default=''),
|
hint: str=REQ(default=''),
|
||||||
field_data: ProfileFieldData=REQ(default={},
|
field_data: ProfileFieldData=REQ(default={},
|
||||||
converter=ujson.loads),
|
converter=ujson.loads),
|
||||||
|
@ -118,6 +138,10 @@ def update_realm_custom_profile_field(request: HttpRequest, user_profile: UserPr
|
||||||
except CustomProfileField.DoesNotExist:
|
except CustomProfileField.DoesNotExist:
|
||||||
return json_error(_('Field id {id} not found.').format(id=field_id))
|
return json_error(_('Field id {id} not found.').format(id=field_id))
|
||||||
|
|
||||||
|
if field.field_type == CustomProfileField.EXTERNAL_ACCOUNT:
|
||||||
|
if is_default_external_field(field.field_type, ujson.loads(field.field_data)):
|
||||||
|
return json_error(_("Default custom field cannot be updated."))
|
||||||
|
|
||||||
validate_custom_profile_field(name, hint, field.field_type, field_data)
|
validate_custom_profile_field(name, hint, field.field_type, field_data)
|
||||||
try:
|
try:
|
||||||
try_update_realm_custom_profile_field(realm, field, name, hint=hint,
|
try_update_realm_custom_profile_field(realm, field, name, hint=hint,
|
||||||
|
|
|
@ -15,7 +15,7 @@ from django.utils.timezone import timedelta as timezone_timedelta
|
||||||
|
|
||||||
from zerver.lib.actions import STREAM_ASSIGNMENT_COLORS, check_add_realm_emoji, \
|
from zerver.lib.actions import STREAM_ASSIGNMENT_COLORS, check_add_realm_emoji, \
|
||||||
do_change_is_admin, do_send_messages, do_update_user_custom_profile_data, \
|
do_change_is_admin, do_send_messages, do_update_user_custom_profile_data, \
|
||||||
try_add_realm_custom_profile_field
|
try_add_realm_custom_profile_field, try_add_realm_default_custom_profile_field
|
||||||
from zerver.lib.bulk_create import bulk_create_streams, bulk_create_users
|
from zerver.lib.bulk_create import bulk_create_streams, bulk_create_users
|
||||||
from zerver.lib.cache import cache_set
|
from zerver.lib.cache import cache_set
|
||||||
from zerver.lib.generate_test_data import create_test_data
|
from zerver.lib.generate_test_data import create_test_data
|
||||||
|
@ -360,12 +360,7 @@ class Command(BaseCommand):
|
||||||
hint="Or your personal blog's URL")
|
hint="Or your personal blog's URL")
|
||||||
mentor = try_add_realm_custom_profile_field(zulip_realm, "Mentor",
|
mentor = try_add_realm_custom_profile_field(zulip_realm, "Mentor",
|
||||||
CustomProfileField.USER)
|
CustomProfileField.USER)
|
||||||
external_account_field_data = {
|
github_profile = try_add_realm_default_custom_profile_field(zulip_realm, "github")
|
||||||
'subtype': 'github'
|
|
||||||
} # type: ProfileFieldData
|
|
||||||
github_profile = try_add_realm_custom_profile_field(zulip_realm, "GitHub",
|
|
||||||
CustomProfileField.EXTERNAL_ACCOUNT,
|
|
||||||
field_data=external_account_field_data)
|
|
||||||
|
|
||||||
# Fill in values for Iago and Hamlet
|
# Fill in values for Iago and Hamlet
|
||||||
hamlet = get_user("hamlet@zulip.com", zulip_realm)
|
hamlet = get_user("hamlet@zulip.com", zulip_realm)
|
||||||
|
|
Loading…
Reference in New Issue