mirror of https://github.com/zulip/zulip.git
custom_profile_fields: Add "required" parameter to the profile fields.
Fixes #28512.
This commit is contained in:
parent
ac0673e0b5
commit
f758ca596b
|
@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 9.0
|
## Changes in Zulip 9.0
|
||||||
|
|
||||||
|
**Feature level 244**
|
||||||
|
|
||||||
|
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events),
|
||||||
|
[`POST /realm/profile_fields`](/api/create-custom-profile-field),
|
||||||
|
[`GET /realm/profile_fields`](/api/get-custom-profile-fields): Added a new
|
||||||
|
parameter `required`, on custom profile field objects, indicating whether an
|
||||||
|
organization administrator has configured the field as something users should
|
||||||
|
be required to provide.
|
||||||
|
|
||||||
**Feature level 243**
|
**Feature level 243**
|
||||||
|
|
||||||
* [`POST /register`](/api/register-queue), [`GET
|
* [`POST /register`](/api/register-queue), [`GET
|
||||||
|
|
|
@ -265,6 +265,7 @@ EXEMPT_FILES = make_set(
|
||||||
"web/src/url-template.d.ts",
|
"web/src/url-template.d.ts",
|
||||||
"web/src/user_card_popover.js",
|
"web/src/user_card_popover.js",
|
||||||
"web/src/user_deactivation_ui.ts",
|
"web/src/user_deactivation_ui.ts",
|
||||||
|
"web/src/user_events.js",
|
||||||
"web/src/user_group_components.js",
|
"web/src/user_group_components.js",
|
||||||
"web/src/user_group_create.js",
|
"web/src/user_group_create.js",
|
||||||
"web/src/user_group_create_members.js",
|
"web/src/user_group_create_members.js",
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
API_FEATURE_LEVEL = 243
|
API_FEATURE_LEVEL = 244
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -62,6 +62,7 @@ export function append_custom_profile_fields(element_id, user_id) {
|
||||||
is_select_field,
|
is_select_field,
|
||||||
field_choices,
|
field_choices,
|
||||||
for_manage_user_modal: element_id === "#edit-user-form .custom-profile-field-form",
|
for_manage_user_modal: element_id === "#edit-user-form .custom-profile-field-form",
|
||||||
|
is_empty_required_field: field.required && !field_value.value,
|
||||||
});
|
});
|
||||||
$(element_id).append(html);
|
$(element_id).append(html);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import render_bankruptcy_alert_content from "../templates/navbar_alerts/bankrupt
|
||||||
import render_configure_email_alert_content from "../templates/navbar_alerts/configure_outgoing_email.hbs";
|
import render_configure_email_alert_content from "../templates/navbar_alerts/configure_outgoing_email.hbs";
|
||||||
import render_demo_organization_deadline_content from "../templates/navbar_alerts/demo_organization_deadline.hbs";
|
import render_demo_organization_deadline_content from "../templates/navbar_alerts/demo_organization_deadline.hbs";
|
||||||
import render_desktop_notifications_alert_content from "../templates/navbar_alerts/desktop_notifications.hbs";
|
import render_desktop_notifications_alert_content from "../templates/navbar_alerts/desktop_notifications.hbs";
|
||||||
|
import render_empty_required_profile_fields from "../templates/navbar_alerts/empty_required_profile_fields.hbs";
|
||||||
import render_insecure_desktop_app_alert_content from "../templates/navbar_alerts/insecure_desktop_app.hbs";
|
import render_insecure_desktop_app_alert_content from "../templates/navbar_alerts/insecure_desktop_app.hbs";
|
||||||
import render_navbar_alert_wrapper from "../templates/navbar_alerts/navbar_alert_wrapper.hbs";
|
import render_navbar_alert_wrapper from "../templates/navbar_alerts/navbar_alert_wrapper.hbs";
|
||||||
import render_profile_incomplete_alert_content from "../templates/navbar_alerts/profile_incomplete.hbs";
|
import render_profile_incomplete_alert_content from "../templates/navbar_alerts/profile_incomplete.hbs";
|
||||||
|
@ -14,6 +15,7 @@ import * as desktop_notifications from "./desktop_notifications";
|
||||||
import * as keydown_util from "./keydown_util";
|
import * as keydown_util from "./keydown_util";
|
||||||
import {localstorage} from "./localstorage";
|
import {localstorage} from "./localstorage";
|
||||||
import {page_params} from "./page_params";
|
import {page_params} from "./page_params";
|
||||||
|
import * as people from "./people";
|
||||||
import {current_user, realm} from "./state_data";
|
import {current_user, realm} from "./state_data";
|
||||||
import {should_display_profile_incomplete_alert} from "./timerender";
|
import {should_display_profile_incomplete_alert} from "./timerender";
|
||||||
import * as unread from "./unread";
|
import * as unread from "./unread";
|
||||||
|
@ -70,6 +72,30 @@ export function should_show_server_upgrade_notification(ls) {
|
||||||
return Date.now() > upgrade_nag_dismissal_duration;
|
return Date.now() > upgrade_nag_dismissal_duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function maybe_show_empty_required_profile_fields_alert() {
|
||||||
|
const empty_required_profile_fields_exist = realm.custom_profile_fields
|
||||||
|
.map((f) => ({
|
||||||
|
...f,
|
||||||
|
value: people.my_custom_profile_data(f.id)?.value,
|
||||||
|
}))
|
||||||
|
.find((f) => f.required && !f.value);
|
||||||
|
if (!empty_required_profile_fields_exist) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $navbar_alert = $("#navbar_alerts_wrapper").children(".alert").first();
|
||||||
|
if (
|
||||||
|
!$navbar_alert?.length ||
|
||||||
|
$navbar_alert.is("#empty-required-profile-fields-warning") ||
|
||||||
|
$navbar_alert.is(":hidden")
|
||||||
|
) {
|
||||||
|
open({
|
||||||
|
data_process: "profile-missing-required",
|
||||||
|
rendered_alert_content_html: render_empty_required_profile_fields(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function dismiss_upgrade_nag(ls) {
|
export function dismiss_upgrade_nag(ls) {
|
||||||
$(".alert[data-process='server-needs-upgrade'").hide();
|
$(".alert[data-process='server-needs-upgrade'").hide();
|
||||||
if (localstorage.supported()) {
|
if (localstorage.supported()) {
|
||||||
|
@ -171,6 +197,8 @@ export function initialize() {
|
||||||
data_process: "profile-incomplete",
|
data_process: "profile-incomplete",
|
||||||
rendered_alert_content_html: render_profile_incomplete_alert_content(),
|
rendered_alert_content_html: render_profile_incomplete_alert_content(),
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
maybe_show_empty_required_profile_fields_alert();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure click handlers.
|
// Configure click handlers.
|
||||||
|
@ -208,6 +236,7 @@ export function initialize() {
|
||||||
show_step($process, 2);
|
show_step($process, 2);
|
||||||
} else {
|
} else {
|
||||||
$(this).closest(".alert").hide();
|
$(this).closest(".alert").hide();
|
||||||
|
maybe_show_empty_required_profile_fields_alert();
|
||||||
}
|
}
|
||||||
$(window).trigger("resize");
|
$(window).trigger("resize");
|
||||||
});
|
});
|
||||||
|
|
|
@ -104,6 +104,7 @@ export function dispatch_normal_event(event) {
|
||||||
realm.custom_profile_fields = event.fields;
|
realm.custom_profile_fields = event.fields;
|
||||||
settings_profile_fields.populate_profile_fields(realm.custom_profile_fields);
|
settings_profile_fields.populate_profile_fields(realm.custom_profile_fields);
|
||||||
settings_account.add_custom_profile_fields_to_settings();
|
settings_account.add_custom_profile_fields_to_settings();
|
||||||
|
navbar_alerts.maybe_show_empty_required_profile_fields_alert();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "default_streams":
|
case "default_streams":
|
||||||
|
|
|
@ -269,6 +269,7 @@ function open_custom_profile_field_form_modal() {
|
||||||
display_in_profile_summary: $("#profile_field_display_in_profile_summary").is(
|
display_in_profile_summary: $("#profile_field_display_in_profile_summary").is(
|
||||||
":checked",
|
":checked",
|
||||||
),
|
),
|
||||||
|
required: $("#profile-field-required").is(":checked"),
|
||||||
};
|
};
|
||||||
const url = "/json/realm/profile_fields";
|
const url = "/json/realm/profile_fields";
|
||||||
const opts = {
|
const opts = {
|
||||||
|
@ -454,6 +455,7 @@ function open_edit_form_modal(e) {
|
||||||
hint: field.hint,
|
hint: field.hint,
|
||||||
choices,
|
choices,
|
||||||
display_in_profile_summary: field.display_in_profile_summary === true,
|
display_in_profile_summary: field.display_in_profile_summary === true,
|
||||||
|
required: field.required === true,
|
||||||
is_select_field: field.type === field_types.SELECT.id,
|
is_select_field: field.type === field_types.SELECT.id,
|
||||||
is_external_account_field: field.type === field_types.EXTERNAL_ACCOUNT.id,
|
is_external_account_field: field.type === field_types.EXTERNAL_ACCOUNT.id,
|
||||||
valid_to_display_in_summary: is_valid_to_display_in_summary(field.type),
|
valid_to_display_in_summary: is_valid_to_display_in_summary(field.type),
|
||||||
|
@ -510,6 +512,7 @@ function open_edit_form_modal(e) {
|
||||||
data.display_in_profile_summary = $profile_field_form
|
data.display_in_profile_summary = $profile_field_form
|
||||||
.find("input[name=display_in_profile_summary]")
|
.find("input[name=display_in_profile_summary]")
|
||||||
.is(":checked");
|
.is(":checked");
|
||||||
|
data.required = $profile_field_form.find("input[name=required]").is(":checked");
|
||||||
|
|
||||||
const new_field_data = read_field_data_from_form(
|
const new_field_data = read_field_data_from_form(
|
||||||
Number.parseInt(field.type, 10),
|
Number.parseInt(field.type, 10),
|
||||||
|
@ -588,6 +591,7 @@ function toggle_display_in_profile_summary_profile_field(e) {
|
||||||
hint: field.hint,
|
hint: field.hint,
|
||||||
field_data,
|
field_data,
|
||||||
display_in_profile_summary: !field.display_in_profile_summary,
|
display_in_profile_summary: !field.display_in_profile_summary,
|
||||||
|
required: field.required,
|
||||||
};
|
};
|
||||||
const $profile_field_status = $("#admin-profile-field-status").expectOne();
|
const $profile_field_status = $("#admin-profile-field-status").expectOne();
|
||||||
|
|
||||||
|
@ -599,6 +603,31 @@ function toggle_display_in_profile_summary_profile_field(e) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggle_required(e) {
|
||||||
|
const field_id = Number.parseInt($(e.currentTarget).attr("data-profile-field-id"), 10);
|
||||||
|
const field = get_profile_field(field_id);
|
||||||
|
|
||||||
|
let field_data;
|
||||||
|
if (field.field_data) {
|
||||||
|
field_data = field.field_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: field.name,
|
||||||
|
hint: field.hint,
|
||||||
|
field_data,
|
||||||
|
display_in_profile_summary: field.display_in_profile_summary,
|
||||||
|
required: !field.required,
|
||||||
|
};
|
||||||
|
const $profile_field_status = $("#admin-profile-field-status").expectOne();
|
||||||
|
|
||||||
|
settings_ui.do_settings_change(
|
||||||
|
channel.patch,
|
||||||
|
"/json/realm/profile_fields/" + field_id,
|
||||||
|
data,
|
||||||
|
$profile_field_status,
|
||||||
|
);
|
||||||
|
}
|
||||||
export function reset() {
|
export function reset() {
|
||||||
meta.loaded = false;
|
meta.loaded = false;
|
||||||
}
|
}
|
||||||
|
@ -648,6 +677,7 @@ export function do_populate_profile_fields(profile_fields_data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const display_in_profile_summary = profile_field.display_in_profile_summary === true;
|
const display_in_profile_summary = profile_field.display_in_profile_summary === true;
|
||||||
|
const required = profile_field.required === true;
|
||||||
$profile_fields_table.append(
|
$profile_fields_table.append(
|
||||||
render_admin_profile_field_list({
|
render_admin_profile_field_list({
|
||||||
profile_field: {
|
profile_field: {
|
||||||
|
@ -661,6 +691,7 @@ export function do_populate_profile_fields(profile_fields_data) {
|
||||||
profile_field.type === field_types.EXTERNAL_ACCOUNT.id,
|
profile_field.type === field_types.EXTERNAL_ACCOUNT.id,
|
||||||
display_in_profile_summary,
|
display_in_profile_summary,
|
||||||
valid_to_display_in_summary: is_valid_to_display_in_summary(profile_field.type),
|
valid_to_display_in_summary: is_valid_to_display_in_summary(profile_field.type),
|
||||||
|
required,
|
||||||
},
|
},
|
||||||
can_modify: current_user.is_admin,
|
can_modify: current_user.is_admin,
|
||||||
realm_default_external_accounts: realm.realm_default_external_accounts,
|
realm_default_external_accounts: realm.realm_default_external_accounts,
|
||||||
|
@ -767,4 +798,5 @@ export function build_page() {
|
||||||
".display_in_profile_summary",
|
".display_in_profile_summary",
|
||||||
toggle_display_in_profile_summary_profile_field,
|
toggle_display_in_profile_summary_profile_field,
|
||||||
);
|
);
|
||||||
|
$("#admin_profile_fields_table").on("click", ".required-field-toggle", toggle_required);
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ export const realm_schema = z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
order: z.number(),
|
order: z.number(),
|
||||||
|
required: z.boolean(),
|
||||||
type: z.number(),
|
type: z.number(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
|
@ -592,4 +592,15 @@ export function initialize(): void {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
delegate("body", {
|
||||||
|
target: ".custom-user-field-label-wrapper",
|
||||||
|
content: $t({
|
||||||
|
defaultMessage: "This profile field is required.",
|
||||||
|
}),
|
||||||
|
appendTo: () => document.body,
|
||||||
|
onHidden(instance) {
|
||||||
|
instance.destroy();
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {buddy_list} from "./buddy_list";
|
||||||
import * as compose_state from "./compose_state";
|
import * as compose_state from "./compose_state";
|
||||||
import * as message_live_update from "./message_live_update";
|
import * as message_live_update from "./message_live_update";
|
||||||
import * as narrow_state from "./narrow_state";
|
import * as narrow_state from "./narrow_state";
|
||||||
|
import * as navbar_alerts from "./navbar_alerts";
|
||||||
import * as people from "./people";
|
import * as people from "./people";
|
||||||
import * as pm_list from "./pm_list";
|
import * as pm_list from "./pm_list";
|
||||||
import * as settings from "./settings";
|
import * as settings from "./settings";
|
||||||
|
@ -21,7 +22,7 @@ import * as settings_profile_fields from "./settings_profile_fields";
|
||||||
import * as settings_realm_user_settings_defaults from "./settings_realm_user_settings_defaults";
|
import * as settings_realm_user_settings_defaults from "./settings_realm_user_settings_defaults";
|
||||||
import * as settings_streams from "./settings_streams";
|
import * as settings_streams from "./settings_streams";
|
||||||
import * as settings_users from "./settings_users";
|
import * as settings_users from "./settings_users";
|
||||||
import {current_user} from "./state_data";
|
import {current_user, realm} from "./state_data";
|
||||||
import * as stream_events from "./stream_events";
|
import * as stream_events from "./stream_events";
|
||||||
|
|
||||||
export const update_person = function update(person) {
|
export const update_person = function update(person) {
|
||||||
|
@ -128,6 +129,33 @@ export const update_person = function update(person) {
|
||||||
|
|
||||||
if (Object.hasOwn(person, "custom_profile_field")) {
|
if (Object.hasOwn(person, "custom_profile_field")) {
|
||||||
people.set_custom_profile_field_data(person.user_id, person.custom_profile_field);
|
people.set_custom_profile_field_data(person.user_id, person.custom_profile_field);
|
||||||
|
if (person.user_id === people.my_current_user_id()) {
|
||||||
|
navbar_alerts.maybe_show_empty_required_profile_fields_alert();
|
||||||
|
|
||||||
|
const field_id = person.custom_profile_field.id;
|
||||||
|
const field_value = people.get_custom_profile_data(person.user_id, field_id)?.value;
|
||||||
|
const is_field_required = realm.custom_profile_fields?.find(
|
||||||
|
(f) => field_id === f.id,
|
||||||
|
)?.required;
|
||||||
|
if (is_field_required) {
|
||||||
|
const $custom_user_field = $(
|
||||||
|
`.profile-settings-form .custom_user_field[data-field-id="${CSS.escape(field_id)}"]`,
|
||||||
|
);
|
||||||
|
const $field = $custom_user_field.find(".field");
|
||||||
|
const $required_symbol = $custom_user_field.find(".required-symbol");
|
||||||
|
if (!field_value) {
|
||||||
|
if (!$field.hasClass("empty-required-field")) {
|
||||||
|
$field.addClass("empty-required-field");
|
||||||
|
$required_symbol.removeClass("hidden");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($field.hasClass("empty-required-field")) {
|
||||||
|
$field.removeClass("empty-required-field");
|
||||||
|
$required_symbol.addClass("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.hasOwn(person, "timezone")) {
|
if (Object.hasOwn(person, "timezone")) {
|
||||||
|
|
|
@ -287,6 +287,7 @@ export function get_custom_profile_field_data(user, field, field_types) {
|
||||||
profile_field.is_external_account = field_type === field_types.EXTERNAL_ACCOUNT.id;
|
profile_field.is_external_account = field_type === field_types.EXTERNAL_ACCOUNT.id;
|
||||||
profile_field.type = field_type;
|
profile_field.type = field_type;
|
||||||
profile_field.display_in_profile_summary = field.display_in_profile_summary;
|
profile_field.display_in_profile_summary = field.display_in_profile_summary;
|
||||||
|
profile_field.required = field.required;
|
||||||
|
|
||||||
switch (field_type) {
|
switch (field_type) {
|
||||||
case field_types.DATE.id:
|
case field_types.DATE.id:
|
||||||
|
|
|
@ -482,6 +482,10 @@
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
border-color: hsl(3deg 73% 74%);
|
||||||
|
}
|
||||||
|
|
||||||
.deactivated-pill {
|
.deactivated-pill {
|
||||||
background-color: hsl(0deg 86% 14%) !important;
|
background-color: hsl(0deg 86% 14%) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,7 +131,9 @@ h3,
|
||||||
|
|
||||||
.admin_profile_fields_table {
|
.admin_profile_fields_table {
|
||||||
& th.display,
|
& th.display,
|
||||||
td.display_in_profile_summary_cell {
|
& th.required,
|
||||||
|
td.display_in_profile_summary_cell,
|
||||||
|
td.required-cell {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -672,6 +674,15 @@ input[type="checkbox"] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.required-symbol {
|
||||||
|
color: hsl(0deg 66% 60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-profile-user-field {
|
||||||
|
margin-top: 5px;
|
||||||
|
max-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
.control-label-disabled {
|
.control-label-disabled {
|
||||||
color: hsl(0deg 0% 82%);
|
color: hsl(0deg 0% 82%);
|
||||||
|
|
||||||
|
@ -1513,7 +1524,6 @@ $option_title_width: 180px;
|
||||||
#edit-user-form {
|
#edit-user-form {
|
||||||
.person_picker {
|
.person_picker {
|
||||||
min-width: 206px;
|
min-width: 206px;
|
||||||
margin-top: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& textarea {
|
& textarea {
|
||||||
|
@ -1536,7 +1546,6 @@ $option_title_width: 180px;
|
||||||
& select {
|
& select {
|
||||||
/* Override undesired Bootstrap default. */
|
/* Override undesired Bootstrap default. */
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
margin-top: 5px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1906,6 +1915,22 @@ $option_title_width: 180px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-required-field {
|
||||||
|
border: 1px solid hsl(3deg 57% 33%);
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
.settings_url_input,
|
||||||
|
.settings_text_input,
|
||||||
|
.settings_textarea,
|
||||||
|
.pill-container {
|
||||||
|
border-color: hsl(0deg 0% 100%);
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: hsl(206deg 80% 62% / 80%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#generate-integration-url-modal {
|
#generate-integration-url-modal {
|
||||||
.inline {
|
.inline {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div data-step="1">
|
||||||
|
<span>
|
||||||
|
{{t 'Your profile is missing required fields.'}}
|
||||||
|
<a class="alert-link" href="#settings/profile">{{t 'Edit your profile'}}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
|
@ -43,5 +43,12 @@
|
||||||
{{t 'Display on user card' }}
|
{{t 'Display on user card' }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label class="checkbox" for="profile-field-required">
|
||||||
|
<input type="checkbox" id="profile-field-required" name="required"/>
|
||||||
|
<span></span>
|
||||||
|
{{t 'Required field' }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -23,6 +23,14 @@
|
||||||
</span>
|
</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
|
<td class="required-cell">
|
||||||
|
<span class="profile-field-required">
|
||||||
|
<label class="checkbox" for="profile-field-required-{{id}}">
|
||||||
|
<input class="required-field-toggle required-checkbox-{{required}}" type="checkbox" id="profile-field-required-{{id}}" {{#if required}} checked="checked" {{/if}} data-profile-field-id="{{id}}"/>
|
||||||
|
<span></span>
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
{{#if ../can_modify}}
|
{{#if ../can_modify}}
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<button class="button rounded small btn-warning open-edit-form-modal tippy-zulip-delayed-tooltip" data-tippy-content="{{t 'Edit custom profile field' }}" data-profile-field-id="{{id}}">
|
<button class="button rounded small btn-warning open-edit-form-modal tippy-zulip-delayed-tooltip" data-tippy-content="{{t 'Edit custom profile field' }}" data-profile-field-id="{{id}}">
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
<div class="custom_user_field" name="{{ field.name }}" data-field-id="{{ field.id }}">
|
<div class="custom_user_field" name="{{ field.name }}" data-field-id="{{ field.id }}">
|
||||||
<label class="inline-block" for="{{ field.name }}" class="title">{{ field.name }}</label>
|
<span class="custom-user-field-label-wrapper">
|
||||||
|
<label class="inline-block {{#if field.required}}required-field{{/if}}" for="{{ field.name }}" class="title">{{ field.name }}</label>
|
||||||
|
<span class="required-symbol {{#unless is_empty_required_field}}hidden{{/unless}}"> *</span>
|
||||||
|
</span>
|
||||||
<div class="alert-notification custom-field-status"></div>
|
<div class="alert-notification custom-field-status"></div>
|
||||||
<div class="settings-profile-field-user-hint">{{ field.hint }}</div>
|
<div class="settings-profile-field-user-hint">{{ field.hint }}</div>
|
||||||
<div class="settings-profile-user-field">
|
<div class="settings-profile-user-field {{#if is_empty_required_field}}empty-required-field{{/if}}">
|
||||||
{{#if is_long_text_field}}
|
{{#if is_long_text_field}}
|
||||||
<textarea maxlength="500" class="custom_user_field_value settings_textarea">{{ field_value.value }}</textarea>
|
<textarea maxlength="500" class="custom_user_field_value settings_textarea">{{ field_value.value }}</textarea>
|
||||||
{{else if is_select_field}}
|
{{else if is_select_field}}
|
||||||
|
|
|
@ -45,5 +45,12 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
<div class="input-group">
|
||||||
|
<label class="checkbox" for="edit-required-{{id}}">
|
||||||
|
<input class="edit-required" type="checkbox" id="edit-required-{{id}}" name="required" {{#if required}} checked="checked" {{/if}}/>
|
||||||
|
<span></span>
|
||||||
|
{{t 'Required field' }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{{/with}}
|
{{/with}}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
<th>{{t "Type" }}</th>
|
<th>{{t "Type" }}</th>
|
||||||
{{#if is_admin}}
|
{{#if is_admin}}
|
||||||
<th class="display">{{t "Card"}}</th>
|
<th class="display">{{t "Card"}}</th>
|
||||||
|
<th class="required">{{t "Required"}}</th>
|
||||||
<th class="actions">{{t "Actions" }}</th>
|
<th class="actions">{{t "Actions" }}</th>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -40,6 +40,7 @@ const message_lists = mock_esm("../src/message_lists");
|
||||||
const user_topics_ui = mock_esm("../src/user_topics_ui");
|
const user_topics_ui = mock_esm("../src/user_topics_ui");
|
||||||
const muted_users_ui = mock_esm("../src/muted_users_ui");
|
const muted_users_ui = mock_esm("../src/muted_users_ui");
|
||||||
const narrow_title = mock_esm("../src/narrow_title");
|
const narrow_title = mock_esm("../src/narrow_title");
|
||||||
|
const navbar_alerts = mock_esm("../src/navbar_alerts");
|
||||||
const pm_list = mock_esm("../src/pm_list");
|
const pm_list = mock_esm("../src/pm_list");
|
||||||
const reactions = mock_esm("../src/reactions");
|
const reactions = mock_esm("../src/reactions");
|
||||||
const realm_icon = mock_esm("../src/realm_icon");
|
const realm_icon = mock_esm("../src/realm_icon");
|
||||||
|
@ -115,7 +116,6 @@ message_lists.home = {
|
||||||
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
|
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
|
||||||
|
|
||||||
// page_params is highly coupled to dispatching now
|
// page_params is highly coupled to dispatching now
|
||||||
|
|
||||||
page_params.test_suite = false;
|
page_params.test_suite = false;
|
||||||
current_user.is_admin = true;
|
current_user.is_admin = true;
|
||||||
realm.realm_description = "already set description";
|
realm.realm_description = "already set description";
|
||||||
|
@ -134,8 +134,17 @@ function dispatch(ev) {
|
||||||
server_events_dispatch.dispatch_normal_event(ev);
|
server_events_dispatch.dispatch_normal_event(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const me = {
|
||||||
|
email: "me@example.com",
|
||||||
|
user_id: 20,
|
||||||
|
full_name: "Me Myself",
|
||||||
|
timezone: "America/Los_Angeles",
|
||||||
|
};
|
||||||
|
|
||||||
people.init();
|
people.init();
|
||||||
|
people.add_active_user(me);
|
||||||
people.add_active_user(test_user);
|
people.add_active_user(test_user);
|
||||||
|
people.initialize_current_user(me.user_id);
|
||||||
|
|
||||||
message_helper.process_new_message(test_message);
|
message_helper.process_new_message(test_message);
|
||||||
|
|
||||||
|
@ -295,6 +304,7 @@ run_test("custom profile fields", ({override}) => {
|
||||||
const event = event_fixtures.custom_profile_fields;
|
const event = event_fixtures.custom_profile_fields;
|
||||||
override(settings_profile_fields, "populate_profile_fields", noop);
|
override(settings_profile_fields, "populate_profile_fields", noop);
|
||||||
override(settings_account, "add_custom_profile_fields_to_settings", noop);
|
override(settings_account, "add_custom_profile_fields_to_settings", noop);
|
||||||
|
override(navbar_alerts, "maybe_show_empty_required_profile_fields_alert", noop);
|
||||||
dispatch(event);
|
dispatch(event);
|
||||||
assert_same(realm.custom_profile_fields, event.fields);
|
assert_same(realm.custom_profile_fields, event.fields);
|
||||||
});
|
});
|
||||||
|
@ -432,6 +442,8 @@ run_test("realm settings", ({override}) => {
|
||||||
override(sidebar_ui, "update_invite_user_option", noop);
|
override(sidebar_ui, "update_invite_user_option", noop);
|
||||||
override(gear_menu, "rerender", noop);
|
override(gear_menu, "rerender", noop);
|
||||||
override(narrow_title, "redraw_title", noop);
|
override(narrow_title, "redraw_title", noop);
|
||||||
|
override(navbar_alerts, "check_profile_incomplete", noop);
|
||||||
|
override(navbar_alerts, "show_profile_incomplete", noop);
|
||||||
|
|
||||||
function test_electron_dispatch(event, fake_send_event) {
|
function test_electron_dispatch(event, fake_send_event) {
|
||||||
with_overrides(({override}) => {
|
with_overrides(({override}) => {
|
||||||
|
|
|
@ -138,6 +138,7 @@ exports.fixtures = {
|
||||||
field_data: "",
|
field_data: "",
|
||||||
order: 1,
|
order: 1,
|
||||||
display_in_profile_summary: false,
|
display_in_profile_summary: false,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
|
@ -147,6 +148,7 @@ exports.fixtures = {
|
||||||
field_data: "",
|
field_data: "",
|
||||||
order: 2,
|
order: 2,
|
||||||
display_in_profile_summary: false,
|
display_in_profile_summary: false,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -99,6 +99,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
||||||
field_data: "",
|
field_data: "",
|
||||||
display_in_profile_summary: false,
|
display_in_profile_summary: false,
|
||||||
valid_to_display_in_summary: true,
|
valid_to_display_in_summary: true,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: SELECT_ID,
|
type: SELECT_ID,
|
||||||
|
@ -117,6 +118,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
||||||
]),
|
]),
|
||||||
display_in_profile_summary: false,
|
display_in_profile_summary: false,
|
||||||
valid_to_display_in_summary: true,
|
valid_to_display_in_summary: true,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: EXTERNAL_ACCOUNT_ID,
|
type: EXTERNAL_ACCOUNT_ID,
|
||||||
|
@ -128,6 +130,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
||||||
}),
|
}),
|
||||||
display_in_profile_summary: true,
|
display_in_profile_summary: true,
|
||||||
valid_to_display_in_summary: true,
|
valid_to_display_in_summary: true,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: EXTERNAL_ACCOUNT_ID,
|
type: EXTERNAL_ACCOUNT_ID,
|
||||||
|
@ -140,6 +143,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
||||||
}),
|
}),
|
||||||
display_in_profile_summary: true,
|
display_in_profile_summary: true,
|
||||||
valid_to_display_in_summary: true,
|
valid_to_display_in_summary: true,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const expected_template_data = [
|
const expected_template_data = [
|
||||||
|
@ -154,6 +158,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
||||||
is_external_account_field: false,
|
is_external_account_field: false,
|
||||||
display_in_profile_summary: false,
|
display_in_profile_summary: false,
|
||||||
valid_to_display_in_summary: true,
|
valid_to_display_in_summary: true,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
can_modify: true,
|
can_modify: true,
|
||||||
realm_default_external_accounts: realm.realm_default_external_accounts,
|
realm_default_external_accounts: realm.realm_default_external_accounts,
|
||||||
|
@ -172,6 +177,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
||||||
is_external_account_field: false,
|
is_external_account_field: false,
|
||||||
display_in_profile_summary: false,
|
display_in_profile_summary: false,
|
||||||
valid_to_display_in_summary: true,
|
valid_to_display_in_summary: true,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
can_modify: true,
|
can_modify: true,
|
||||||
realm_default_external_accounts: realm.realm_default_external_accounts,
|
realm_default_external_accounts: realm.realm_default_external_accounts,
|
||||||
|
@ -187,6 +193,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
||||||
is_external_account_field: true,
|
is_external_account_field: true,
|
||||||
display_in_profile_summary: true,
|
display_in_profile_summary: true,
|
||||||
valid_to_display_in_summary: true,
|
valid_to_display_in_summary: true,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
can_modify: true,
|
can_modify: true,
|
||||||
realm_default_external_accounts: realm.realm_default_external_accounts,
|
realm_default_external_accounts: realm.realm_default_external_accounts,
|
||||||
|
@ -202,6 +209,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
||||||
is_external_account_field: true,
|
is_external_account_field: true,
|
||||||
display_in_profile_summary: true,
|
display_in_profile_summary: true,
|
||||||
valid_to_display_in_summary: true,
|
valid_to_display_in_summary: true,
|
||||||
|
required: false,
|
||||||
},
|
},
|
||||||
can_modify: true,
|
can_modify: true,
|
||||||
realm_default_external_accounts: realm.realm_default_external_accounts,
|
realm_default_external_accounts: realm.realm_default_external_accounts,
|
||||||
|
|
|
@ -9,6 +9,7 @@ const $ = require("./lib/zjquery");
|
||||||
const {current_user} = require("./lib/zpage_params");
|
const {current_user} = require("./lib/zpage_params");
|
||||||
|
|
||||||
const message_live_update = mock_esm("../src/message_live_update");
|
const message_live_update = mock_esm("../src/message_live_update");
|
||||||
|
const navbar_alerts = mock_esm("../src/navbar_alerts");
|
||||||
const settings_account = mock_esm("../src/settings_account", {
|
const settings_account = mock_esm("../src/settings_account", {
|
||||||
maybe_update_deactivate_account_button() {},
|
maybe_update_deactivate_account_button() {},
|
||||||
update_email() {},
|
update_email() {},
|
||||||
|
@ -68,7 +69,7 @@ function initialize() {
|
||||||
|
|
||||||
initialize();
|
initialize();
|
||||||
|
|
||||||
run_test("updates", () => {
|
run_test("updates", ({override}) => {
|
||||||
let person;
|
let person;
|
||||||
|
|
||||||
const isaac = {
|
const isaac = {
|
||||||
|
@ -79,6 +80,7 @@ run_test("updates", () => {
|
||||||
};
|
};
|
||||||
people.add_active_user(isaac);
|
people.add_active_user(isaac);
|
||||||
|
|
||||||
|
override(navbar_alerts, "maybe_show_empty_required_profile_fields_alert", noop);
|
||||||
user_events.update_person({
|
user_events.update_person({
|
||||||
user_id: isaac.user_id,
|
user_id: isaac.user_id,
|
||||||
role: settings_config.user_role_values.guest.code,
|
role: settings_config.user_role_values.guest.code,
|
||||||
|
|
|
@ -25,6 +25,7 @@ def try_add_realm_default_custom_profile_field(
|
||||||
realm: Realm,
|
realm: Realm,
|
||||||
field_subtype: str,
|
field_subtype: str,
|
||||||
display_in_profile_summary: bool = False,
|
display_in_profile_summary: bool = False,
|
||||||
|
required: bool = False,
|
||||||
) -> CustomProfileField:
|
) -> CustomProfileField:
|
||||||
field_data = DEFAULT_EXTERNAL_ACCOUNTS[field_subtype]
|
field_data = DEFAULT_EXTERNAL_ACCOUNTS[field_subtype]
|
||||||
custom_profile_field = CustomProfileField(
|
custom_profile_field = CustomProfileField(
|
||||||
|
@ -34,6 +35,7 @@ def try_add_realm_default_custom_profile_field(
|
||||||
hint=field_data.hint,
|
hint=field_data.hint,
|
||||||
field_data=orjson.dumps(dict(subtype=field_subtype)).decode(),
|
field_data=orjson.dumps(dict(subtype=field_subtype)).decode(),
|
||||||
display_in_profile_summary=display_in_profile_summary,
|
display_in_profile_summary=display_in_profile_summary,
|
||||||
|
required=required,
|
||||||
)
|
)
|
||||||
custom_profile_field.save()
|
custom_profile_field.save()
|
||||||
custom_profile_field.order = custom_profile_field.id
|
custom_profile_field.order = custom_profile_field.id
|
||||||
|
@ -49,12 +51,14 @@ def try_add_realm_custom_profile_field(
|
||||||
hint: str = "",
|
hint: str = "",
|
||||||
field_data: Optional[ProfileFieldData] = None,
|
field_data: Optional[ProfileFieldData] = None,
|
||||||
display_in_profile_summary: bool = False,
|
display_in_profile_summary: bool = False,
|
||||||
|
required: bool = False,
|
||||||
) -> CustomProfileField:
|
) -> CustomProfileField:
|
||||||
custom_profile_field = CustomProfileField(
|
custom_profile_field = CustomProfileField(
|
||||||
realm=realm,
|
realm=realm,
|
||||||
name=name,
|
name=name,
|
||||||
field_type=field_type,
|
field_type=field_type,
|
||||||
display_in_profile_summary=display_in_profile_summary,
|
display_in_profile_summary=display_in_profile_summary,
|
||||||
|
required=required,
|
||||||
)
|
)
|
||||||
custom_profile_field.hint = hint
|
custom_profile_field.hint = hint
|
||||||
if custom_profile_field.field_type in (
|
if custom_profile_field.field_type in (
|
||||||
|
@ -101,10 +105,12 @@ def try_update_realm_custom_profile_field(
|
||||||
hint: str = "",
|
hint: str = "",
|
||||||
field_data: Optional[ProfileFieldData] = None,
|
field_data: Optional[ProfileFieldData] = None,
|
||||||
display_in_profile_summary: bool = False,
|
display_in_profile_summary: bool = False,
|
||||||
|
required: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
field.name = name
|
field.name = name
|
||||||
field.hint = hint
|
field.hint = hint
|
||||||
field.display_in_profile_summary = display_in_profile_summary
|
field.display_in_profile_summary = display_in_profile_summary
|
||||||
|
field.required = required
|
||||||
if field.field_type in (CustomProfileField.SELECT, CustomProfileField.EXTERNAL_ACCOUNT):
|
if field.field_type in (CustomProfileField.SELECT, CustomProfileField.EXTERNAL_ACCOUNT):
|
||||||
if field.field_type == CustomProfileField.SELECT:
|
if field.field_type == CustomProfileField.SELECT:
|
||||||
assert field_data is not None
|
assert field_data is not None
|
||||||
|
|
|
@ -169,6 +169,7 @@ custom_profile_field_type = DictType(
|
||||||
("hint", str),
|
("hint", str),
|
||||||
("field_data", str),
|
("field_data", str),
|
||||||
("order", int),
|
("order", int),
|
||||||
|
("required", bool),
|
||||||
],
|
],
|
||||||
optional_keys=[
|
optional_keys=[
|
||||||
("display_in_profile_summary", bool),
|
("display_in_profile_summary", bool),
|
||||||
|
|
|
@ -22,6 +22,7 @@ class ProfileDataElementBase(TypedDict, total=False):
|
||||||
type: int
|
type: int
|
||||||
hint: str
|
hint: str
|
||||||
display_in_profile_summary: bool
|
display_in_profile_summary: bool
|
||||||
|
required: bool
|
||||||
field_data: str
|
field_data: str
|
||||||
order: int
|
order: int
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-03-01 09:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0503_realm_zulip_update_announcements_level"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="customprofilefield",
|
||||||
|
name="required",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -77,6 +77,7 @@ class CustomProfileField(models.Model):
|
||||||
# Whether the field should be displayed in smaller summary
|
# Whether the field should be displayed in smaller summary
|
||||||
# sections of a page displaying custom profile fields.
|
# sections of a page displaying custom profile fields.
|
||||||
display_in_profile_summary = models.BooleanField(default=False)
|
display_in_profile_summary = models.BooleanField(default=False)
|
||||||
|
required = models.BooleanField(default=False)
|
||||||
|
|
||||||
SHORT_TEXT = 1
|
SHORT_TEXT = 1
|
||||||
LONG_TEXT = 2
|
LONG_TEXT = 2
|
||||||
|
@ -165,6 +166,7 @@ class CustomProfileField(models.Model):
|
||||||
"hint": self.hint,
|
"hint": self.hint,
|
||||||
"field_data": self.field_data,
|
"field_data": self.field_data,
|
||||||
"order": self.order,
|
"order": self.order,
|
||||||
|
"required": self.required,
|
||||||
}
|
}
|
||||||
if self.display_in_profile_summary:
|
if self.display_in_profile_summary:
|
||||||
data_as_dict["display_in_profile_summary"] = True
|
data_as_dict["display_in_profile_summary"] = True
|
||||||
|
|
|
@ -1913,6 +1913,7 @@ paths:
|
||||||
"hint": "Enter your GitHub username",
|
"hint": "Enter your GitHub username",
|
||||||
"field_data": '{"subtype":"github"}',
|
"field_data": '{"subtype":"github"}',
|
||||||
"order": 8,
|
"order": 8,
|
||||||
|
"required": true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"id": 0,
|
"id": 0,
|
||||||
|
@ -10298,6 +10299,7 @@ paths:
|
||||||
"hint": "Enter your GitHub username",
|
"hint": "Enter your GitHub username",
|
||||||
"field_data": '{"subtype":"github"}',
|
"field_data": '{"subtype":"github"}',
|
||||||
"order": 8,
|
"order": 8,
|
||||||
|
"required": true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 9,
|
"id": 9,
|
||||||
|
@ -10422,6 +10424,18 @@ paths:
|
||||||
**Changes**: New in Zulip 6.0 (feature level 146).
|
**Changes**: New in Zulip 6.0 (feature level 146).
|
||||||
type: boolean
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
|
required:
|
||||||
|
description: |
|
||||||
|
Whether an organization administrator has configured this profile field as
|
||||||
|
required.
|
||||||
|
|
||||||
|
Because the required property is mutable, clients cannot assume that a required
|
||||||
|
custom profile field has a value. The Zulip web application displays a prominent
|
||||||
|
banner to any user who has not set a value for a required field.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 9.0 (feature level 244).
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
required:
|
required:
|
||||||
- field_type
|
- field_type
|
||||||
encoding:
|
encoding:
|
||||||
|
@ -10431,6 +10445,8 @@ paths:
|
||||||
contentType: application/json
|
contentType: application/json
|
||||||
display_in_profile_summary:
|
display_in_profile_summary:
|
||||||
contentType: application/json
|
contentType: application/json
|
||||||
|
required:
|
||||||
|
contentType: application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Success.
|
description: Success.
|
||||||
|
@ -18946,6 +18962,17 @@ components:
|
||||||
[profile field types](/help/custom-profile-fields#profile-field-types).
|
[profile field types](/help/custom-profile-fields#profile-field-types).
|
||||||
|
|
||||||
**Changes**: New in Zulip 6.0 (feature level 146).
|
**Changes**: New in Zulip 6.0 (feature level 146).
|
||||||
|
required:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
Whether an organization administrator has configured this profile field as
|
||||||
|
required.
|
||||||
|
|
||||||
|
Because the required property is mutable, clients cannot assume that a required
|
||||||
|
custom profile field has a value. The Zulip web application displays a prominent
|
||||||
|
banner to any user who has not set a value for a required field.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 9.0 (feature level 244).
|
||||||
OnboardingStep:
|
OnboardingStep:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
|
|
@ -482,6 +482,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
|
||||||
self.assertEqual(field.name, "New phone number")
|
self.assertEqual(field.name, "New phone number")
|
||||||
self.assertIs(field.hint, "")
|
self.assertIs(field.hint, "")
|
||||||
self.assertEqual(field.field_type, CustomProfileField.SHORT_TEXT)
|
self.assertEqual(field.field_type, CustomProfileField.SHORT_TEXT)
|
||||||
|
self.assertEqual(field.required, False)
|
||||||
|
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
f"/json/realm/profile_fields/{field.id}",
|
f"/json/realm/profile_fields/{field.id}",
|
||||||
|
@ -511,12 +512,24 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
|
||||||
msg = 'Argument "display_in_profile_summary" is not valid JSON.'
|
msg = 'Argument "display_in_profile_summary" is not valid JSON.'
|
||||||
self.assert_json_error(result, msg)
|
self.assert_json_error(result, msg)
|
||||||
|
|
||||||
|
result = self.client_patch(
|
||||||
|
f"/json/realm/profile_fields/{field.id}",
|
||||||
|
info={
|
||||||
|
"name": "New phone number",
|
||||||
|
"hint": "New contact number",
|
||||||
|
"required": "invalid value",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
msg = 'Argument "required" is not valid JSON.'
|
||||||
|
self.assert_json_error(result, msg)
|
||||||
|
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
f"/json/realm/profile_fields/{field.id}",
|
f"/json/realm/profile_fields/{field.id}",
|
||||||
info={
|
info={
|
||||||
"name": "New phone number",
|
"name": "New phone number",
|
||||||
"hint": "New contact number",
|
"hint": "New contact number",
|
||||||
"display_in_profile_summary": "true",
|
"display_in_profile_summary": "true",
|
||||||
|
"required": "true",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
@ -527,6 +540,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
|
||||||
self.assertEqual(field.hint, "New contact number")
|
self.assertEqual(field.hint, "New contact number")
|
||||||
self.assertEqual(field.field_type, CustomProfileField.SHORT_TEXT)
|
self.assertEqual(field.field_type, CustomProfileField.SHORT_TEXT)
|
||||||
self.assertEqual(field.display_in_profile_summary, True)
|
self.assertEqual(field.display_in_profile_summary, True)
|
||||||
|
self.assertEqual(field.required, True)
|
||||||
|
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
f"/json/realm/profile_fields/{field.id}",
|
f"/json/realm/profile_fields/{field.id}",
|
||||||
|
|
|
@ -155,6 +155,7 @@ def create_realm_custom_profile_field(
|
||||||
field_data: ProfileFieldData = REQ(default={}, json_validator=check_profile_field_data),
|
field_data: ProfileFieldData = REQ(default={}, json_validator=check_profile_field_data),
|
||||||
field_type: int = REQ(json_validator=check_int),
|
field_type: int = REQ(json_validator=check_int),
|
||||||
display_in_profile_summary: bool = REQ(default=False, json_validator=check_bool),
|
display_in_profile_summary: bool = REQ(default=False, json_validator=check_bool),
|
||||||
|
required: bool = REQ(default=False, json_validator=check_bool),
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
if display_in_profile_summary and display_in_profile_summary_limit_reached(user_profile.realm):
|
if display_in_profile_summary and display_in_profile_summary_limit_reached(user_profile.realm):
|
||||||
raise JsonableError(
|
raise JsonableError(
|
||||||
|
@ -170,6 +171,7 @@ def create_realm_custom_profile_field(
|
||||||
realm=user_profile.realm,
|
realm=user_profile.realm,
|
||||||
field_subtype=field_subtype,
|
field_subtype=field_subtype,
|
||||||
display_in_profile_summary=display_in_profile_summary,
|
display_in_profile_summary=display_in_profile_summary,
|
||||||
|
required=required,
|
||||||
)
|
)
|
||||||
return json_success(request, data={"id": field.id})
|
return json_success(request, data={"id": field.id})
|
||||||
else:
|
else:
|
||||||
|
@ -180,6 +182,7 @@ def create_realm_custom_profile_field(
|
||||||
field_type=field_type,
|
field_type=field_type,
|
||||||
hint=hint,
|
hint=hint,
|
||||||
display_in_profile_summary=display_in_profile_summary,
|
display_in_profile_summary=display_in_profile_summary,
|
||||||
|
required=required,
|
||||||
)
|
)
|
||||||
return json_success(request, data={"id": field.id})
|
return json_success(request, data={"id": field.id})
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
|
@ -209,6 +212,7 @@ def update_realm_custom_profile_field(
|
||||||
hint: str = REQ(default=""),
|
hint: str = REQ(default=""),
|
||||||
field_data: ProfileFieldData = REQ(default={}, json_validator=check_profile_field_data),
|
field_data: ProfileFieldData = REQ(default={}, json_validator=check_profile_field_data),
|
||||||
display_in_profile_summary: bool = REQ(default=False, json_validator=check_bool),
|
display_in_profile_summary: bool = REQ(default=False, json_validator=check_bool),
|
||||||
|
required: bool = REQ(default=False, json_validator=check_bool),
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
realm = user_profile.realm
|
realm = user_profile.realm
|
||||||
try:
|
try:
|
||||||
|
@ -246,6 +250,7 @@ def update_realm_custom_profile_field(
|
||||||
hint=hint,
|
hint=hint,
|
||||||
field_data=field_data,
|
field_data=field_data,
|
||||||
display_in_profile_summary=display_in_profile_summary,
|
display_in_profile_summary=display_in_profile_summary,
|
||||||
|
required=required,
|
||||||
)
|
)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
raise JsonableError(_("A field with that label already exists."))
|
raise JsonableError(_("A field with that label already exists."))
|
||||||
|
|
Loading…
Reference in New Issue