mirror of https://github.com/zulip/zulip.git
settings: Merge settings API endpoints.
This API change removes unnecessary complexity from a client that wants to change a user's personal settings, and also saves developers from needing to make decisions about what sort of setting something is at the API level. We preserve the old settings endpoints as mapping to the same function as the new one for backwards-compatibility. We delete the documentation for the old endpoints, though the documentation for the merged /settings endpoint mentions how to use the old endpoints when needed. We migrate all backend tests to the new endpoints, except for individual tests for each legacy endpoint to verify they still work. Co-authored-by: sahil839 <sahilbatra839@gmail.com>
This commit is contained in:
parent
89724e5bb4
commit
256091dc15
|
@ -11,6 +11,14 @@ below features are supported.
|
||||||
|
|
||||||
## Changes in Zulip 5.0
|
## Changes in Zulip 5.0
|
||||||
|
|
||||||
|
**Feature level 80**
|
||||||
|
|
||||||
|
* [`PATCH /settings`](/api/update-settings): The
|
||||||
|
`/settings/notifications` and `/settings/display` endpoints were
|
||||||
|
merged into the main `/settings` endpoint; now all personal settings
|
||||||
|
should be edited using that single endpoint. The old URLs are
|
||||||
|
preserved as deprecated aliases for backwards compatibility.
|
||||||
|
|
||||||
**Feature level 79**
|
**Feature level 79**
|
||||||
|
|
||||||
* [`GET /users/me/subscriptions`](/api/get-subscriptions): The
|
* [`GET /users/me/subscriptions`](/api/get-subscriptions): The
|
||||||
|
@ -22,12 +30,13 @@ below features are supported.
|
||||||
|
|
||||||
**Feature level 78**
|
**Feature level 78**
|
||||||
|
|
||||||
* `PATCH /settings`: Added `ignored_parameters_unsupported` field,
|
* [`PATCH /settings`](/api/update-settings): Added
|
||||||
which is a list of parameters that were ignored by the endpoint,
|
`ignored_parameters_unsupported` field, which is a list of
|
||||||
to the response object.
|
parameters that were ignored by the endpoint, to the response
|
||||||
|
object.
|
||||||
|
|
||||||
* `PATCH /settings`: Removed `full_name` and `account_email` fields
|
* [`PATCH /settings`](/api/update-settings): Removed `full_name` and
|
||||||
from the response object.
|
`account_email` fields from the response object.
|
||||||
|
|
||||||
**Feature level 77**
|
**Feature level 77**
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,7 @@
|
||||||
* [Set "typing" status](/api/set-typing-status)
|
* [Set "typing" status](/api/set-typing-status)
|
||||||
* [Get user presence](/api/get-user-presence)
|
* [Get user presence](/api/get-user-presence)
|
||||||
* [Get attachments](/api/get-attachments)
|
* [Get attachments](/api/get-attachments)
|
||||||
* [Update display settings](/api/update-display-settings)
|
* [Update settings](/api/update-settings)
|
||||||
* [Update notification settings](/api/update-notification-settings)
|
|
||||||
* [Get user groups](/api/get-user-groups)
|
* [Get user groups](/api/get-user-groups)
|
||||||
* [Create a user group](/api/create-user-group)
|
* [Create a user group](/api/create-user-group)
|
||||||
* [Update a user group](/api/update-user-group)
|
* [Update a user group](/api/update-user-group)
|
||||||
|
|
|
@ -820,7 +820,6 @@ markdown_docs_length_exclude = {
|
||||||
"templates/zerver/api/get-messages.md",
|
"templates/zerver/api/get-messages.md",
|
||||||
# This macro has a long indented URL
|
# This macro has a long indented URL
|
||||||
"templates/zerver/help/include/git-webhook-url-with-branches-indented.md",
|
"templates/zerver/help/include/git-webhook-url-with-branches-indented.md",
|
||||||
"templates/zerver/api/update-notification-settings.md",
|
|
||||||
# These two are the same file and have some too-long lines for GitHub badges
|
# These two are the same file and have some too-long lines for GitHub badges
|
||||||
"README.md",
|
"README.md",
|
||||||
"docs/overview/readme.md",
|
"docs/overview/readme.md",
|
||||||
|
|
|
@ -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 = 79
|
API_FEATURE_LEVEL = 80
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -28,9 +28,7 @@ EXCLUDE_UNDOCUMENTED_ENDPOINTS = {
|
||||||
}
|
}
|
||||||
# Consists of endpoints with some documentation remaining.
|
# Consists of endpoints with some documentation remaining.
|
||||||
# These are skipped but return true as the validator cannot exclude objects
|
# These are skipped but return true as the validator cannot exclude objects
|
||||||
EXCLUDE_DOCUMENTED_ENDPOINTS = {
|
EXCLUDE_DOCUMENTED_ENDPOINTS: Set[Tuple[str, str]] = set([])
|
||||||
("/settings/notifications", "patch"),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Most of our code expects allOf to be preprocessed away because that is what
|
# Most of our code expects allOf to be preprocessed away because that is what
|
||||||
# yamole did. Its algorithm for doing so is not standards compliant, but we
|
# yamole did. Its algorithm for doing so is not standards compliant, but we
|
||||||
|
|
|
@ -1132,35 +1132,20 @@ def get_server_settings(client: Client) -> None:
|
||||||
validate_against_openapi_schema(result, "/server_settings", "get", "200")
|
validate_against_openapi_schema(result, "/server_settings", "get", "200")
|
||||||
|
|
||||||
|
|
||||||
@openapi_test_function("/settings/notifications:patch")
|
@openapi_test_function("/settings:patch")
|
||||||
def update_notification_settings(client: Client) -> None:
|
def update_settings(client: Client) -> None:
|
||||||
|
|
||||||
# {code_example|start}
|
# {code_example|start}
|
||||||
# Enable push notifications even when online
|
# Enable push notifications even when online and change emojiset
|
||||||
request = {
|
request = {
|
||||||
"enable_offline_push_notifications": True,
|
"enable_offline_push_notifications": True,
|
||||||
"enable_online_push_notifications": True,
|
"enable_online_push_notifications": True,
|
||||||
}
|
|
||||||
result = client.update_notification_settings(request)
|
|
||||||
# {code_example|end}
|
|
||||||
|
|
||||||
validate_against_openapi_schema(result, "/settings/notifications", "patch", "200")
|
|
||||||
|
|
||||||
|
|
||||||
@openapi_test_function("/settings/display:patch")
|
|
||||||
def update_display_settings(client: Client) -> None:
|
|
||||||
|
|
||||||
# {code_example|start}
|
|
||||||
# Show user list on left sidebar in narrow windows.
|
|
||||||
# Change emoji set used for display to Google modern.
|
|
||||||
request = {
|
|
||||||
"left_side_userlist": True,
|
|
||||||
"emojiset": "google",
|
"emojiset": "google",
|
||||||
}
|
}
|
||||||
result = client.call_endpoint("settings/display", method="PATCH", request=request)
|
result = client.call_endpoint("/settings", method="PATCH", request=request)
|
||||||
# {code_example|end}
|
# {code_example|end}
|
||||||
|
|
||||||
validate_against_openapi_schema(result, "/settings/display", "patch", "200")
|
validate_against_openapi_schema(result, "/settings", "patch", "200")
|
||||||
|
|
||||||
|
|
||||||
@openapi_test_function("/user_uploads:post")
|
@openapi_test_function("/user_uploads:post")
|
||||||
|
@ -1488,8 +1473,7 @@ def test_users(client: Client, owner_client: Client) -> None:
|
||||||
get_user_by_email(client)
|
get_user_by_email(client)
|
||||||
get_subscription_status(client)
|
get_subscription_status(client)
|
||||||
get_profile(client)
|
get_profile(client)
|
||||||
update_notification_settings(client)
|
update_settings(client)
|
||||||
update_display_settings(client)
|
|
||||||
upload_file(client)
|
upload_file(client)
|
||||||
get_attachments(client)
|
get_attachments(client)
|
||||||
set_typing_status(client)
|
set_typing_status(client)
|
||||||
|
@ -1520,7 +1504,6 @@ def test_streams(client: Client, nonadmin_client: Client) -> None:
|
||||||
remove_subscriptions(client)
|
remove_subscriptions(client)
|
||||||
toggle_mute_topic(client)
|
toggle_mute_topic(client)
|
||||||
update_subscription_settings(client)
|
update_subscription_settings(client)
|
||||||
update_notification_settings(client)
|
|
||||||
get_stream_topics(client, 1)
|
get_stream_topics(client, 1)
|
||||||
archive_stream(client, stream_id)
|
archive_stream(client, stream_id)
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -253,7 +253,6 @@ class OpenAPIArgumentsTest(ZulipTestCase):
|
||||||
"/users/me/android_gcm_reg_id",
|
"/users/me/android_gcm_reg_id",
|
||||||
"/users/me/apns_device_token",
|
"/users/me/apns_device_token",
|
||||||
#### These personal settings endpoints have modest value to document:
|
#### These personal settings endpoints have modest value to document:
|
||||||
"/settings",
|
|
||||||
"/users/me/avatar",
|
"/users/me/avatar",
|
||||||
"/users/me/api_key/regenerate",
|
"/users/me/api_key/regenerate",
|
||||||
# Much more valuable would be an org admin bulk-upload feature.
|
# Much more valuable would be an org admin bulk-upload feature.
|
||||||
|
|
|
@ -158,12 +158,10 @@ class ChangeSettingsTest(ZulipTestCase):
|
||||||
#
|
#
|
||||||
# TODO: Make this work more like do_test_realm_update_api
|
# TODO: Make this work more like do_test_realm_update_api
|
||||||
if notification_setting != "notification_sound":
|
if notification_setting != "notification_sound":
|
||||||
self.check_for_toggle_param_patch(
|
self.check_for_toggle_param_patch("/json/settings", notification_setting)
|
||||||
"/json/settings/notifications", notification_setting
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_change_notification_sound(self) -> None:
|
def test_change_notification_sound(self) -> None:
|
||||||
pattern = "/json/settings/notifications"
|
pattern = "/json/settings"
|
||||||
param = "notification_sound"
|
param = "notification_sound"
|
||||||
user_profile = self.example_user("hamlet")
|
user_profile = self.example_user("hamlet")
|
||||||
self.login_user(user_profile)
|
self.login_user(user_profile)
|
||||||
|
@ -191,7 +189,7 @@ class ChangeSettingsTest(ZulipTestCase):
|
||||||
s for s in UserProfile.property_types if UserProfile.property_types[s] is bool
|
s for s in UserProfile.property_types if UserProfile.property_types[s] is bool
|
||||||
)
|
)
|
||||||
for display_setting in boolean_settings:
|
for display_setting in boolean_settings:
|
||||||
self.check_for_toggle_param_patch("/json/settings/display", display_setting)
|
self.check_for_toggle_param_patch("/json/settings", display_setting)
|
||||||
|
|
||||||
def test_enter_sends_setting(self) -> None:
|
def test_enter_sends_setting(self) -> None:
|
||||||
self.check_for_toggle_param("/json/users/me/enter-sends", "enter_sends")
|
self.check_for_toggle_param("/json/users/me/enter-sends", "enter_sends")
|
||||||
|
@ -344,7 +342,7 @@ class ChangeSettingsTest(ZulipTestCase):
|
||||||
else:
|
else:
|
||||||
data = {setting_name: orjson.dumps(test_value).decode()}
|
data = {setting_name: orjson.dumps(test_value).decode()}
|
||||||
|
|
||||||
result = self.client_patch("/json/settings/display", data)
|
result = self.client_patch("/json/settings", data)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
user_profile = self.example_user("hamlet")
|
user_profile = self.example_user("hamlet")
|
||||||
self.assertEqual(getattr(user_profile, setting_name), test_value)
|
self.assertEqual(getattr(user_profile, setting_name), test_value)
|
||||||
|
@ -356,7 +354,7 @@ class ChangeSettingsTest(ZulipTestCase):
|
||||||
else:
|
else:
|
||||||
data = {setting_name: orjson.dumps(invalid_value).decode()}
|
data = {setting_name: orjson.dumps(invalid_value).decode()}
|
||||||
|
|
||||||
result = self.client_patch("/json/settings/display", data)
|
result = self.client_patch("/json/settings", data)
|
||||||
# the json error for multiple word setting names (ex: default_language)
|
# the json error for multiple word setting names (ex: default_language)
|
||||||
# displays as 'Invalid language'. Using setting_name.split('_') to format.
|
# displays as 'Invalid language'. Using setting_name.split('_') to format.
|
||||||
self.assert_json_error(result, f"Invalid {setting_name}")
|
self.assert_json_error(result, f"Invalid {setting_name}")
|
||||||
|
@ -376,7 +374,7 @@ class ChangeSettingsTest(ZulipTestCase):
|
||||||
def do_change_emojiset(self, emojiset: str) -> HttpResponse:
|
def do_change_emojiset(self, emojiset: str) -> HttpResponse:
|
||||||
self.login("hamlet")
|
self.login("hamlet")
|
||||||
data = {"emojiset": emojiset}
|
data = {"emojiset": emojiset}
|
||||||
result = self.client_patch("/json/settings/display", data)
|
result = self.client_patch("/json/settings", data)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def test_emojiset(self) -> None:
|
def test_emojiset(self) -> None:
|
||||||
|
@ -415,6 +413,35 @@ class ChangeSettingsTest(ZulipTestCase):
|
||||||
self.assertIn("ignored_parameters_unsupported", result)
|
self.assertIn("ignored_parameters_unsupported", result)
|
||||||
self.assertEqual(result["ignored_parameters_unsupported"], ["invalid_setting"])
|
self.assertEqual(result["ignored_parameters_unsupported"], ["invalid_setting"])
|
||||||
|
|
||||||
|
def test_changing_setting_using_display_setting_endpoint(self) -> None:
|
||||||
|
"""
|
||||||
|
This test is just for adding coverage for `/settings/display` endpoint which is
|
||||||
|
now depreceated.
|
||||||
|
"""
|
||||||
|
self.login("hamlet")
|
||||||
|
|
||||||
|
result = self.client_patch(
|
||||||
|
"/json/settings/display", dict(color_scheme=UserProfile.COLOR_SCHEME_NIGHT)
|
||||||
|
)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
hamlet = self.example_user("hamlet")
|
||||||
|
self.assertEqual(hamlet.color_scheme, UserProfile.COLOR_SCHEME_NIGHT)
|
||||||
|
|
||||||
|
def test_changing_setting_using_notification_setting_endpoint(self) -> None:
|
||||||
|
"""
|
||||||
|
This test is just for adding coverage for `/settings/notifications` endpoint which is
|
||||||
|
now depreceated.
|
||||||
|
"""
|
||||||
|
self.login("hamlet")
|
||||||
|
|
||||||
|
result = self.client_patch(
|
||||||
|
"/json/settings/notifications",
|
||||||
|
dict(enable_stream_desktop_notifications=orjson.dumps(True).decode()),
|
||||||
|
)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
hamlet = self.example_user("hamlet")
|
||||||
|
self.assertEqual(hamlet.enable_stream_desktop_notifications, True)
|
||||||
|
|
||||||
|
|
||||||
class UserChangesTest(ZulipTestCase):
|
class UserChangesTest(ZulipTestCase):
|
||||||
def test_update_api_key(self) -> None:
|
def test_update_api_key(self) -> None:
|
||||||
|
|
|
@ -87,6 +87,10 @@ def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpRes
|
||||||
return render(request, "confirmation/confirm_email_change.html", context=ctx)
|
return render(request, "confirmation/confirm_email_change.html", context=ctx)
|
||||||
|
|
||||||
|
|
||||||
|
emojiset_choices = {emojiset["key"] for emojiset in UserProfile.emojiset_choices()}
|
||||||
|
default_view_options = ["recent_topics", "all_messages"]
|
||||||
|
|
||||||
|
|
||||||
@human_users_only
|
@human_users_only
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
def json_change_settings(
|
def json_change_settings(
|
||||||
|
@ -96,7 +100,74 @@ def json_change_settings(
|
||||||
email: str = REQ(default=""),
|
email: str = REQ(default=""),
|
||||||
old_password: str = REQ(default=""),
|
old_password: str = REQ(default=""),
|
||||||
new_password: str = REQ(default=""),
|
new_password: str = REQ(default=""),
|
||||||
|
twenty_four_hour_time: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
dense_mode: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
starred_message_counts: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
fluid_layout_width: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
high_contrast_mode: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
color_scheme: Optional[int] = REQ(
|
||||||
|
json_validator=check_int_in(UserProfile.COLOR_SCHEME_CHOICES), default=None
|
||||||
|
),
|
||||||
|
translate_emoticons: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
default_language: Optional[str] = REQ(default=None),
|
||||||
|
default_view: Optional[str] = REQ(
|
||||||
|
str_validator=check_string_in(default_view_options), default=None
|
||||||
|
),
|
||||||
|
left_side_userlist: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
emojiset: Optional[str] = REQ(str_validator=check_string_in(emojiset_choices), default=None),
|
||||||
|
demote_inactive_streams: Optional[int] = REQ(
|
||||||
|
json_validator=check_int_in(UserProfile.DEMOTE_STREAMS_CHOICES), default=None
|
||||||
|
),
|
||||||
|
timezone: Optional[str] = REQ(
|
||||||
|
str_validator=check_string_in(pytz.all_timezones_set), default=None
|
||||||
|
),
|
||||||
|
enable_stream_desktop_notifications: Optional[bool] = REQ(
|
||||||
|
json_validator=check_bool, default=None
|
||||||
|
),
|
||||||
|
enable_stream_email_notifications: Optional[bool] = REQ(
|
||||||
|
json_validator=check_bool, default=None
|
||||||
|
),
|
||||||
|
enable_stream_push_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
enable_stream_audible_notifications: Optional[bool] = REQ(
|
||||||
|
json_validator=check_bool, default=None
|
||||||
|
),
|
||||||
|
wildcard_mentions_notify: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
notification_sound: Optional[str] = REQ(default=None),
|
||||||
|
enable_desktop_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
enable_sounds: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
enable_offline_email_notifications: Optional[bool] = REQ(
|
||||||
|
json_validator=check_bool, default=None
|
||||||
|
),
|
||||||
|
enable_offline_push_notifications: Optional[bool] = REQ(
|
||||||
|
json_validator=check_bool, default=None
|
||||||
|
),
|
||||||
|
enable_online_push_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
enable_digest_emails: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
enable_login_emails: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
enable_marketing_emails: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
message_content_in_email_notifications: Optional[bool] = REQ(
|
||||||
|
json_validator=check_bool, default=None
|
||||||
|
),
|
||||||
|
pm_content_in_desktop_notifications: Optional[bool] = REQ(
|
||||||
|
json_validator=check_bool, default=None
|
||||||
|
),
|
||||||
|
desktop_icon_count_display: Optional[int] = REQ(json_validator=check_int, default=None),
|
||||||
|
realm_name_in_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
presence_enabled: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
|
# We can't use REQ for this widget because
|
||||||
|
# get_available_language_codes requires provisioning to be
|
||||||
|
# complete.
|
||||||
|
if default_language is not None and default_language not in get_available_language_codes():
|
||||||
|
raise JsonableError(_("Invalid default_language"))
|
||||||
|
|
||||||
|
if (
|
||||||
|
notification_sound is not None
|
||||||
|
and notification_sound not in get_available_notification_sounds()
|
||||||
|
and notification_sound != "none"
|
||||||
|
):
|
||||||
|
raise JsonableError(_("Invalid notification sound '{}'").format(notification_sound))
|
||||||
|
|
||||||
if new_password != "":
|
if new_password != "":
|
||||||
return_data: Dict[str, Any] = {}
|
return_data: Dict[str, Any] = {}
|
||||||
if email_belongs_to_ldap(user_profile.realm, user_profile.delivery_email):
|
if email_belongs_to_ldap(user_profile.realm, user_profile.delivery_email):
|
||||||
|
@ -172,6 +243,23 @@ def json_change_settings(
|
||||||
# Note that check_change_full_name strips the passed name automatically
|
# Note that check_change_full_name strips the passed name automatically
|
||||||
check_change_full_name(user_profile, full_name, user_profile)
|
check_change_full_name(user_profile, full_name, user_profile)
|
||||||
|
|
||||||
|
# Loop over user_profile.property_types
|
||||||
|
request_settings = {k: v for k, v in list(locals().items()) if k in user_profile.property_types}
|
||||||
|
for k, v in list(request_settings.items()):
|
||||||
|
if v is not None and getattr(user_profile, k) != v:
|
||||||
|
do_set_user_display_setting(user_profile, k, v)
|
||||||
|
|
||||||
|
req_vars = {
|
||||||
|
k: v for k, v in list(locals().items()) if k in user_profile.notification_setting_types
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v in list(req_vars.items()):
|
||||||
|
if v is not None and getattr(user_profile, k) != v:
|
||||||
|
do_change_notification_settings(user_profile, k, v, acting_user=user_profile)
|
||||||
|
|
||||||
|
if timezone is not None and user_profile.timezone != timezone:
|
||||||
|
do_set_user_display_setting(user_profile, "timezone", timezone)
|
||||||
|
|
||||||
# TODO: Do this more generally.
|
# TODO: Do this more generally.
|
||||||
from zerver.lib.request import get_request_notes
|
from zerver.lib.request import get_request_notes
|
||||||
|
|
||||||
|
@ -186,120 +274,6 @@ def json_change_settings(
|
||||||
return json_success(result)
|
return json_success(result)
|
||||||
|
|
||||||
|
|
||||||
emojiset_choices = {emojiset["key"] for emojiset in UserProfile.emojiset_choices()}
|
|
||||||
default_view_options = ["recent_topics", "all_messages"]
|
|
||||||
|
|
||||||
|
|
||||||
@human_users_only
|
|
||||||
@has_request_variables
|
|
||||||
def update_display_settings_backend(
|
|
||||||
request: HttpRequest,
|
|
||||||
user_profile: UserProfile,
|
|
||||||
twenty_four_hour_time: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
dense_mode: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
starred_message_counts: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
fluid_layout_width: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
high_contrast_mode: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
color_scheme: Optional[int] = REQ(
|
|
||||||
json_validator=check_int_in(UserProfile.COLOR_SCHEME_CHOICES), default=None
|
|
||||||
),
|
|
||||||
translate_emoticons: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
default_language: Optional[str] = REQ(default=None),
|
|
||||||
default_view: Optional[str] = REQ(
|
|
||||||
str_validator=check_string_in(default_view_options), default=None
|
|
||||||
),
|
|
||||||
left_side_userlist: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
emojiset: Optional[str] = REQ(str_validator=check_string_in(emojiset_choices), default=None),
|
|
||||||
demote_inactive_streams: Optional[int] = REQ(
|
|
||||||
json_validator=check_int_in(UserProfile.DEMOTE_STREAMS_CHOICES), default=None
|
|
||||||
),
|
|
||||||
timezone: Optional[str] = REQ(
|
|
||||||
str_validator=check_string_in(pytz.all_timezones_set), default=None
|
|
||||||
),
|
|
||||||
) -> HttpResponse:
|
|
||||||
|
|
||||||
# We can't use REQ for this widget because
|
|
||||||
# get_available_language_codes requires provisioning to be
|
|
||||||
# complete.
|
|
||||||
if default_language is not None and default_language not in get_available_language_codes():
|
|
||||||
raise JsonableError(_("Invalid default_language"))
|
|
||||||
|
|
||||||
request_settings = {k: v for k, v in list(locals().items()) if k in user_profile.property_types}
|
|
||||||
result: Dict[str, Any] = {}
|
|
||||||
for k, v in list(request_settings.items()):
|
|
||||||
if v is not None and getattr(user_profile, k) != v:
|
|
||||||
do_set_user_display_setting(user_profile, k, v)
|
|
||||||
result[k] = v
|
|
||||||
|
|
||||||
if timezone is not None and user_profile.timezone != timezone:
|
|
||||||
do_set_user_display_setting(user_profile, "timezone", timezone)
|
|
||||||
result["timezone"] = timezone
|
|
||||||
|
|
||||||
return json_success(result)
|
|
||||||
|
|
||||||
|
|
||||||
@human_users_only
|
|
||||||
@has_request_variables
|
|
||||||
def json_change_notify_settings(
|
|
||||||
request: HttpRequest,
|
|
||||||
user_profile: UserProfile,
|
|
||||||
enable_stream_desktop_notifications: Optional[bool] = REQ(
|
|
||||||
json_validator=check_bool, default=None
|
|
||||||
),
|
|
||||||
enable_stream_email_notifications: Optional[bool] = REQ(
|
|
||||||
json_validator=check_bool, default=None
|
|
||||||
),
|
|
||||||
enable_stream_push_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
enable_stream_audible_notifications: Optional[bool] = REQ(
|
|
||||||
json_validator=check_bool, default=None
|
|
||||||
),
|
|
||||||
wildcard_mentions_notify: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
notification_sound: Optional[str] = REQ(default=None),
|
|
||||||
enable_desktop_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
enable_sounds: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
enable_offline_email_notifications: Optional[bool] = REQ(
|
|
||||||
json_validator=check_bool, default=None
|
|
||||||
),
|
|
||||||
enable_offline_push_notifications: Optional[bool] = REQ(
|
|
||||||
json_validator=check_bool, default=None
|
|
||||||
),
|
|
||||||
enable_online_push_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
enable_digest_emails: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
enable_login_emails: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
enable_marketing_emails: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
message_content_in_email_notifications: Optional[bool] = REQ(
|
|
||||||
json_validator=check_bool, default=None
|
|
||||||
),
|
|
||||||
pm_content_in_desktop_notifications: Optional[bool] = REQ(
|
|
||||||
json_validator=check_bool, default=None
|
|
||||||
),
|
|
||||||
desktop_icon_count_display: Optional[int] = REQ(json_validator=check_int, default=None),
|
|
||||||
realm_name_in_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
presence_enabled: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
|
||||||
) -> HttpResponse:
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
# Stream notification settings.
|
|
||||||
|
|
||||||
if (
|
|
||||||
notification_sound is not None
|
|
||||||
and notification_sound not in get_available_notification_sounds()
|
|
||||||
and notification_sound != "none"
|
|
||||||
):
|
|
||||||
raise JsonableError(_("Invalid notification sound '{}'").format(notification_sound))
|
|
||||||
|
|
||||||
req_vars = {
|
|
||||||
k: v for k, v in list(locals().items()) if k in user_profile.notification_setting_types
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v in list(req_vars.items()):
|
|
||||||
if v is not None and getattr(user_profile, k) != v:
|
|
||||||
do_change_notification_settings(user_profile, k, v, acting_user=user_profile)
|
|
||||||
result[k] = v
|
|
||||||
|
|
||||||
return json_success(result)
|
|
||||||
|
|
||||||
|
|
||||||
def set_avatar_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
def set_avatar_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
||||||
if len(request.FILES) != 1:
|
if len(request.FILES) != 1:
|
||||||
raise JsonableError(_("You must upload exactly one avatar."))
|
raise JsonableError(_("You must upload exactly one avatar."))
|
||||||
|
|
|
@ -182,11 +182,9 @@ from zerver.views.user_settings import (
|
||||||
change_enter_sends,
|
change_enter_sends,
|
||||||
confirm_email_change,
|
confirm_email_change,
|
||||||
delete_avatar_backend,
|
delete_avatar_backend,
|
||||||
json_change_notify_settings,
|
|
||||||
json_change_settings,
|
json_change_settings,
|
||||||
regenerate_api_key,
|
regenerate_api_key,
|
||||||
set_avatar_backend,
|
set_avatar_backend,
|
||||||
update_display_settings_backend,
|
|
||||||
)
|
)
|
||||||
from zerver.views.users import (
|
from zerver.views.users import (
|
||||||
add_bot_backend,
|
add_bot_backend,
|
||||||
|
@ -414,8 +412,13 @@ v1_api_and_json_patterns = [
|
||||||
),
|
),
|
||||||
# settings -> zerver.views.user_settings
|
# settings -> zerver.views.user_settings
|
||||||
rest_path("settings", PATCH=json_change_settings),
|
rest_path("settings", PATCH=json_change_settings),
|
||||||
rest_path("settings/display", PATCH=update_display_settings_backend),
|
# These next two are legacy aliases for /settings, from before
|
||||||
rest_path("settings/notifications", PATCH=json_change_notify_settings),
|
# we merged the endpoints. They are documented in the `/json/settings`
|
||||||
|
# documentation, rather than having dedicated pages.
|
||||||
|
rest_path("settings/display", PATCH=(json_change_settings, {"intentionally_undocumented"})),
|
||||||
|
rest_path(
|
||||||
|
"settings/notifications", PATCH=(json_change_settings, {"intentionally_undocumented"})
|
||||||
|
),
|
||||||
# users/me/alert_words -> zerver.views.alert_words
|
# users/me/alert_words -> zerver.views.alert_words
|
||||||
rest_path(
|
rest_path(
|
||||||
"users/me/alert_words",
|
"users/me/alert_words",
|
||||||
|
|
Loading…
Reference in New Issue