diff --git a/static/js/settings_account.js b/static/js/settings_account.js index 2264ec6dc4..035de6332c 100644 --- a/static/js/settings_account.js +++ b/static/js/settings_account.js @@ -89,6 +89,8 @@ exports.add_custom_profile_fields_to_settings = function () { type = "date"; } else if (field_type === "URL") { type = "url"; + } else if (field_type === "User") { + type = "user"; } else { blueslip.error("Undefined field type."); } diff --git a/zerver/lib/types.py b/zerver/lib/types.py index 9bfcc111f6..17cb46a703 100644 --- a/zerver/lib/types.py +++ b/zerver/lib/types.py @@ -7,13 +7,15 @@ ViewFuncT = TypeVar('ViewFuncT', bound=Callable[..., HttpResponse]) # including many examples Validator = Callable[[str, object], Optional[str]] ExtendedValidator = Callable[[str, str, object], Optional[str]] +RealmUserValidator = Callable[[int, object, bool], Optional[str]] ProfileDataElement = Dict[str, Union[int, float, Optional[str]]] ProfileData = List[ProfileDataElement] FieldElement = Tuple[int, str, Validator, Callable[[Any], Any]] ExtendedFieldElement = Tuple[int, str, ExtendedValidator, Callable[[Any], Any]] +UserFieldElement = Tuple[int, str, RealmUserValidator, Callable[[Any], Any]] -FieldTypeData = List[Union[FieldElement, ExtendedFieldElement]] +FieldTypeData = List[Union[FieldElement, ExtendedFieldElement, UserFieldElement]] ProfileFieldData = Dict[str, Dict[str, str]] diff --git a/zerver/migrations/0172_add_user_type_of_custom_profile_field.py b/zerver/migrations/0172_add_user_type_of_custom_profile_field.py new file mode 100644 index 0000000000..e25fae45fd --- /dev/null +++ b/zerver/migrations/0172_add_user_type_of_custom_profile_field.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-05-08 17:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('zerver', '0171_userprofile_dense_mode'), + ] + + operations = [ + migrations.AlterField( + model_name='customprofilefield', + name='field_type', + field=models.PositiveSmallIntegerField(choices=[(1, 'Short text'), (2, 'Long text'), (4, 'Date'), (5, 'URL'), (3, 'Choice'), (6, 'User')], default=1), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 3f23b84acd..d589262001 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -36,7 +36,8 @@ from zerver.lib.validator import check_int, check_float, \ check_url from zerver.lib.name_restrictions import is_disposable_domain from zerver.lib.types import Validator, ExtendedValidator, \ - ProfileDataElement, ProfileData, FieldTypeData + ProfileDataElement, ProfileData, FieldTypeData, FieldElement, \ + RealmUserValidator from django.utils.encoding import force_text @@ -1971,16 +1972,24 @@ class CustomProfileField(models.Model): CHOICE = 3 DATE = 4 URL = 5 + USER = 6 - # These are the fields whose validators require field_data - # argument as well. - EXTENDED_FIELD_TYPE_DATA = [ + # These are the fields whose validators require more than var_name + # and value argument. i.e. CHOICE require field_data, USER require + # realm as argument. + CHOICE_FIELD_TYPE_DATA = [ (CHOICE, str(_('Choice')), validate_choice_field, str), ] # type: FieldTypeData + USER_FIELD_TYPE_DATA = [ + (USER, str(_('User')), check_valid_user_id, int), + ] # type: FieldTypeData - EXTENDED_FIELD_VALIDATORS = { - item[0]: item[2] for item in EXTENDED_FIELD_TYPE_DATA + CHOICE_FIELD_VALIDATORS = { + item[0]: item[2] for item in CHOICE_FIELD_TYPE_DATA } # type: Dict[int, ExtendedValidator] + USER_FIELD_VALIDATORS = { + item[0]: item[2] for item in USER_FIELD_TYPE_DATA + } # type: Dict[int, RealmUserValidator] FIELD_TYPE_DATA = [ # Type, Name, Validator, Converter @@ -1990,7 +1999,7 @@ class CustomProfileField(models.Model): (URL, str(_('URL')), check_url, str), ] # type: FieldTypeData - ALL_FIELD_TYPES = FIELD_TYPE_DATA + EXTENDED_FIELD_TYPE_DATA + ALL_FIELD_TYPES = FIELD_TYPE_DATA + CHOICE_FIELD_TYPE_DATA + USER_FIELD_TYPE_DATA FIELD_VALIDATORS = {item[0]: item[2] for item in FIELD_TYPE_DATA} # type: Dict[int, Validator] FIELD_CONVERTERS = {item[0]: item[3] for item in ALL_FIELD_TYPES} # type: Dict[int, Callable[[Any], Any]] diff --git a/zerver/tests/test_custom_profile_data.py b/zerver/tests/test_custom_profile_data.py index 36d8413e8b..00eb3a1c45 100644 --- a/zerver/tests/test_custom_profile_data.py +++ b/zerver/tests/test_custom_profile_data.py @@ -342,6 +342,21 @@ class CustomProfileFieldTest(ZulipTestCase): self.assert_error_update_invalid_value(field_name, u"not URL", u"{} is not a URL".format(field_name)) + def test_update_invalid_user_field(self) -> None: + field_name = "Mentor" + invalid_user_id = 1000 + self.assert_error_update_invalid_value(field_name, invalid_user_id, + u"Invalid user ID: %d" + % (invalid_user_id)) + + def test_create_field_of_type_user(self) -> None: + self.login(self.example_email("iago")) + data = {"name": "Your mentor", + "field_type": CustomProfileField.USER, + } + result = self.client_post("/json/realm/profile_fields", info=data) + self.assert_json_success(result) + def test_update_profile_data_successfully(self) -> None: self.login(self.example_email("iago")) realm = get_realm('zulip') @@ -352,6 +367,7 @@ class CustomProfileFieldTest(ZulipTestCase): ('Favorite editor', 'vim'), ('Birthday', '1909-3-5'), ('GitHub profile', 'https://github.com/ABC'), + ('Mentor', self.example_user("cordelia").id), ] data = [] diff --git a/zerver/views/custom_profile_fields.py b/zerver/views/custom_profile_fields.py index 750e2833d9..9ac52263e2 100644 --- a/zerver/views/custom_profile_fields.py +++ b/zerver/views/custom_profile_fields.py @@ -131,18 +131,21 @@ def update_user_custom_profile_data( return json_error(_('Field id {id} not found.').format(id=field_id)) validators = CustomProfileField.FIELD_VALIDATORS - extended_validators = CustomProfileField.EXTENDED_FIELD_VALIDATORS field_type = field.field_type value = item['value'] var_name = '{}'.format(field.name) if field_type in validators: validator = validators[field_type] result = validator(var_name, value) - else: - # Check extended validators. - extended_validator = extended_validators[field_type] + elif field_type == CustomProfileField.CHOICE: + choice_field_validator = CustomProfileField.CHOICE_FIELD_VALIDATORS[field_type] field_data = field.field_data - result = extended_validator(var_name, field_data, value) + result = choice_field_validator(var_name, field_data, value) + elif field_type == CustomProfileField.USER: + user_field_validator = CustomProfileField.USER_FIELD_VALIDATORS[field_type] + result = user_field_validator(user_profile.realm.id, value, False) + else: + raise AssertionError("Invalid field type") if result is not None: return json_error(result) diff --git a/zilencer/management/commands/populate_db.py b/zilencer/management/commands/populate_db.py index 5e707e035c..2bba22c826 100644 --- a/zilencer/management/commands/populate_db.py +++ b/zilencer/management/commands/populate_db.py @@ -327,6 +327,8 @@ class Command(BaseCommand): favorite_website = try_add_realm_custom_profile_field(zulip_realm, "GitHub profile", CustomProfileField.URL, hint="Or your personal blog's URL") + mentor = try_add_realm_custom_profile_field(zulip_realm, "Mentor", + CustomProfileField.USER) # Fill in values for Iago and Hamlet hamlet = get_user("hamlet@zulip.com", zulip_realm) @@ -337,6 +339,7 @@ class Command(BaseCommand): {"id": favorite_editor.id, "value": "emacs"}, {"id": birthday.id, "value": "2000-1-1"}, {"id": favorite_website.id, "value": "https://github.com/zulip/zulip"}, + {"id": mentor.id, "value": hamlet.id}, ]) do_update_user_custom_profile_data(hamlet, [ {"id": phone_number.id, "value": "+0-11-23-456-7890"}, @@ -345,6 +348,7 @@ class Command(BaseCommand): {"id": favorite_editor.id, "value": "vim"}, {"id": birthday.id, "value": "1900-1-1"}, {"id": favorite_website.id, "value": "https://blog.zulig.org"}, + {"id": mentor.id, "value": iago.id}, ]) else: zulip_realm = get_realm("zulip")