custom_profile_fields: Add "display_in_profile_summary" field in model.

To allow `custom_profile_field` to display in user profile popover,
added new boolean field "display_in_profile_summary" in its model class.

In `custom_profile_fields.py`, functions are edited as per conditions,
like currently we can display max 2 `custom_profile_fields` except
`LONG_TEXT` and `USER` type fields.

Default external account custom profile fields made updatable for only
this new field, as previous they were not updatable.

Fixes part of: #21215
This commit is contained in:
yogesh sirsat 2022-07-13 00:34:47 +05:30 committed by Tim Abbott
parent 2e9cd20380
commit 543f36b7da
11 changed files with 234 additions and 17 deletions

View File

@ -20,6 +20,14 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 6.0 ## Changes in Zulip 6.0
**Feature level 146**
* [`POST /realm/profile_fields`](/api/create-custom-profile-field),
[`GET /realm/profile_fields`](/api/get-custom-profile-fields): Added a
new parameter `display_in_profile_summary`, which clients use to
decide whether to display the field in a small/summary section of the
user's profile.
**Feature level 145** **Feature level 145**
* [`DELETE users/me/subscriptions`](/api/unsubscribe): Normal users can * [`DELETE users/me/subscriptions`](/api/unsubscribe): Normal users can

View File

@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
# Changes should be accompanied by documentation explaining what the # Changes should be accompanied by documentation explaining what the
# new level means in templates/zerver/api/changelog.md, as well as # new level means in templates/zerver/api/changelog.md, as well as
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`. # "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 145 API_FEATURE_LEVEL = 146
# 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

View File

@ -26,7 +26,9 @@ def notify_realm_custom_profile_fields(realm: Realm) -> None:
def try_add_realm_default_custom_profile_field( def try_add_realm_default_custom_profile_field(
realm: Realm, field_subtype: str realm: Realm,
field_subtype: str,
display_in_profile_summary: 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(
@ -35,6 +37,7 @@ def try_add_realm_default_custom_profile_field(
field_type=CustomProfileField.EXTERNAL_ACCOUNT, field_type=CustomProfileField.EXTERNAL_ACCOUNT,
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,
) )
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,8 +52,14 @@ def try_add_realm_custom_profile_field(
field_type: int, field_type: int,
hint: str = "", hint: str = "",
field_data: Optional[ProfileFieldData] = None, field_data: Optional[ProfileFieldData] = None,
display_in_profile_summary: bool = False,
) -> CustomProfileField: ) -> CustomProfileField:
custom_profile_field = CustomProfileField(realm=realm, name=name, field_type=field_type) custom_profile_field = CustomProfileField(
realm=realm,
name=name,
field_type=field_type,
display_in_profile_summary=display_in_profile_summary,
)
custom_profile_field.hint = hint custom_profile_field.hint = hint
if ( if (
custom_profile_field.field_type == CustomProfileField.SELECT custom_profile_field.field_type == CustomProfileField.SELECT
@ -95,9 +104,11 @@ def try_update_realm_custom_profile_field(
name: str, name: str,
hint: str = "", hint: str = "",
field_data: Optional[ProfileFieldData] = None, field_data: Optional[ProfileFieldData] = None,
display_in_profile_summary: 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
if ( if (
field.field_type == CustomProfileField.SELECT field.field_type == CustomProfileField.SELECT
or field.field_type == CustomProfileField.EXTERNAL_ACCOUNT or field.field_type == CustomProfileField.EXTERNAL_ACCOUNT

View File

@ -171,6 +171,9 @@ custom_profile_field_type = DictType(
("field_data", str), ("field_data", str),
("order", int), ("order", int),
], ],
optional_keys=[
("display_in_profile_summary", bool),
],
) )
custom_profile_fields_event = event_dict_type( custom_profile_fields_event = event_dict_type(

View File

@ -29,11 +29,12 @@ RealmUserValidator = Callable[[int, object, bool], List[int]]
ProfileDataElementValue = Union[str, List[int]] ProfileDataElementValue = Union[str, List[int]]
class ProfileDataElementBase(TypedDict): class ProfileDataElementBase(TypedDict, total=False):
id: int id: int
name: str name: str
type: int type: int
hint: str hint: str
display_in_profile_summary: bool
field_data: str field_data: str
order: int order: int

View File

@ -0,0 +1,18 @@
# Generated by Django 4.0.7 on 2022-09-19 17:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0411_alter_muteduser_muted_user_and_more"),
]
operations = [
migrations.AddField(
model_name="customprofilefield",
name="display_in_profile_summary",
field=models.BooleanField(default=False),
),
]

View File

@ -4540,13 +4540,20 @@ class CustomProfileField(models.Model):
HINT_MAX_LENGTH = 80 HINT_MAX_LENGTH = 80
NAME_MAX_LENGTH = 40 NAME_MAX_LENGTH = 40
MAX_DISPLAY_IN_PROFILE_SUMMARY_FIELDS = 2
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name="ID") id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name="ID")
realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE) realm: Realm = models.ForeignKey(Realm, on_delete=CASCADE)
name: str = models.CharField(max_length=NAME_MAX_LENGTH) name: str = models.CharField(max_length=NAME_MAX_LENGTH)
hint: str = models.CharField(max_length=HINT_MAX_LENGTH, default="") hint: str = models.CharField(max_length=HINT_MAX_LENGTH, default="")
# Sort order for display of custom profile fields.
order: int = models.IntegerField(default=0) order: int = models.IntegerField(default=0)
# Whether the field should be displayed in smaller summary
# sections of a page displaying custom profile fields.
display_in_profile_summary: bool = models.BooleanField(default=False)
SHORT_TEXT = 1 SHORT_TEXT = 1
LONG_TEXT = 2 LONG_TEXT = 2
SELECT = 3 SELECT = 3
@ -4619,7 +4626,7 @@ class CustomProfileField(models.Model):
unique_together = ("realm", "name") unique_together = ("realm", "name")
def as_dict(self) -> ProfileDataElementBase: def as_dict(self) -> ProfileDataElementBase:
return { data_as_dict: ProfileDataElementBase = {
"id": self.id, "id": self.id,
"name": self.name, "name": self.name,
"type": self.field_type, "type": self.field_type,
@ -4627,6 +4634,10 @@ class CustomProfileField(models.Model):
"field_data": self.field_data, "field_data": self.field_data,
"order": self.order, "order": self.order,
} }
if self.display_in_profile_summary:
data_as_dict["display_in_profile_summary"] = True
return data_as_dict
def is_renderable(self) -> bool: def is_renderable(self) -> bool:
if self.field_type in [CustomProfileField.SHORT_TEXT, CustomProfileField.LONG_TEXT]: if self.field_type in [CustomProfileField.SHORT_TEXT, CustomProfileField.LONG_TEXT]:

View File

@ -1666,6 +1666,7 @@ paths:
"hint": "", "hint": "",
"field_data": '{"0":{"text":"Vim","order":"1"},"1":{"text":"Emacs","order":"2"}}', "field_data": '{"0":{"text":"Vim","order":"1"},"1":{"text":"Emacs","order":"2"}}',
"order": 4, "order": 4,
"display_in_profile_summary": true,
}, },
{ {
"id": 5, "id": 5,
@ -1682,6 +1683,7 @@ paths:
"hint": "Or your personal blog's URL", "hint": "Or your personal blog's URL",
"field_data": "", "field_data": "",
"order": 6, "order": 6,
"display_in_profile_summary": true,
}, },
{ {
"id": 7, "id": 7,
@ -8250,6 +8252,7 @@ paths:
"hint": "", "hint": "",
"field_data": '{"0":{"text":"Vim","order":"1"},"1":{"text":"Emacs","order":"2"}}', "field_data": '{"0":{"text":"Vim","order":"1"},"1":{"text":"Emacs","order":"2"}}',
"order": 4, "order": 4,
"display_in_profile_summary": true,
}, },
{ {
"id": 5, "id": 5,
@ -8266,6 +8269,7 @@ paths:
"hint": "Or your personal blog's URL", "hint": "Or your personal blog's URL",
"field_data": "", "field_data": "",
"order": 6, "order": 6,
"display_in_profile_summary": true,
}, },
{ {
"id": 7, "id": 7,
@ -8378,6 +8382,25 @@ paths:
"python": {"text": "Python", "order": "1"}, "python": {"text": "Python", "order": "1"},
"java": {"text": "Java", "order": "2"}, "java": {"text": "Java", "order": "2"},
} }
- name: display_in_profile_summary
in: query
description: |
Whether clients should display this profile field in a summary section of a
user's profile (or in a more easily accessible "small profile").
At most 2 profile fields may have this property be true in a given
organization. The "Long text" [profile field types][profile-field-types]
profile field types cannot be selected to be displayed in profile summaries.
The "Person picker" profile field is also not supported, but that is likely to
be temporary.
[profile-field-types]: /help/add-custom-profile-fields#profile-field-types
**Changes**: New in Zulip 6.0 (feature level 146).
schema:
type: boolean
example: true
responses: responses:
"200": "200":
description: Success. description: Success.
@ -15418,6 +15441,15 @@ components:
dropdown UI for individual users to select an option. dropdown UI for individual users to select an option.
The interface for field type 7 is not yet stabilized. The interface for field type 7 is not yet stabilized.
display_in_profile_summary:
type: boolean
description: |
Whether the custom profile field, display or not in the user profile summary.
Currently it's value not allowed to be `true` of `Long text` and `Person picker`
[profile field types](/help/add-custom-profile-fields#profile-field-types).
**Changes**: New in Zulip 6.0 (feature level 146).
Hotspot: Hotspot:
type: object type: object
additionalProperties: false additionalProperties: false

View File

@ -60,11 +60,19 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
data["hint"] = "*" * 81 data["hint"] = "*" * 81
data["field_type"] = CustomProfileField.SHORT_TEXT data["field_type"] = CustomProfileField.SHORT_TEXT
result = self.client_post("/json/realm/profile_fields", info=data) result = self.client_post("/json/realm/profile_fields", info=data)
msg = "hint is too long (limit: 80 characters)" self.assert_json_error(result, "hint is too long (limit: 80 characters)")
self.assert_json_error(result, msg)
data["name"] = "Phone" data["name"] = "Phone"
data["hint"] = "Contact number" data["hint"] = "Contact number"
data["field_type"] = CustomProfileField.LONG_TEXT
data["display_in_profile_summary"] = "true"
result = self.client_post("/json/realm/profile_fields", info=data)
self.assert_json_error(result, "Field type not supported for display in profile summary.")
data["field_type"] = CustomProfileField.USER
result = self.client_post("/json/realm/profile_fields", info=data)
self.assert_json_error(result, "Field type not supported for display in profile summary.")
data["field_type"] = CustomProfileField.SHORT_TEXT data["field_type"] = CustomProfileField.SHORT_TEXT
result = self.client_post("/json/realm/profile_fields", info=data) result = self.client_post("/json/realm/profile_fields", info=data)
self.assert_json_success(result) self.assert_json_success(result)
@ -75,12 +83,19 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
data["name"] = "Name " data["name"] = "Name "
data["hint"] = "Some name" data["hint"] = "Some name"
data["field_type"] = CustomProfileField.SHORT_TEXT data["field_type"] = CustomProfileField.SHORT_TEXT
data["display_in_profile_summary"] = "true"
result = self.client_post("/json/realm/profile_fields", info=data) result = self.client_post("/json/realm/profile_fields", info=data)
self.assert_json_success(result) self.assert_json_success(result)
field = CustomProfileField.objects.get(name="Name", realm=realm) field = CustomProfileField.objects.get(name="Name", realm=realm)
self.assertEqual(field.id, field.order) self.assertEqual(field.id, field.order)
result = self.client_post("/json/realm/profile_fields", info=data)
self.assert_json_error(
result, "Only 2 custom profile fields can be displayed in the profile summary."
)
data["display_in_profile_summary"] = "false"
result = self.client_post("/json/realm/profile_fields", info=data) result = self.client_post("/json/realm/profile_fields", info=data)
self.assert_json_error(result, "A field with that label already exists.") self.assert_json_error(result, "A field with that label already exists.")
@ -202,7 +217,7 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
) )
self.assert_json_success(result) self.assert_json_success(result)
# Default external account field data cannot be updated # Default external account field data cannot be updated except "display_in_profile_summary" field
field = CustomProfileField.objects.get(name="Twitter username", realm=realm) field = CustomProfileField.objects.get(name="Twitter username", realm=realm)
result = self.client_patch( result = self.client_patch(
f"/json/realm/profile_fields/{field.id}", f"/json/realm/profile_fields/{field.id}",
@ -210,6 +225,18 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
) )
self.assert_json_error(result, "Default custom field cannot be updated.") self.assert_json_error(result, "Default custom field cannot be updated.")
result = self.client_patch(
f"/json/realm/profile_fields/{field.id}",
info={
"name": field.name,
"hint": field.hint,
"field_type": field_type,
"field_data": field_data,
"display_in_profile_summary": "true",
},
)
self.assert_json_success(result)
result = self.client_delete(f"/json/realm/profile_fields/{field.id}") result = self.client_delete(f"/json/realm/profile_fields/{field.id}")
self.assert_json_success(result) self.assert_json_success(result)
@ -470,6 +497,18 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
info={ info={
"name": "New phone number", "name": "New phone number",
"hint": "New contact number", "hint": "New contact number",
"display_in_profile_summary": "invalid value",
},
)
msg = 'Argument "display_in_profile_summary" is not valid JSON.'
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",
"display_in_profile_summary": "true",
}, },
) )
self.assert_json_success(result) self.assert_json_success(result)
@ -479,14 +518,16 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
self.assertEqual(field.name, "New phone number") self.assertEqual(field.name, "New phone number")
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)
result = self.client_patch( result = self.client_patch(
f"/json/realm/profile_fields/{field.id}", f"/json/realm/profile_fields/{field.id}",
info={"name": "Name "}, info={"name": "Name ", "display_in_profile_summary": "true"},
) )
self.assert_json_success(result) self.assert_json_success(result)
field.refresh_from_db() field.refresh_from_db()
self.assertEqual(field.name, "Name") self.assertEqual(field.name, "Name")
self.assertEqual(field.display_in_profile_summary, True)
field = CustomProfileField.objects.get(name="Favorite editor", realm=realm) field = CustomProfileField.objects.get(name="Favorite editor", realm=realm)
result = self.client_patch( result = self.client_patch(
@ -516,10 +557,27 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
).decode() ).decode()
result = self.client_patch( result = self.client_patch(
f"/json/realm/profile_fields/{field.id}", f"/json/realm/profile_fields/{field.id}",
info={"name": "Favorite editor", "field_data": field_data}, info={
"name": "Favorite editor",
"field_data": field_data,
"display_in_profile_summary": "true",
},
) )
self.assert_json_success(result) self.assert_json_success(result)
field = CustomProfileField.objects.get(name="Birthday", realm=realm)
result = self.client_patch(
f"/json/realm/profile_fields/{field.id}",
info={
"name": field.name,
"hint": field.hint,
"display_in_profile_summary": "true",
},
)
self.assert_json_error(
result, "Only 2 custom profile fields can be displayed in the profile summary."
)
def test_update_is_aware_of_uniqueness(self) -> None: def test_update_is_aware_of_uniqueness(self) -> None:
self.login("iago") self.login("iago")
realm = get_realm("zulip") realm = get_realm("zulip")

View File

@ -985,9 +985,12 @@ class NormalActionsTest(BaseAction):
field = realm.customprofilefield_set.get(realm=realm, name="Biography") field = realm.customprofilefield_set.get(realm=realm, name="Biography")
name = field.name name = field.name
hint = "Biography of the user" hint = "Biography of the user"
display_in_profile_summary = False
events = self.verify_action( events = self.verify_action(
lambda: try_update_realm_custom_profile_field(realm, field, name, hint=hint) lambda: try_update_realm_custom_profile_field(
realm, field, name, hint=hint, display_in_profile_summary=display_in_profile_summary
)
) )
check_custom_profile_fields("events[0]", events[0]) check_custom_profile_fields("events[0]", events[0])

View File

@ -1,4 +1,4 @@
from typing import List, cast from typing import List, Optional, cast
import orjson import orjson
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -23,6 +23,7 @@ from zerver.lib.response import json_success
from zerver.lib.types import ProfileDataElementUpdateDict, ProfileFieldData, Validator from zerver.lib.types import ProfileDataElementUpdateDict, ProfileFieldData, Validator
from zerver.lib.users import validate_user_custom_profile_data from zerver.lib.users import validate_user_custom_profile_data
from zerver.lib.validator import ( from zerver.lib.validator import (
check_bool,
check_capped_string, check_capped_string,
check_dict, check_dict,
check_dict_only, check_dict_only,
@ -70,6 +71,19 @@ def validate_custom_field_data(field_type: int, field_data: ProfileFieldData) ->
raise JsonableError(error.message) raise JsonableError(error.message)
def validate_display_in_profile_summary_field(
field_type: int, display_in_profile_summary: bool
) -> None:
if not display_in_profile_summary:
return
# The LONG_TEXT field type doesn't make sense visually for profile
# field summaries. The USER field type will require some further
# client support.
if field_type == CustomProfileField.LONG_TEXT or field_type == CustomProfileField.USER:
raise JsonableError(_("Field type not supported for display in profile summary."))
def is_default_external_field(field_type: int, field_data: ProfileFieldData) -> bool: def is_default_external_field(field_type: int, field_data: ProfileFieldData) -> bool:
if field_type != CustomProfileField.EXTERNAL_ACCOUNT: if field_type != CustomProfileField.EXTERNAL_ACCOUNT:
return False return False
@ -79,7 +93,11 @@ def is_default_external_field(field_type: int, field_data: ProfileFieldData) ->
def validate_custom_profile_field( def validate_custom_profile_field(
name: str, hint: str, field_type: int, field_data: ProfileFieldData name: str,
hint: str,
field_type: int,
field_data: ProfileFieldData,
display_in_profile_summary: bool,
) -> None: ) -> None:
# Validate field data # Validate field data
validate_custom_field_data(field_type, field_data) validate_custom_field_data(field_type, field_data)
@ -94,12 +112,36 @@ def validate_custom_profile_field(
if field_type not in field_types: if field_type not in field_types:
raise JsonableError(_("Invalid field type.")) raise JsonableError(_("Invalid field type."))
validate_display_in_profile_summary_field(field_type, display_in_profile_summary)
check_profile_field_data: Validator[ProfileFieldData] = check_dict( check_profile_field_data: Validator[ProfileFieldData] = check_dict(
value_validator=check_union([check_dict(value_validator=check_string), check_string]) value_validator=check_union([check_dict(value_validator=check_string), check_string])
) )
def update_only_display_in_profile_summary(
requested_name: str,
requested_hint: str,
requested_field_data: ProfileFieldData,
existing_field: CustomProfileField,
) -> bool:
if (
requested_name != existing_field.name
or requested_hint != existing_field.hint
or requested_field_data != orjson.loads(existing_field.field_data)
):
return False
return True
def display_in_profile_summary_limit_reached(profile_field_id: Optional[int] = None) -> bool:
query = CustomProfileField.objects.filter(display_in_profile_summary=True)
if profile_field_id is not None:
query = query.exclude(id=profile_field_id)
return query.count() >= CustomProfileField.MAX_DISPLAY_IN_PROFILE_SUMMARY_FIELDS
@require_realm_admin @require_realm_admin
@has_request_variables @has_request_variables
def create_realm_custom_profile_field( def create_realm_custom_profile_field(
@ -109,8 +151,14 @@ def create_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),
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),
) -> HttpResponse: ) -> HttpResponse:
validate_custom_profile_field(name, hint, field_type, field_data) if display_in_profile_summary and display_in_profile_summary_limit_reached():
raise JsonableError(
_("Only 2 custom profile fields can be displayed in the profile summary.")
)
validate_custom_profile_field(name, hint, field_type, field_data, display_in_profile_summary)
try: try:
if is_default_external_field(field_type, field_data): if is_default_external_field(field_type, field_data):
field_subtype = field_data["subtype"] field_subtype = field_data["subtype"]
@ -118,6 +166,7 @@ def create_realm_custom_profile_field(
field = try_add_realm_default_custom_profile_field( field = try_add_realm_default_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,
) )
return json_success(request, data={"id": field.id}) return json_success(request, data={"id": field.id})
else: else:
@ -127,6 +176,7 @@ def create_realm_custom_profile_field(
field_data=field_data, field_data=field_data,
field_type=field_type, field_type=field_type,
hint=hint, hint=hint,
display_in_profile_summary=display_in_profile_summary,
) )
return json_success(request, data={"id": field.id}) return json_success(request, data={"id": field.id})
except IntegrityError: except IntegrityError:
@ -155,6 +205,7 @@ def update_realm_custom_profile_field(
name: str = REQ(default="", converter=lambda var_name, x: x.strip()), name: str = REQ(default="", converter=lambda var_name, x: x.strip()),
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),
) -> HttpResponse: ) -> HttpResponse:
realm = user_profile.realm realm = user_profile.realm
try: try:
@ -162,13 +213,34 @@ def update_realm_custom_profile_field(
except CustomProfileField.DoesNotExist: except CustomProfileField.DoesNotExist:
raise JsonableError(_("Field id {id} not found.").format(id=field_id)) raise JsonableError(_("Field id {id} not found.").format(id=field_id))
if display_in_profile_summary and display_in_profile_summary_limit_reached(field.id):
raise JsonableError(
_("Only 2 custom profile fields can be displayed in the profile summary.")
)
if field.field_type == CustomProfileField.EXTERNAL_ACCOUNT: if field.field_type == CustomProfileField.EXTERNAL_ACCOUNT:
if is_default_external_field(field.field_type, orjson.loads(field.field_data)): # HACK: Allow changing the display_in_profile_summary property
# of default external account types, but not any others.
#
# TODO: Make the name/hint/field_data parameters optional, and
# just require that None was passed for all of them for this case.
if is_default_external_field(
field.field_type, orjson.loads(field.field_data)
) and not update_only_display_in_profile_summary(name, hint, field_data, field):
raise JsonableError(_("Default custom field cannot be updated.")) raise JsonableError(_("Default custom field cannot be updated."))
validate_custom_profile_field(name, hint, field.field_type, field_data) validate_custom_profile_field(
name, hint, field.field_type, field_data, display_in_profile_summary
)
try: try:
try_update_realm_custom_profile_field(realm, field, name, hint=hint, field_data=field_data) try_update_realm_custom_profile_field(
realm,
field,
name,
hint=hint,
field_data=field_data,
display_in_profile_summary=display_in_profile_summary,
)
except IntegrityError: except IntegrityError:
raise JsonableError(_("A field with that label already exists.")) raise JsonableError(_("A field with that label already exists."))
return json_success(request) return json_success(request)