custom fields: Add user type of custom fields.

Fixes #8878
This commit is contained in:
Yashashvi Dave
2018-05-06 13:13:38 +05:30
committed by Tim Abbott
parent 1e948ab405
commit e82c879b85
7 changed files with 69 additions and 13 deletions

View File

@@ -89,6 +89,8 @@ exports.add_custom_profile_fields_to_settings = function () {
type = "date"; type = "date";
} else if (field_type === "URL") { } else if (field_type === "URL") {
type = "url"; type = "url";
} else if (field_type === "User") {
type = "user";
} else { } else {
blueslip.error("Undefined field type."); blueslip.error("Undefined field type.");
} }

View File

@@ -7,13 +7,15 @@ ViewFuncT = TypeVar('ViewFuncT', bound=Callable[..., HttpResponse])
# including many examples # including many examples
Validator = Callable[[str, object], Optional[str]] Validator = Callable[[str, object], Optional[str]]
ExtendedValidator = Callable[[str, 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]]] ProfileDataElement = Dict[str, Union[int, float, Optional[str]]]
ProfileData = List[ProfileDataElement] ProfileData = List[ProfileDataElement]
FieldElement = Tuple[int, str, Validator, Callable[[Any], Any]] FieldElement = Tuple[int, str, Validator, Callable[[Any], Any]]
ExtendedFieldElement = Tuple[int, str, ExtendedValidator, 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]] ProfileFieldData = Dict[str, Dict[str, str]]

View File

@@ -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),
),
]

View File

@@ -36,7 +36,8 @@ from zerver.lib.validator import check_int, check_float, \
check_url check_url
from zerver.lib.name_restrictions import is_disposable_domain from zerver.lib.name_restrictions import is_disposable_domain
from zerver.lib.types import Validator, ExtendedValidator, \ from zerver.lib.types import Validator, ExtendedValidator, \
ProfileDataElement, ProfileData, FieldTypeData ProfileDataElement, ProfileData, FieldTypeData, FieldElement, \
RealmUserValidator
from django.utils.encoding import force_text from django.utils.encoding import force_text
@@ -1971,16 +1972,24 @@ class CustomProfileField(models.Model):
CHOICE = 3 CHOICE = 3
DATE = 4 DATE = 4
URL = 5 URL = 5
USER = 6
# These are the fields whose validators require field_data # These are the fields whose validators require more than var_name
# argument as well. # and value argument. i.e. CHOICE require field_data, USER require
EXTENDED_FIELD_TYPE_DATA = [ # realm as argument.
CHOICE_FIELD_TYPE_DATA = [
(CHOICE, str(_('Choice')), validate_choice_field, str), (CHOICE, str(_('Choice')), validate_choice_field, str),
] # type: FieldTypeData ] # type: FieldTypeData
USER_FIELD_TYPE_DATA = [
(USER, str(_('User')), check_valid_user_id, int),
] # type: FieldTypeData
EXTENDED_FIELD_VALIDATORS = { CHOICE_FIELD_VALIDATORS = {
item[0]: item[2] for item in EXTENDED_FIELD_TYPE_DATA item[0]: item[2] for item in CHOICE_FIELD_TYPE_DATA
} # type: Dict[int, ExtendedValidator] } # 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 = [ FIELD_TYPE_DATA = [
# Type, Name, Validator, Converter # Type, Name, Validator, Converter
@@ -1990,7 +1999,7 @@ class CustomProfileField(models.Model):
(URL, str(_('URL')), check_url, str), (URL, str(_('URL')), check_url, str),
] # type: FieldTypeData ] # 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_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]] FIELD_CONVERTERS = {item[0]: item[3] for item in ALL_FIELD_TYPES} # type: Dict[int, Callable[[Any], Any]]

View File

@@ -342,6 +342,21 @@ class CustomProfileFieldTest(ZulipTestCase):
self.assert_error_update_invalid_value(field_name, u"not URL", self.assert_error_update_invalid_value(field_name, u"not URL",
u"{} is not a URL".format(field_name)) 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: def test_update_profile_data_successfully(self) -> None:
self.login(self.example_email("iago")) self.login(self.example_email("iago"))
realm = get_realm('zulip') realm = get_realm('zulip')
@@ -352,6 +367,7 @@ class CustomProfileFieldTest(ZulipTestCase):
('Favorite editor', 'vim'), ('Favorite editor', 'vim'),
('Birthday', '1909-3-5'), ('Birthday', '1909-3-5'),
('GitHub profile', 'https://github.com/ABC'), ('GitHub profile', 'https://github.com/ABC'),
('Mentor', self.example_user("cordelia").id),
] ]
data = [] data = []

View File

@@ -131,18 +131,21 @@ def update_user_custom_profile_data(
return json_error(_('Field id {id} not found.').format(id=field_id)) return json_error(_('Field id {id} not found.').format(id=field_id))
validators = CustomProfileField.FIELD_VALIDATORS validators = CustomProfileField.FIELD_VALIDATORS
extended_validators = CustomProfileField.EXTENDED_FIELD_VALIDATORS
field_type = field.field_type field_type = field.field_type
value = item['value'] value = item['value']
var_name = '{}'.format(field.name) var_name = '{}'.format(field.name)
if field_type in validators: if field_type in validators:
validator = validators[field_type] validator = validators[field_type]
result = validator(var_name, value) result = validator(var_name, value)
else: elif field_type == CustomProfileField.CHOICE:
# Check extended validators. choice_field_validator = CustomProfileField.CHOICE_FIELD_VALIDATORS[field_type]
extended_validator = extended_validators[field_type]
field_data = field.field_data 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: if result is not None:
return json_error(result) return json_error(result)

View File

@@ -327,6 +327,8 @@ class Command(BaseCommand):
favorite_website = try_add_realm_custom_profile_field(zulip_realm, "GitHub profile", favorite_website = try_add_realm_custom_profile_field(zulip_realm, "GitHub profile",
CustomProfileField.URL, CustomProfileField.URL,
hint="Or your personal blog's 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 # Fill in values for Iago and Hamlet
hamlet = get_user("hamlet@zulip.com", zulip_realm) hamlet = get_user("hamlet@zulip.com", zulip_realm)
@@ -337,6 +339,7 @@ class Command(BaseCommand):
{"id": favorite_editor.id, "value": "emacs"}, {"id": favorite_editor.id, "value": "emacs"},
{"id": birthday.id, "value": "2000-1-1"}, {"id": birthday.id, "value": "2000-1-1"},
{"id": favorite_website.id, "value": "https://github.com/zulip/zulip"}, {"id": favorite_website.id, "value": "https://github.com/zulip/zulip"},
{"id": mentor.id, "value": hamlet.id},
]) ])
do_update_user_custom_profile_data(hamlet, [ do_update_user_custom_profile_data(hamlet, [
{"id": phone_number.id, "value": "+0-11-23-456-7890"}, {"id": phone_number.id, "value": "+0-11-23-456-7890"},
@@ -345,6 +348,7 @@ class Command(BaseCommand):
{"id": favorite_editor.id, "value": "vim"}, {"id": favorite_editor.id, "value": "vim"},
{"id": birthday.id, "value": "1900-1-1"}, {"id": birthday.id, "value": "1900-1-1"},
{"id": favorite_website.id, "value": "https://blog.zulig.org"}, {"id": favorite_website.id, "value": "https://blog.zulig.org"},
{"id": mentor.id, "value": iago.id},
]) ])
else: else:
zulip_realm = get_realm("zulip") zulip_realm = get_realm("zulip")