mirror of https://github.com/zulip/zulip.git
models: Add `external_account` in custom profile field types.
Add new custom profile field type, External account. External account field links user's social media profile with account. e.g. GitHub, Twitter, etc. Fixes part of #12302
This commit is contained in:
parent
3368589df2
commit
d7ee2aced1
|
@ -7,17 +7,27 @@ set_global('Sortable', {create: () => {}});
|
|||
|
||||
const SHORT_TEXT_ID = 1;
|
||||
const CHOICE_ID = 3;
|
||||
const EXTERNAL_ACCOUNT_ID = 7;
|
||||
|
||||
const SHORT_TEXT_NAME = "Short Text";
|
||||
const CHOICE_NAME = "Choice";
|
||||
const EXTERNAL_ACCOUNT_NAME = "External account";
|
||||
|
||||
page_params.custom_profile_fields = {};
|
||||
page_params.realm_default_external_accounts = JSON.stringify({});
|
||||
|
||||
page_params.custom_profile_field_types = {
|
||||
SHORT_TEXT: {
|
||||
id: SHORT_TEXT_ID,
|
||||
name: "Short Text",
|
||||
name: SHORT_TEXT_NAME,
|
||||
},
|
||||
CHOICE: {
|
||||
id: CHOICE_ID,
|
||||
name: "Choice",
|
||||
name: CHOICE_NAME,
|
||||
},
|
||||
EXTERNAL_ACCOUNT: {
|
||||
id: EXTERNAL_ACCOUNT_ID,
|
||||
name: EXTERNAL_ACCOUNT_NAME,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -78,6 +88,25 @@ run_test('populate_profile_fields', () => {
|
|||
},
|
||||
]),
|
||||
},
|
||||
{
|
||||
type: EXTERNAL_ACCOUNT_ID,
|
||||
id: 20,
|
||||
name: 'github profile',
|
||||
hint: 'username only',
|
||||
field_data: JSON.stringify({
|
||||
subtype: 'github',
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: EXTERNAL_ACCOUNT_ID,
|
||||
id: 21,
|
||||
name: 'zulip profile',
|
||||
hint: 'username only',
|
||||
field_data: JSON.stringify({
|
||||
subtype: 'custom',
|
||||
url_pattern: 'https://chat.zulip.com/%(username)s',
|
||||
}),
|
||||
},
|
||||
];
|
||||
const expected_template_data = [
|
||||
{
|
||||
|
@ -85,25 +114,59 @@ run_test('populate_profile_fields', () => {
|
|||
id: 10,
|
||||
name: 'favorite color',
|
||||
hint: 'blue?',
|
||||
type: 'Short Text',
|
||||
type: SHORT_TEXT_NAME,
|
||||
choices: [],
|
||||
is_choice_field: false,
|
||||
is_external_account_field: false,
|
||||
},
|
||||
can_modify: true,
|
||||
realm_default_external_accounts:
|
||||
page_params.realm_default_external_accounts,
|
||||
},
|
||||
{
|
||||
profile_field: {
|
||||
id: 30,
|
||||
name: 'meal',
|
||||
hint: 'lunch',
|
||||
type: 'Choice',
|
||||
type: CHOICE_NAME,
|
||||
choices: [
|
||||
{order: 0, value: 0, text: 'lunch'},
|
||||
{order: 1, value: 1, text: 'dinner'},
|
||||
],
|
||||
is_choice_field: true,
|
||||
is_external_account_field: false,
|
||||
},
|
||||
can_modify: true,
|
||||
realm_default_external_accounts:
|
||||
page_params.realm_default_external_accounts,
|
||||
},
|
||||
{
|
||||
profile_field: {
|
||||
id: 20,
|
||||
name: 'github profile',
|
||||
hint: 'username only',
|
||||
type: EXTERNAL_ACCOUNT_NAME,
|
||||
choices: [],
|
||||
is_choice_field: false,
|
||||
is_external_account_field: true,
|
||||
},
|
||||
can_modify: true,
|
||||
realm_default_external_accounts:
|
||||
page_params.realm_default_external_accounts,
|
||||
},
|
||||
{
|
||||
profile_field: {
|
||||
id: 21,
|
||||
name: 'zulip profile',
|
||||
hint: 'username only',
|
||||
type: EXTERNAL_ACCOUNT_NAME,
|
||||
choices: [],
|
||||
is_choice_field: false,
|
||||
is_external_account_field: true,
|
||||
},
|
||||
can_modify: true,
|
||||
realm_default_external_accounts:
|
||||
page_params.realm_default_external_accounts,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ exports.build_page = function () {
|
|||
plan_includes_wide_organization_logo: page_params.plan_includes_wide_organization_logo,
|
||||
upgrade_text_for_wide_organization_logo:
|
||||
page_params.upgrade_text_for_wide_organization_logo,
|
||||
realm_default_external_accounts: page_params.realm_default_external_accounts,
|
||||
};
|
||||
|
||||
options.admin_settings_label = admin_settings_label;
|
||||
|
|
|
@ -104,6 +104,7 @@ function get_custom_profile_field_data(user, field, field_types, dateFormat) {
|
|||
profile_field.name = field.name;
|
||||
profile_field.is_user_field = false;
|
||||
profile_field.is_link = field_type === field_types.URL.id;
|
||||
profile_field.is_external_account = field_type === field_types.EXTERNAL_ACCOUNT.id;
|
||||
profile_field.type = field_type;
|
||||
|
||||
switch (field_type) {
|
||||
|
@ -124,6 +125,11 @@ function get_custom_profile_field_data(user, field, field_types, dateFormat) {
|
|||
profile_field.value = field_value.value;
|
||||
profile_field.rendered_value = field_value.rendered_value;
|
||||
break;
|
||||
case field_types.EXTERNAL_ACCOUNT.id:
|
||||
profile_field.value = field_value.value;
|
||||
profile_field.field_data = JSON.parse(field.field_data);
|
||||
profile_field.link = settings_profile_fields.get_external_account_link(profile_field);
|
||||
break;
|
||||
default:
|
||||
profile_field.value = field_value.value;
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ exports.append_custom_profile_fields = function (element_id, user_id) {
|
|||
all_field_template_types[all_field_types.CHOICE.id] = "choice";
|
||||
all_field_template_types[all_field_types.USER.id] = "user";
|
||||
all_field_template_types[all_field_types.DATE.id] = "date";
|
||||
all_field_template_types[all_field_types.EXTERNAL_ACCOUNT.id] = "text";
|
||||
|
||||
all_custom_fields.forEach(function (field) {
|
||||
var field_value = people.get_custom_profile_data(user_id, field.id);
|
||||
|
|
|
@ -76,6 +76,15 @@ function read_choice_field_data_from_form(field_elem) {
|
|||
return field_data;
|
||||
}
|
||||
|
||||
function read_external_account_field_data(field_elem) {
|
||||
var field_data = {};
|
||||
field_data.subtype = $(field_elem).find('select[name=external_acc_field_type]').val();
|
||||
if (field_data.subtype === "custom") {
|
||||
field_data.url_pattern = $(field_elem).find('input[name=url_pattern]').val();
|
||||
}
|
||||
return field_data;
|
||||
}
|
||||
|
||||
function update_choice_delete_btn(container, display_flag) {
|
||||
var no_of_choice_row = container.find(".choice-row").length;
|
||||
|
||||
|
@ -106,12 +115,20 @@ function clear_form_data() {
|
|||
create_choice_row($("#profile_field_choices"));
|
||||
update_choice_delete_btn($("#profile_field_choices"), false);
|
||||
$("#profile_field_choices_row").hide();
|
||||
// Clear external account field form
|
||||
$("#custom_field_url_pattern").val("");
|
||||
$("#custom_external_account_url_pattern").hide();
|
||||
$("#profile_field_external_accounts").hide();
|
||||
$("#profile_field_external_accounts_type").val(0);
|
||||
}
|
||||
|
||||
function read_field_data_from_form(field_type_id, field_elem) {
|
||||
// Only read field data if we are creating a choice field
|
||||
// or external account field.
|
||||
if (field_type_id === field_types.CHOICE.id) {
|
||||
return read_choice_field_data_from_form(field_elem);
|
||||
} else if (field_type_id === field_types.EXTERNAL_ACCOUNT.id) {
|
||||
return read_external_account_field_data(field_elem);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,7 +138,6 @@ function create_profile_field(e) {
|
|||
|
||||
var field_data = {};
|
||||
var field_type = $('#profile_field_type').val();
|
||||
|
||||
var opts = {
|
||||
success_continuation: clear_form_data,
|
||||
};
|
||||
|
@ -186,6 +202,15 @@ exports.parse_field_choices_from_field_data = function (field_data) {
|
|||
return choices;
|
||||
};
|
||||
|
||||
function set_up_external_account_field_edit_form(field_elem, url_pattern_val) {
|
||||
if (field_elem.form.find('select[name=external_acc_field_type]').val() === 'custom') {
|
||||
field_elem.form.find('input[name=url_pattern]').val(url_pattern_val);
|
||||
field_elem.form.find('.custom_external_account_detail').show();
|
||||
} else {
|
||||
field_elem.form.find('.custom_external_account_detail').hide();
|
||||
}
|
||||
}
|
||||
|
||||
function set_up_choices_field_edit_form(profile_field, field_data) {
|
||||
// Re-render field choices in edit form to load initial choice data
|
||||
var choice_list = profile_field.form.find('.edit_profile_field_choices_container');
|
||||
|
@ -229,6 +254,11 @@ function open_edit_form(e) {
|
|||
set_up_choices_field_edit_form(profile_field, field_data);
|
||||
}
|
||||
|
||||
if (parseInt(field.type, 10) === field_types.EXTERNAL_ACCOUNT.id) {
|
||||
profile_field.form.find('select[name=external_acc_field_type]').val(field_data.subtype);
|
||||
set_up_external_account_field_edit_form(profile_field, field_data.url_pattern);
|
||||
}
|
||||
|
||||
profile_field.form.find('.reset').on("click", function () {
|
||||
profile_field.form.hide();
|
||||
profile_field.row.show();
|
||||
|
@ -254,6 +284,11 @@ function open_edit_form(e) {
|
|||
|
||||
profile_field.form.find(".edit_profile_field_choices_container").on("input", ".choice-row input", add_choice_row);
|
||||
profile_field.form.find(".edit_profile_field_choices_container").on("click", "button.delete-choice", delete_choice_row);
|
||||
$(".profile_field_external_accounts_edit select").on('change', function (e) {
|
||||
var field_id = $(e.target).closest('.profile-field-form').attr('data-profile-field-id');
|
||||
var field_form = get_profile_field_info(field_id);
|
||||
set_up_external_account_field_edit_form(field_form, "");
|
||||
});
|
||||
}
|
||||
|
||||
exports.reset = function () {
|
||||
|
@ -307,8 +342,11 @@ exports.do_populate_profile_fields = function (profile_fields_data) {
|
|||
type: exports.field_type_id_to_string(profile_field.type),
|
||||
choices: choices,
|
||||
is_choice_field: profile_field.type === field_types.CHOICE.id,
|
||||
is_external_account_field: profile_field.type ===
|
||||
field_types.EXTERNAL_ACCOUNT.id,
|
||||
},
|
||||
can_modify: page_params.is_admin,
|
||||
realm_default_external_accounts: page_params.realm_default_external_accounts,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -355,6 +393,46 @@ function set_up_choices_field() {
|
|||
$("#profile_field_choices").on("click", "button.delete-choice", delete_choice_row);
|
||||
}
|
||||
|
||||
function set_up_external_account_field() {
|
||||
var field_elem = $("#profile_field_external_accounts");
|
||||
var field_url_pattern_elem = $("#custom_external_account_url_pattern");
|
||||
|
||||
$('#profile_field_type').on('change', function (e) {
|
||||
var selected_field_id = parseInt($(e.target).val(), 10);
|
||||
if (selected_field_id === field_types.EXTERNAL_ACCOUNT.id) {
|
||||
field_elem.show();
|
||||
if ($("#profile_field_external_accounts_type").val() === 'custom') {
|
||||
field_url_pattern_elem.show();
|
||||
} else {
|
||||
field_url_pattern_elem.hide();
|
||||
}
|
||||
} else {
|
||||
field_url_pattern_elem.hide();
|
||||
field_elem.hide();
|
||||
}
|
||||
});
|
||||
$("#profile_field_external_accounts_type").on("change", function () {
|
||||
if ($("#profile_field_external_accounts_type").val() === 'custom') {
|
||||
field_url_pattern_elem.show();
|
||||
} else {
|
||||
field_url_pattern_elem.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exports.get_external_account_link = function (field) {
|
||||
var field_subtype = field.field_data.subtype;
|
||||
var field_url_pattern;
|
||||
|
||||
if (field_subtype === 'custom') {
|
||||
field_url_pattern = field.field_data.url_pattern;
|
||||
} else {
|
||||
field_url_pattern =
|
||||
page_params.realm_default_external_accounts[field_subtype].url_pattern;
|
||||
}
|
||||
return field_url_pattern.replace('%(username)s', field.value);
|
||||
};
|
||||
|
||||
exports.set_up = function () {
|
||||
exports.build_page();
|
||||
exports.maybe_disable_widgets();
|
||||
|
@ -371,6 +449,7 @@ exports.build_page = function () {
|
|||
$("#profile-field-settings").on("click", "#add-custom-profile-field-btn", create_profile_field);
|
||||
$("#admin_profile_fields_table").on("click", ".open-edit-form", open_edit_form);
|
||||
set_up_choices_field();
|
||||
set_up_external_account_field();
|
||||
clear_form_data();
|
||||
};
|
||||
|
||||
|
|
|
@ -48,6 +48,21 @@
|
|||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if is_external_account_field}}
|
||||
<div class="input-group profile_field_external_accounts_edit">
|
||||
<label for="external_acc_field_type">{{t "External account type" }}</label>
|
||||
<select name="external_acc_field_type">
|
||||
{{#each ../realm_default_external_accounts}}
|
||||
<option value='{{@key}}'>{{this.text}}</option>
|
||||
{{/each}}
|
||||
<option value="custom">{{t 'Custom' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group custom_external_account_detail">
|
||||
<label for="url_pattern">{{t "URL pattern" }}</label>
|
||||
<input type="url" name="url_pattern" autocomplete="off" maxlength="80" />
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="input-group">
|
||||
<button type="button" class="button rounded sea-green submit">
|
||||
{{t 'Save changes' }}
|
||||
|
|
|
@ -41,6 +41,19 @@
|
|||
<tbody id="profile_field_choices" class="profile-field-choices"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="control-group" id="profile_field_external_accounts">
|
||||
<label for="profile_field_external_accounts_type" class="control-label">{{t "External account type" }}</label>
|
||||
<select id="profile_field_external_accounts_type" name="external_acc_field_type">
|
||||
{{#each realm_default_external_accounts}}
|
||||
<option value='{{@key}}'>{{this.text}}</option>
|
||||
{{/each}}
|
||||
<option value="custom">{{t 'Custom' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control-group" id="custom_external_account_url_pattern">
|
||||
<label for="custom_field_url_pattern" class="control-label">{{t "URL pattern" }}</label>
|
||||
<input type="url" id="custom_field_url_pattern" name="url_pattern" autocomplete="off" maxlength="80" />
|
||||
</div>
|
||||
<button type="submit" class="button rounded sea-green" id="add-custom-profile-field-btn">
|
||||
{{t 'Add profile field' }}
|
||||
</button>
|
||||
|
|
|
@ -51,6 +51,8 @@
|
|||
</div>
|
||||
{{else if this.is_link}}
|
||||
<a href={{this.value}} target="_blank" class="value">{{this.value}}</a>
|
||||
{{else if this.is_external_account}}
|
||||
<a href={{this.link}} target="_blank" class="value">{{this.value}}</a>
|
||||
{{else}}
|
||||
{{#if this.rendered_value}}
|
||||
<div class="value rendered_markdown">{{{this.rendered_value}}}</div>
|
||||
|
|
|
@ -5385,7 +5385,8 @@ def try_add_realm_custom_profile_field(realm: Realm, name: str, field_type: int,
|
|||
field_data: Optional[ProfileFieldData]=None) -> CustomProfileField:
|
||||
field = CustomProfileField(realm=realm, name=name, field_type=field_type)
|
||||
field.hint = hint
|
||||
if field.field_type == CustomProfileField.CHOICE:
|
||||
if (field.field_type == CustomProfileField.CHOICE or
|
||||
field.field_type == CustomProfileField.EXTERNAL_ACCOUNT):
|
||||
field.field_data = ujson.dumps(field_data or {})
|
||||
|
||||
field.save()
|
||||
|
@ -5410,7 +5411,8 @@ def try_update_realm_custom_profile_field(realm: Realm, field: CustomProfileFiel
|
|||
field_data: Optional[ProfileFieldData]=None) -> None:
|
||||
field.name = name
|
||||
field.hint = hint
|
||||
if field.field_type == CustomProfileField.CHOICE:
|
||||
if (field.field_type == CustomProfileField.CHOICE or
|
||||
field.field_type == CustomProfileField.EXTERNAL_ACCOUNT):
|
||||
field.field_data = ujson.dumps(field_data or {})
|
||||
field.save()
|
||||
notify_realm_custom_profile_fields(realm, 'update')
|
||||
|
|
|
@ -53,7 +53,7 @@ from zerver.models import Client, Message, Realm, UserPresence, UserProfile, Cus
|
|||
get_default_stream_groups, CustomProfileField, Stream
|
||||
from zproject.backends import email_auth_enabled, password_auth_enabled
|
||||
from version import ZULIP_VERSION
|
||||
|
||||
from zerver.lib.external_accounts import DEFAULT_EXTERNAL_ACCOUNTS
|
||||
|
||||
def get_raw_user_data(realm: Realm, client_gravatar: bool) -> Dict[int, Dict[str, str]]:
|
||||
user_dicts = get_realm_user_dicts(realm.id)
|
||||
|
@ -218,6 +218,8 @@ def fetch_initial_state_data(user_profile: UserProfile,
|
|||
state['realm_plan_type'] = realm.plan_type
|
||||
state['plan_includes_wide_organization_logo'] = realm.plan_type != Realm.LIMITED
|
||||
state['upgrade_text_for_wide_organization_logo'] = str(Realm.UPGRADE_TEXT_STANDARD)
|
||||
state['realm_default_external_accounts'] = DEFAULT_EXTERNAL_ACCOUNTS
|
||||
|
||||
if realm.notifications_stream and not realm.notifications_stream.deactivated:
|
||||
notifications_stream = realm.notifications_stream
|
||||
state['realm_notifications_stream_id'] = notifications_stream.id
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
"""
|
||||
This module stores data for "External Account" custom profile field.
|
||||
"""
|
||||
from typing import Optional
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from zerver.lib.validator import check_required_string, \
|
||||
check_url_pattern, check_dict_only
|
||||
from zerver.lib.types import ProfileFieldData
|
||||
|
||||
DEFAULT_EXTERNAL_ACCOUNTS = {
|
||||
"twitter": {
|
||||
"text": "Twitter",
|
||||
"url_pattern": "https://twitter.com/%(username)s"
|
||||
},
|
||||
"github": {
|
||||
"text": 'GitHub',
|
||||
"url_pattern": "https://github.com/%(username)s"
|
||||
},
|
||||
}
|
||||
|
||||
def validate_external_account_field_data(field_data: ProfileFieldData) -> Optional[str]:
|
||||
field_validator = check_dict_only(
|
||||
[('subtype', check_required_string)],
|
||||
[('url_pattern', check_url_pattern)],
|
||||
)
|
||||
error = field_validator('field_data', field_data)
|
||||
if error:
|
||||
return error
|
||||
|
||||
field_subtype = field_data.get('subtype')
|
||||
if field_subtype not in DEFAULT_EXTERNAL_ACCOUNTS.keys():
|
||||
if field_subtype == "custom":
|
||||
if 'url_pattern' not in field_data.keys():
|
||||
return _("Custom external account must define url pattern")
|
||||
else:
|
||||
return _("Invalid external account type")
|
||||
|
||||
return None
|
|
@ -24,4 +24,4 @@ UserFieldElement = Tuple[int, str, RealmUserValidator, Callable[[Any], Any], str
|
|||
|
||||
FieldTypeData = List[Union[FieldElement, ExtendedFieldElement, UserFieldElement]]
|
||||
|
||||
ProfileFieldData = Dict[str, Dict[str, str]]
|
||||
ProfileFieldData = Dict[str, Union[Dict[str, str], str]]
|
||||
|
|
|
@ -228,6 +228,21 @@ def check_url(var_name: str, val: object) -> Optional[str]:
|
|||
except ValidationError:
|
||||
return _('%s is not a URL') % (var_name,)
|
||||
|
||||
def check_url_pattern(var_name: str, val: object) -> Optional[str]:
|
||||
error = check_string(var_name, val)
|
||||
if error:
|
||||
return error
|
||||
val = cast(str, val)
|
||||
|
||||
if val.count('%(username)s') != 1:
|
||||
return _('username should appear exactly once in pattern.')
|
||||
url_val = val.replace('%(username)s', 'username')
|
||||
|
||||
error = check_url(var_name, url_val)
|
||||
if error:
|
||||
return error
|
||||
return None
|
||||
|
||||
def validate_choice_field_data(field_data: ProfileFieldData) -> Optional[str]:
|
||||
"""
|
||||
This function is used to validate the data sent to the server while
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.20 on 2019-05-30 08:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('zerver', '0233_userprofile_avatar_hash'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customprofilefield',
|
||||
name='field_type',
|
||||
field=models.PositiveSmallIntegerField(choices=[(1, 'Short text'), (2, 'Long text'), (4, 'Date picker'), (5, 'Link'), (7, 'External account'), (3, 'List of options'), (6, 'Person picker')], default=1),
|
||||
),
|
||||
]
|
|
@ -2613,6 +2613,7 @@ class CustomProfileField(models.Model):
|
|||
DATE = 4
|
||||
URL = 5
|
||||
USER = 6
|
||||
EXTERNAL_ACCOUNT = 7
|
||||
|
||||
# These are the fields whose validators require more than var_name
|
||||
# and value argument. i.e. CHOICE require field_data, USER require
|
||||
|
@ -2637,6 +2638,7 @@ class CustomProfileField(models.Model):
|
|||
(LONG_TEXT, str(_('Long text')), check_long_string, str, "LONG_TEXT"),
|
||||
(DATE, str(_('Date picker')), check_date, str, "DATE"),
|
||||
(URL, str(_('Link')), check_url, str, "URL"),
|
||||
(EXTERNAL_ACCOUNT, str(_('External account')), check_short_string, str, "EXTERNAL_ACCOUNT"),
|
||||
] # type: FieldTypeData
|
||||
|
||||
ALL_FIELD_TYPES = FIELD_TYPE_DATA + CHOICE_FIELD_TYPE_DATA + USER_FIELD_TYPE_DATA
|
||||
|
|
|
@ -141,6 +141,103 @@ class CustomProfileFieldTest(ZulipTestCase):
|
|||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_success(result)
|
||||
|
||||
def test_create_external_account_field(self) -> None:
|
||||
self.login(self.example_email("iago"))
|
||||
realm = get_realm('zulip')
|
||||
data = {} # type: Dict[str, Union[str, int, Dict[str, str]]]
|
||||
data["name"] = "Twitter"
|
||||
data["field_type"] = CustomProfileField.EXTERNAL_ACCOUNT
|
||||
|
||||
data['field_data'] = 'invalid'
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_error(result, "Bad value for 'field_data': invalid")
|
||||
|
||||
data['field_data'] = ujson.dumps({})
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_error(result, "subtype key is missing from field_data")
|
||||
|
||||
data["field_data"] = ujson.dumps({
|
||||
'subtype': ''
|
||||
})
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_error(result, 'field_data["subtype"] cannot be blank.')
|
||||
|
||||
data["field_data"] = ujson.dumps({
|
||||
'subtype': '123'
|
||||
})
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_error(result, 'Invalid external account type')
|
||||
|
||||
non_default_external_account = 'linkedin'
|
||||
data["field_data"] = ujson.dumps({
|
||||
'subtype': non_default_external_account
|
||||
})
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_error(result, 'Invalid external account type')
|
||||
|
||||
data["field_data"] = ujson.dumps({
|
||||
'subtype': 'twitter'
|
||||
})
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_success(result)
|
||||
|
||||
twitter_field = CustomProfileField.objects.get(name="Twitter", realm=realm)
|
||||
self.assertEqual(twitter_field.field_type, CustomProfileField.EXTERNAL_ACCOUNT)
|
||||
self.assertEqual(twitter_field.name, "Twitter")
|
||||
self.assertEqual(ujson.loads(twitter_field.field_data)['subtype'], 'twitter')
|
||||
|
||||
data['name'] = 'Reddit'
|
||||
data["field_data"] = ujson.dumps({
|
||||
'subtype': 'custom'
|
||||
})
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_error(result, 'Custom external account must define url pattern')
|
||||
|
||||
data["field_data"] = ujson.dumps({
|
||||
'subtype': 'custom',
|
||||
'url_pattern': 123,
|
||||
})
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_error(result, 'field_data["url_pattern"] is not a string')
|
||||
|
||||
data["field_data"] = ujson.dumps({
|
||||
'subtype': 'custom',
|
||||
'url_pattern': 'invalid',
|
||||
})
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_error(result, 'username should appear exactly once in pattern.')
|
||||
|
||||
data["field_data"] = ujson.dumps({
|
||||
'subtype': 'custom',
|
||||
'url_pattern': 'https://www.reddit.com/%(username)s/user/%(username)s',
|
||||
})
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_error(result, 'username should appear exactly once in pattern.')
|
||||
|
||||
data["field_data"] = ujson.dumps({
|
||||
'subtype': 'custom',
|
||||
'url_pattern': 'reddit.com/%(username)s',
|
||||
})
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_error(result, 'field_data["url_pattern"] is not a URL')
|
||||
|
||||
data["field_data"] = ujson.dumps({
|
||||
'subtype': 'custom',
|
||||
'url_pattern': 'https://www.reddit.com/user/%(username)s',
|
||||
})
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_success(result)
|
||||
|
||||
custom_field = CustomProfileField.objects.get(name="Reddit", realm=realm)
|
||||
self.assertEqual(custom_field.field_type, CustomProfileField.EXTERNAL_ACCOUNT)
|
||||
self.assertEqual(custom_field.name, "Reddit")
|
||||
field_data = ujson.loads(custom_field.field_data)
|
||||
self.assertEqual(field_data['subtype'], 'custom')
|
||||
self.assertEqual(field_data['url_pattern'], 'https://www.reddit.com/user/%(username)s')
|
||||
|
||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||
self.assert_json_error(result, "A field with that name already exists.")
|
||||
|
||||
def test_not_realm_admin(self) -> None:
|
||||
self.login(self.example_email("hamlet"))
|
||||
result = self.client_post("/json/realm/profile_fields")
|
||||
|
@ -416,6 +513,7 @@ class CustomProfileFieldTest(ZulipTestCase):
|
|||
('Birthday', '1909-3-5'),
|
||||
('Favorite website', 'https://zulipchat.com'),
|
||||
('Mentor', [self.example_user("cordelia").id]),
|
||||
('GitHub', 'zulip-mobile')
|
||||
]
|
||||
|
||||
data = []
|
||||
|
|
|
@ -129,6 +129,7 @@ class HomeTest(ZulipTestCase):
|
|||
"realm_bot_domain",
|
||||
"realm_bots",
|
||||
"realm_create_stream_policy",
|
||||
"realm_default_external_accounts",
|
||||
"realm_default_language",
|
||||
"realm_default_stream_groups",
|
||||
"realm_default_streams",
|
||||
|
|
|
@ -416,6 +416,7 @@ class PermissionTest(ZulipTestCase):
|
|||
'Birthday': '1909-3-5',
|
||||
'Favorite website': 'https://zulipchat.com',
|
||||
'Mentor': [cordelia.id],
|
||||
'GitHub': 'timabbott',
|
||||
}
|
||||
|
||||
for field_name in fields:
|
||||
|
@ -500,7 +501,8 @@ class PermissionTest(ZulipTestCase):
|
|||
'Favorite editor': None,
|
||||
'Birthday': None,
|
||||
'Favorite website': 'https://zulip.github.io',
|
||||
'Mentor': [hamlet.id]
|
||||
'Mentor': [hamlet.id],
|
||||
'GitHub': 'timabbott',
|
||||
}
|
||||
new_profile_data = []
|
||||
for field_name in fields:
|
||||
|
|
|
@ -22,6 +22,7 @@ from zerver.models import (UserProfile,
|
|||
CustomProfileField, custom_profile_fields_for_realm)
|
||||
from zerver.lib.exceptions import JsonableError
|
||||
from zerver.lib.users import validate_user_custom_profile_data
|
||||
from zerver.lib.external_accounts import validate_external_account_field_data
|
||||
|
||||
def list_realm_custom_profile_fields(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
||||
fields = custom_profile_fields_for_realm(user_profile.realm_id)
|
||||
|
@ -50,11 +51,12 @@ def validate_custom_field_data(field_type: int,
|
|||
if len(field_data) < 1:
|
||||
raise JsonableError(_("Field must have at least one choice."))
|
||||
error = validate_choice_field_data(field_data)
|
||||
elif field_type == CustomProfileField.EXTERNAL_ACCOUNT:
|
||||
error = validate_external_account_field_data(field_data)
|
||||
|
||||
if error:
|
||||
raise JsonableError(error)
|
||||
|
||||
|
||||
@require_realm_admin
|
||||
@has_request_variables
|
||||
def create_realm_custom_profile_field(request: HttpRequest,
|
||||
|
|
|
@ -29,6 +29,7 @@ from zerver.models import CustomProfileField, DefaultStream, Message, Realm, Rea
|
|||
UserMessage, UserPresence, UserProfile, clear_database, \
|
||||
email_to_username, get_client, get_huddle, get_realm, get_stream, \
|
||||
get_system_bot, get_user, get_user_profile_by_id
|
||||
from zerver.lib.types import ProfileFieldData
|
||||
|
||||
from scripts.lib.zulip_tools import get_or_create_dev_uuid_var_path
|
||||
|
||||
|
@ -348,7 +349,7 @@ class Command(BaseCommand):
|
|||
field_data = {
|
||||
'vim': {'text': 'Vim', 'order': '1'},
|
||||
'emacs': {'text': 'Emacs', 'order': '2'},
|
||||
}
|
||||
} # type: ProfileFieldData
|
||||
favorite_editor = try_add_realm_custom_profile_field(zulip_realm,
|
||||
"Favorite editor",
|
||||
CustomProfileField.CHOICE,
|
||||
|
@ -360,6 +361,12 @@ class Command(BaseCommand):
|
|||
hint="Or your personal blog's URL")
|
||||
mentor = try_add_realm_custom_profile_field(zulip_realm, "Mentor",
|
||||
CustomProfileField.USER)
|
||||
external_account_field_data = {
|
||||
'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
|
||||
hamlet = get_user("hamlet@zulip.com", zulip_realm)
|
||||
|
@ -371,6 +378,7 @@ class Command(BaseCommand):
|
|||
{"id": birthday.id, "value": "2000-1-1"},
|
||||
{"id": favorite_website.id, "value": "https://zulip.readthedocs.io/en/latest/"},
|
||||
{"id": mentor.id, "value": [hamlet.id]},
|
||||
{"id": github_profile.id, "value": 'zulip'},
|
||||
])
|
||||
do_update_user_custom_profile_data(hamlet, [
|
||||
{"id": phone_number.id, "value": "+0-11-23-456-7890"},
|
||||
|
@ -383,6 +391,7 @@ class Command(BaseCommand):
|
|||
{"id": birthday.id, "value": "1900-1-1"},
|
||||
{"id": favorite_website.id, "value": "https://blog.zulig.org"},
|
||||
{"id": mentor.id, "value": [iago.id]},
|
||||
{"id": github_profile.id, "value": 'zulipbot'},
|
||||
])
|
||||
else:
|
||||
zulip_realm = get_realm("zulip")
|
||||
|
|
Loading…
Reference in New Issue