python: Convert translated positional {} fields to {named} fields.

Translators benefit from the extra information in the field names, and
need the reordering freedom that isn’t available with multiple
positional fields.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2023-07-17 13:40:33 -07:00 committed by Tim Abbott
parent db6323b2c4
commit 143baa4243
40 changed files with 199 additions and 98 deletions

View File

@ -315,7 +315,7 @@ def get_chart_data(
labels_sort_function = None labels_sort_function = None
include_empty_subgroups = True include_empty_subgroups = True
else: else:
raise JsonableError(_("Unknown chart name: {}").format(chart_name)) raise JsonableError(_("Unknown chart name: {chart_name}").format(chart_name=chart_name))
# Most likely someone using our API endpoint. The /stats page does not # Most likely someone using our API endpoint. The /stats page does not
# pass a start or end in its requests. # pass a start or end in its requests.

View File

@ -109,14 +109,17 @@ def validate_licenses(
if licenses is None or (not exempt_from_license_number_check and licenses < min_licenses): if licenses is None or (not exempt_from_license_number_check and licenses < min_licenses):
raise BillingError( raise BillingError(
"not enough licenses", _("You must invoice for at least {} users.").format(min_licenses) "not enough licenses",
_("You must invoice for at least {min_licenses} users.").format(
min_licenses=min_licenses
),
) )
if max_licenses is not None and licenses > max_licenses: if max_licenses is not None and licenses > max_licenses:
message = _( message = _(
"Invoices with more than {} licenses can't be processed from this page. To complete " "Invoices with more than {max_licenses} licenses can't be processed from this page. To"
"the upgrade, please contact {}." " complete the upgrade, please contact {email}."
).format(max_licenses, settings.ZULIP_ADMINISTRATOR) ).format(max_licenses=max_licenses, email=settings.ZULIP_ADMINISTRATOR)
raise BillingError("too many licenses", message) raise BillingError("too many licenses", message)

View File

@ -76,6 +76,20 @@ rules:
severity: ERROR severity: ERROR
message: "Immediately formatting a lazily translated string destroys its laziness" message: "Immediately formatting a lazily translated string destroys its laziness"
- id: translated-positional-field
languages: [python]
patterns:
- pattern-either:
- pattern: django.utils.translation.gettext("$MESSAGE")
- pattern: django.utils.translation.pgettext($CONTEXT, "$MESSAGE")
- pattern: django.utils.translation.gettext_lazy("$MESSAGE")
- pattern: django.utils.translation.pgettext_lazy($CONTEXT, "$MESSAGE")
- metavariable-regex:
metavariable: $MESSAGE
regex: (^|.*[^{])(\{\{)*\{[:!}].*
severity: ERROR
message: "Prefer {named} fields over positional {} in translated strings"
- id: mutable-default-type - id: mutable-default-type
languages: [python] languages: [python]
pattern-either: pattern-either:
@ -99,7 +113,9 @@ rules:
pattern-either: pattern-either:
- pattern: '"..." % ...' - pattern: '"..." % ...'
- pattern: django.utils.translation.gettext(...) % ... - pattern: django.utils.translation.gettext(...) % ...
- pattern: django.utils.translation.pgettext(...) % ...
- pattern: django.utils.translation.gettext_lazy(...) % ... - pattern: django.utils.translation.gettext_lazy(...) % ...
- pattern: django.utils.translation.pgettext_lazy(...) % ...
severity: ERROR severity: ERROR
message: "Prefer f-strings or .format for string formatting" message: "Prefer f-strings or .format for string formatting"

View File

@ -21,18 +21,22 @@ from zerver.tornado.django_api import send_event_on_commit
def check_default_stream_group_name(group_name: str) -> None: def check_default_stream_group_name(group_name: str) -> None:
if group_name.strip() == "": if group_name.strip() == "":
raise JsonableError(_("Invalid default stream group name '{}'").format(group_name)) raise JsonableError(
_("Invalid default stream group name '{group_name}'").format(group_name=group_name)
)
if len(group_name) > DefaultStreamGroup.MAX_NAME_LENGTH: if len(group_name) > DefaultStreamGroup.MAX_NAME_LENGTH:
raise JsonableError( raise JsonableError(
_("Default stream group name too long (limit: {} characters)").format( _("Default stream group name too long (limit: {max_length} characters)").format(
DefaultStreamGroup.MAX_NAME_LENGTH, max_length=DefaultStreamGroup.MAX_NAME_LENGTH,
) )
) )
for i in group_name: for i in group_name:
if ord(i) == 0: if ord(i) == 0:
raise JsonableError( raise JsonableError(
_("Default stream group name '{}' contains NULL (0x00) characters.").format( _(
group_name, "Default stream group name '{group_name}' contains NULL (0x00) characters."
).format(
group_name=group_name,
) )
) )
@ -45,7 +49,9 @@ def lookup_default_stream_groups(
try: try:
default_stream_group = DefaultStreamGroup.objects.get(name=group_name, realm=realm) default_stream_group = DefaultStreamGroup.objects.get(name=group_name, realm=realm)
except DefaultStreamGroup.DoesNotExist: except DefaultStreamGroup.DoesNotExist:
raise JsonableError(_("Invalid default stream group {}").format(group_name)) raise JsonableError(
_("Invalid default stream group {group_name}").format(group_name=group_name)
)
default_stream_groups.append(default_stream_group) default_stream_groups.append(default_stream_group)
return default_stream_groups return default_stream_groups
@ -157,11 +163,17 @@ def do_change_default_stream_group_name(
) -> None: ) -> None:
if group.name == new_group_name: if group.name == new_group_name:
raise JsonableError( raise JsonableError(
_("This default stream group is already named '{}'").format(new_group_name) _("This default stream group is already named '{group_name}'").format(
group_name=new_group_name
)
) )
if DefaultStreamGroup.objects.filter(name=new_group_name, realm=realm).exists(): if DefaultStreamGroup.objects.filter(name=new_group_name, realm=realm).exists():
raise JsonableError(_("Default stream group '{}' already exists").format(new_group_name)) raise JsonableError(
_("Default stream group '{group_name}' already exists").format(
group_name=new_group_name
)
)
group.name = new_group_name group.name = new_group_name
group.save() group.save()

View File

@ -254,11 +254,13 @@ def do_update_message_flags(
) -> int: ) -> int:
valid_flags = [item for item in UserMessage.flags if item not in UserMessage.NON_API_FLAGS] valid_flags = [item for item in UserMessage.flags if item not in UserMessage.NON_API_FLAGS]
if flag not in valid_flags: if flag not in valid_flags:
raise JsonableError(_("Invalid flag: '{}'").format(flag)) raise JsonableError(_("Invalid flag: '{flag}'").format(flag=flag))
if flag in UserMessage.NON_EDITABLE_FLAGS: if flag in UserMessage.NON_EDITABLE_FLAGS:
raise JsonableError(_("Flag not editable: '{}'").format(flag)) raise JsonableError(_("Flag not editable: '{flag}'").format(flag=flag))
if operation not in ("add", "remove"): if operation not in ("add", "remove"):
raise JsonableError(_("Invalid message flag operation: '{}'").format(operation)) raise JsonableError(
_("Invalid message flag operation: '{operation}'").format(operation=operation)
)
is_adding = operation == "add" is_adding = operation == "add"
flagattr = getattr(UserMessage.flags, flag) flagattr = getattr(UserMessage.flags, flag)
flag_target = flagattr if is_adding else 0 flag_target = flagattr if is_adding else 0

View File

@ -52,7 +52,7 @@ def check_send_typing_notification(sender: UserProfile, user_ids: List[int], ope
# is relevant here. # is relevant here.
user_profile = get_user_by_id_in_realm_including_cross_realm(user_id, sender.realm) user_profile = get_user_by_id_in_realm_including_cross_realm(user_id, sender.realm)
except UserProfile.DoesNotExist: except UserProfile.DoesNotExist:
raise JsonableError(_("Invalid user ID {}").format(user_id)) raise JsonableError(_("Invalid user ID {user_id}").format(user_id=user_id))
user_profiles.append(user_profile) user_profiles.append(user_profile)
do_send_typing_notification( do_send_typing_notification(

View File

@ -210,7 +210,7 @@ def check_add_user_group(
do_send_create_user_group_event(user_group, initial_members) do_send_create_user_group_event(user_group, initial_members)
return user_group return user_group
except django.db.utils.IntegrityError: except django.db.utils.IntegrityError:
raise JsonableError(_("User group '{}' already exists.").format(name)) raise JsonableError(_("User group '{group_name}' already exists.").format(group_name=name))
def do_send_user_group_update_event( def do_send_user_group_update_event(
@ -242,7 +242,7 @@ def do_update_user_group_name(
).decode(), ).decode(),
) )
except django.db.utils.IntegrityError: except django.db.utils.IntegrityError:
raise JsonableError(_("User group '{}' already exists.").format(name)) raise JsonableError(_("User group '{group_name}' already exists.").format(group_name=name))
do_send_user_group_update_event(user_group, dict(name=name)) do_send_user_group_update_event(user_group, dict(name=name))

View File

@ -494,10 +494,10 @@ class OurAuthenticationForm(AuthenticationForm):
secs_to_freedom = int(e.secs_to_freedom) secs_to_freedom = int(e.secs_to_freedom)
error_message = _( error_message = _(
"You're making too many attempts to sign in." "You're making too many attempts to sign in."
" Try again in {} seconds or contact your organization administrator" " Try again in {seconds} seconds or contact your organization administrator"
" for help." " for help."
) )
raise ValidationError(error_message.format(secs_to_freedom)) raise ValidationError(error_message.format(seconds=secs_to_freedom))
if return_data.get("inactive_realm"): if return_data.get("inactive_realm"):
raise AssertionError("Programming error: inactive realm in authentication form") raise AssertionError("Programming error: inactive realm in authentication form")

View File

@ -19,7 +19,7 @@ def get_user_profiles(emails: Iterable[str], realm: Realm) -> List[UserProfile]:
try: try:
user_profile = get_user_including_cross_realm(email, realm) user_profile = get_user_including_cross_realm(email, realm)
except UserProfile.DoesNotExist: except UserProfile.DoesNotExist:
raise JsonableError(_("Invalid email '{}'").format(email)) raise JsonableError(_("Invalid email '{email}'").format(email=email))
user_profiles.append(user_profile) user_profiles.append(user_profile)
return user_profiles return user_profiles
@ -30,7 +30,7 @@ def get_user_profiles_by_ids(user_ids: Iterable[int], realm: Realm) -> List[User
try: try:
user_profile = get_user_by_id_in_realm_including_cross_realm(user_id, realm) user_profile = get_user_by_id_in_realm_including_cross_realm(user_id, realm)
except UserProfile.DoesNotExist: except UserProfile.DoesNotExist:
raise JsonableError(_("Invalid user ID {}").format(user_id)) raise JsonableError(_("Invalid user ID {user_id}").format(user_id=user_id))
user_profiles.append(user_profile) user_profiles.append(user_profile)
return user_profiles return user_profiles

View File

@ -85,7 +85,7 @@ def get_emoji_data(realm_id: int, emoji_name: str) -> EmojiData:
emoji_code = name_to_codepoint[emoji_name] emoji_code = name_to_codepoint[emoji_name]
return EmojiData(emoji_code=emoji_code, reaction_type=Reaction.UNICODE_EMOJI) return EmojiData(emoji_code=emoji_code, reaction_type=Reaction.UNICODE_EMOJI)
raise JsonableError(_("Emoji '{}' does not exist").format(emoji_name)) raise JsonableError(_("Emoji '{emoji_name}' does not exist").format(emoji_name=emoji_name))
def check_emoji_request(realm: Realm, emoji_name: str, emoji_code: str, emoji_type: str) -> None: def check_emoji_request(realm: Realm, emoji_name: str, emoji_code: str, emoji_type: str) -> None:

View File

@ -105,7 +105,7 @@ def check_narrow_for_events(narrow: Collection[NarrowTerm]) -> None:
for narrow_term in narrow: for narrow_term in narrow:
operator = narrow_term.operator operator = narrow_term.operator
if operator not in ["stream", "topic", "sender", "is"]: if operator not in ["stream", "topic", "sender", "is"]:
raise JsonableError(_("Operator {} not supported.").format(operator)) raise JsonableError(_("Operator {operator} not supported.").format(operator=operator))
def is_spectator_compatible(narrow: Iterable[Dict[str, Any]]) -> bool: def is_spectator_compatible(narrow: Iterable[Dict[str, Any]]) -> bool:

View File

@ -338,8 +338,8 @@ def parse_gcm_options(options: Dict[str, Any], data: Dict[str, Any]) -> str:
if priority not in ("normal", "high"): if priority not in ("normal", "high"):
raise JsonableError( raise JsonableError(
_( _(
"Invalid GCM option to bouncer: priority {!r}", "Invalid GCM option to bouncer: priority {priority!r}",
).format(priority) ).format(priority=priority)
) )
if options: if options:
@ -348,8 +348,8 @@ def parse_gcm_options(options: Dict[str, Any], data: Dict[str, Any]) -> str:
# one-way compatibility. # one-way compatibility.
raise JsonableError( raise JsonableError(
_( _(
"Invalid GCM options to bouncer: {}", "Invalid GCM options to bouncer: {options}",
).format(orjson.dumps(options).decode()) ).format(options=orjson.dumps(options).decode())
) )
return priority # when this grows a second option, can make it a tuple return priority # when this grows a second option, can make it a tuple

View File

@ -90,7 +90,7 @@ def send_to_push_bouncer(
if "code" in result_dict and result_dict["code"] == "INVALID_ZULIP_SERVER": if "code" in result_dict and result_dict["code"] == "INVALID_ZULIP_SERVER":
# Invalid Zulip server credentials should email this server's admins # Invalid Zulip server credentials should email this server's admins
raise PushNotificationBouncerError( raise PushNotificationBouncerError(
_("Push notifications bouncer error: {}").format(msg) _("Push notifications bouncer error: {error}").format(error=msg)
) )
else: else:
# But most other errors coming from the push bouncer # But most other errors coming from the push bouncer

View File

@ -439,7 +439,9 @@ def has_request_variables(
except orjson.JSONDecodeError: except orjson.JSONDecodeError:
if param.argument_type == "body": if param.argument_type == "body":
raise InvalidJSONError(_("Malformed JSON")) raise InvalidJSONError(_("Malformed JSON"))
raise JsonableError(_('Argument "{}" is not valid JSON.').format(post_var_name)) raise JsonableError(
_('Argument "{name}" is not valid JSON.').format(name=post_var_name)
)
try: try:
val = param.json_validator(post_var_name, val) val = param.json_validator(post_var_name, val)

View File

@ -298,7 +298,9 @@ def access_stream_for_send_message(
return return
# All other cases are an error. # All other cases are an error.
raise JsonableError(_("Not authorized to send to stream '{}'").format(stream.name)) raise JsonableError(
_("Not authorized to send to stream '{stream_name}'").format(stream_name=stream.name)
)
def check_for_exactly_one_stream_arg(stream_id: Optional[int], stream: Optional[str]) -> None: def check_for_exactly_one_stream_arg(stream_id: Optional[int], stream: Optional[str]) -> None:
@ -456,7 +458,9 @@ def check_stream_name_available(realm: Realm, name: str) -> None:
check_stream_name(name) check_stream_name(name)
try: try:
get_stream(name, realm) get_stream(name, realm)
raise JsonableError(_("Stream name '{}' is already taken.").format(name)) raise JsonableError(
_("Stream name '{stream_name}' is already taken.").format(stream_name=name)
)
except Stream.DoesNotExist: except Stream.DoesNotExist:
pass pass
@ -464,7 +468,7 @@ def check_stream_name_available(realm: Realm, name: str) -> None:
def access_stream_by_name( def access_stream_by_name(
user_profile: UserProfile, stream_name: str, allow_realm_admin: bool = False user_profile: UserProfile, stream_name: str, allow_realm_admin: bool = False
) -> Tuple[Stream, Optional[Subscription]]: ) -> Tuple[Stream, Optional[Subscription]]:
error = _("Invalid stream name '{}'").format(stream_name) error = _("Invalid stream name '{stream_name}'").format(stream_name=stream_name)
try: try:
stream = get_realm_stream(stream_name, user_profile.realm_id) stream = get_realm_stream(stream_name, user_profile.realm_id)
except Stream.DoesNotExist: except Stream.DoesNotExist:
@ -584,7 +588,7 @@ def can_access_stream_history(user_profile: UserProfile, stream: Stream) -> bool
if stream.is_history_public_to_subscribers(): if stream.is_history_public_to_subscribers():
# In this case, we check if the user is subscribed. # In this case, we check if the user is subscribed.
error = _("Invalid stream name '{}'").format(stream.name) error = _("Invalid stream name '{stream_name}'").format(stream_name=stream.name)
try: try:
access_stream_common(user_profile, stream, error) access_stream_common(user_profile, stream, error)
except JsonableError: except JsonableError:
@ -728,8 +732,10 @@ def list_to_streams(
if not autocreate: if not autocreate:
raise JsonableError( raise JsonableError(
_("Stream(s) ({}) do not exist").format( _("Stream(s) ({stream_names}) do not exist").format(
", ".join(stream_dict["name"] for stream_dict in missing_stream_dicts), stream_names=", ".join(
stream_dict["name"] for stream_dict in missing_stream_dicts
),
) )
) )
@ -764,7 +770,9 @@ def access_default_stream_group_by_id(realm: Realm, group_id: int) -> DefaultStr
try: try:
return DefaultStreamGroup.objects.get(realm=realm, id=group_id) return DefaultStreamGroup.objects.get(realm=realm, id=group_id)
except DefaultStreamGroup.DoesNotExist: except DefaultStreamGroup.DoesNotExist:
raise JsonableError(_("Default stream group with id '{}' does not exist.").format(group_id)) raise JsonableError(
_("Default stream group with id '{group_id}' does not exist.").format(group_id=group_id)
)
def get_stream_by_narrow_operand_access_unchecked(operand: Union[str, int], realm: Realm) -> Stream: def get_stream_by_narrow_operand_access_unchecked(operand: Union[str, int], realm: Realm) -> Stream:

View File

@ -39,13 +39,17 @@ def check_stream_name(stream_name: str) -> None:
if len(stream_name) > Stream.MAX_NAME_LENGTH: if len(stream_name) > Stream.MAX_NAME_LENGTH:
raise JsonableError( raise JsonableError(
_("Stream name too long (limit: {} characters).").format(Stream.MAX_NAME_LENGTH) _("Stream name too long (limit: {max_length} characters).").format(
max_length=Stream.MAX_NAME_LENGTH
)
) )
invalid_character_pos = check_string_is_printable(stream_name) invalid_character_pos = check_string_is_printable(stream_name)
if invalid_character_pos is not None: if invalid_character_pos is not None:
raise JsonableError( raise JsonableError(
_("Invalid character in stream name, at position {}!").format(invalid_character_pos) _("Invalid character in stream name, at position {position}!").format(
position=invalid_character_pos
)
) )
@ -56,5 +60,7 @@ def check_stream_topic(topic: str) -> None:
invalid_character_pos = check_string_is_printable(topic) invalid_character_pos = check_string_is_printable(topic)
if invalid_character_pos is not None: if invalid_character_pos is not None:
raise JsonableError( raise JsonableError(
_("Invalid character in topic, at position {}!").format(invalid_character_pos) _("Invalid character in topic, at position {position}!").format(
position=invalid_character_pos
)
) )

View File

@ -63,7 +63,9 @@ def access_user_groups_as_potential_subgroups(
valid_group_ids = [group.id for group in user_groups] valid_group_ids = [group.id for group in user_groups]
invalid_group_ids = [group_id for group_id in user_group_ids if group_id not in valid_group_ids] invalid_group_ids = [group_id for group_id in user_group_ids if group_id not in valid_group_ids]
if invalid_group_ids: if invalid_group_ids:
raise JsonableError(_("Invalid user group ID: {}").format(invalid_group_ids[0])) raise JsonableError(
_("Invalid user group ID: {group_id}").format(group_id=invalid_group_ids[0])
)
return list(user_groups) return list(user_groups)
@ -81,21 +83,29 @@ def access_user_group_for_setting(
user_group = access_user_group_by_id(user_group_id, user_profile, for_read=True) user_group = access_user_group_by_id(user_group_id, user_profile, for_read=True)
if require_system_group and not user_group.is_system_group: if require_system_group and not user_group.is_system_group:
raise JsonableError(_("'{}' must be a system user group.").format(setting_name)) raise JsonableError(
_("'{setting_name}' must be a system user group.").format(setting_name=setting_name)
)
if not allow_internet_group and user_group.name == UserGroup.EVERYONE_ON_INTERNET_GROUP_NAME: if not allow_internet_group and user_group.name == UserGroup.EVERYONE_ON_INTERNET_GROUP_NAME:
raise JsonableError( raise JsonableError(
_("'{}' setting cannot be set to 'role:internet' group.").format(setting_name) _("'{setting_name}' setting cannot be set to 'role:internet' group.").format(
setting_name=setting_name
)
) )
if not allow_owners_group and user_group.name == UserGroup.OWNERS_GROUP_NAME: if not allow_owners_group and user_group.name == UserGroup.OWNERS_GROUP_NAME:
raise JsonableError( raise JsonableError(
_("'{}' setting cannot be set to 'role:owners' group.").format(setting_name) _("'{setting_name}' setting cannot be set to 'role:owners' group.").format(
setting_name=setting_name
)
) )
if not allow_nobody_group and user_group.name == UserGroup.NOBODY_GROUP_NAME: if not allow_nobody_group and user_group.name == UserGroup.NOBODY_GROUP_NAME:
raise JsonableError( raise JsonableError(
_("'{}' setting cannot be set to 'role:nobody' group.").format(setting_name) _("'{setting_name}' setting cannot be set to 'role:nobody' group.").format(
setting_name=setting_name
)
) )
return user_group return user_group
@ -104,12 +114,16 @@ def access_user_group_for_setting(
def check_user_group_name(group_name: str) -> str: def check_user_group_name(group_name: str) -> str:
if len(group_name) > UserGroup.MAX_NAME_LENGTH: if len(group_name) > UserGroup.MAX_NAME_LENGTH:
raise JsonableError( raise JsonableError(
_("User group name cannot exceed {} characters.").format(UserGroup.MAX_NAME_LENGTH) _("User group name cannot exceed {max_length} characters.").format(
max_length=UserGroup.MAX_NAME_LENGTH
)
) )
for invalid_prefix in UserGroup.INVALID_NAME_PREFIXES: for invalid_prefix in UserGroup.INVALID_NAME_PREFIXES:
if group_name.startswith(invalid_prefix): if group_name.startswith(invalid_prefix):
raise JsonableError(_("User group name cannot start with '{}'.").format(invalid_prefix)) raise JsonableError(
_("User group name cannot start with '{prefix}'.").format(prefix=invalid_prefix)
)
return group_name return group_name

View File

@ -92,13 +92,15 @@ def check_valid_bot_config(
config_options = {c[1]: c[2] for c in integration.config_options} config_options = {c[1]: c[2] for c in integration.config_options}
break break
if not config_options: if not config_options:
raise JsonableError(_("Invalid integration '{}'.").format(service_name)) raise JsonableError(
_("Invalid integration '{integration_name}'.").format(integration_name=service_name)
)
missing_keys = set(config_options.keys()) - set(config_data.keys()) missing_keys = set(config_options.keys()) - set(config_data.keys())
if missing_keys: if missing_keys:
raise JsonableError( raise JsonableError(
_("Missing configuration parameters: {}").format( _("Missing configuration parameters: {keys}").format(
missing_keys, keys=missing_keys,
) )
) )
@ -106,7 +108,11 @@ def check_valid_bot_config(
value = config_data[key] value = config_data[key]
error = validator(key, value) error = validator(key, value)
if error is not None: if error is not None:
raise JsonableError(_("Invalid {} value {} ({})").format(key, value, error)) raise JsonableError(
_("Invalid {key} value {value} ({error})").format(
key=key, value=value, error=error
)
)
elif bot_type == UserProfile.EMBEDDED_BOT: elif bot_type == UserProfile.EMBEDDED_BOT:
try: try:
@ -229,12 +235,12 @@ def user_ids_to_users(user_ids: Sequence[int], realm: Realm) -> List[UserProfile
found_user_ids = user_profiles_by_id.keys() found_user_ids = user_profiles_by_id.keys()
missed_user_ids = [user_id for user_id in user_ids if user_id not in found_user_ids] missed_user_ids = [user_id for user_id in user_ids if user_id not in found_user_ids]
if missed_user_ids: if missed_user_ids:
raise JsonableError(_("Invalid user ID: {}").format(missed_user_ids[0])) raise JsonableError(_("Invalid user ID: {user_id}").format(user_id=missed_user_ids[0]))
user_profiles = list(user_profiles_by_id.values()) user_profiles = list(user_profiles_by_id.values())
for user_profile in user_profiles: for user_profile in user_profiles:
if user_profile.realm != realm: if user_profile.realm != realm:
raise JsonableError(_("Invalid user ID: {}").format(user_profile.id)) raise JsonableError(_("Invalid user ID: {user_id}").format(user_id=user_profile.id))
return user_profiles return user_profiles

View File

@ -314,7 +314,7 @@ def check_dict(
delta_keys = set(val.keys()) - required_keys_set - optional_keys_set delta_keys = set(val.keys()) - required_keys_set - optional_keys_set
if len(delta_keys) != 0: if len(delta_keys) != 0:
raise ValidationError( raise ValidationError(
_("Unexpected arguments: {}").format(", ".join(list(delta_keys))) _("Unexpected arguments: {keys}").format(keys=", ".join(list(delta_keys)))
) )
return cast(Dict[str, ResultT], val) return cast(Dict[str, ResultT], val)

View File

@ -228,4 +228,6 @@ def unix_milliseconds_to_timestamp(milliseconds: Any, webhook: str) -> datetime:
seconds = milliseconds / 1000 seconds = milliseconds / 1000
return timestamp_to_datetime(seconds) return timestamp_to_datetime(seconds)
except (ValueError, TypeError): except (ValueError, TypeError):
raise JsonableError(_("The {} webhook expects time in milliseconds.").format(webhook)) raise JsonableError(
_("The {webhook} webhook expects time in milliseconds.").format(webhook=webhook)
)

View File

@ -76,4 +76,4 @@ def process_zcommands(content: str, user_profile: UserProfile) -> Dict[str, Any]
setting_value=False, setting_value=False,
) )
) )
raise JsonableError(_("No such command: {}").format(command)) raise JsonableError(_("No such command: {command}").format(command=command))

View File

@ -1230,9 +1230,11 @@ def filter_pattern_validator(value: str) -> Pattern[str]:
except re2.error as e: except re2.error as e:
if len(e.args) >= 1: if len(e.args) >= 1:
if isinstance(e.args[0], str): # nocoverage if isinstance(e.args[0], str): # nocoverage
raise ValidationError(_("Bad regular expression: {}").format(e.args[0])) raise ValidationError(_("Bad regular expression: {regex}").format(regex=e.args[0]))
if isinstance(e.args[0], bytes): if isinstance(e.args[0], bytes):
raise ValidationError(_("Bad regular expression: {}").format(e.args[0].decode())) raise ValidationError(
_("Bad regular expression: {regex}").format(regex=e.args[0].decode())
)
raise ValidationError(_("Unknown regular expression error")) # nocoverage raise ValidationError(_("Unknown regular expression error")) # nocoverage
return regex return regex
@ -4697,13 +4699,15 @@ def check_valid_user_ids(realm_id: int, val: object, allow_deactivated: bool = F
try: try:
user_profile = get_user_profile_by_id_in_realm(user_id, realm) user_profile = get_user_profile_by_id_in_realm(user_id, realm)
except UserProfile.DoesNotExist: except UserProfile.DoesNotExist:
raise ValidationError(_("Invalid user ID: {}").format(user_id)) raise ValidationError(_("Invalid user ID: {user_id}").format(user_id=user_id))
if not allow_deactivated and not user_profile.is_active: if not allow_deactivated and not user_profile.is_active:
raise ValidationError(_("User with ID {} is deactivated").format(user_id)) raise ValidationError(
_("User with ID {user_id} is deactivated").format(user_id=user_id)
)
if user_profile.is_bot: if user_profile.is_bot:
raise ValidationError(_("User with ID {} is a bot").format(user_id)) raise ValidationError(_("User with ID {user_id} is a bot").format(user_id=user_id))
return user_ids return user_ids

View File

@ -105,11 +105,13 @@ def events_register_backend(
# These parameters must be false for anonymous requests. # These parameters must be false for anonymous requests.
if client_gravatar: if client_gravatar:
raise JsonableError( raise JsonableError(
_("Invalid '{}' parameter for anonymous request").format("client_gravatar") _("Invalid '{key}' parameter for anonymous request").format(key="client_gravatar")
) )
if include_subscribers: if include_subscribers:
raise JsonableError( raise JsonableError(
_("Invalid '{}' parameter for anonymous request").format("include_subscribers") _("Invalid '{key}' parameter for anonymous request").format(
key="include_subscribers"
)
) )
# Language set by spectator to be passed down to clients as user_settings. # Language set by spectator to be passed down to clients as user_settings.

View File

@ -16,6 +16,6 @@ def mark_hotspot_as_read(
request: HttpRequest, user: UserProfile, hotspot: str = REQ() request: HttpRequest, user: UserProfile, hotspot: str = REQ()
) -> HttpResponse: ) -> HttpResponse:
if hotspot not in ALL_HOTSPOTS: if hotspot not in ALL_HOTSPOTS:
raise JsonableError(_("Unknown hotspot: {}").format(hotspot)) raise JsonableError(_("Unknown hotspot: {hotspot}").format(hotspot=hotspot))
do_mark_hotspot_as_read(user, hotspot) do_mark_hotspot_as_read(user, hotspot)
return json_success(request) return json_success(request)

View File

@ -79,7 +79,9 @@ def invite_users_backend(
(stream, sub) = access_stream_by_id(user_profile, stream_id) (stream, sub) = access_stream_by_id(user_profile, stream_id)
except JsonableError: except JsonableError:
raise JsonableError( raise JsonableError(
_("Stream does not exist with id: {}. No invites were sent.").format(stream_id) _("Stream does not exist with id: {stream_id}. No invites were sent.").format(
stream_id=stream_id
)
) )
streams.append(stream) streams.append(stream)
@ -204,7 +206,11 @@ def generate_multiuse_invite_backend(
try: try:
(stream, sub) = access_stream_by_id(user_profile, stream_id) (stream, sub) = access_stream_by_id(user_profile, stream_id)
except JsonableError: except JsonableError:
raise JsonableError(_("Invalid stream ID {}. No invites were sent.").format(stream_id)) raise JsonableError(
_("Invalid stream ID {stream_id}. No invites were sent.").format(
stream_id=stream_id
)
)
streams.append(stream) streams.append(stream)
invite_link = do_create_multiuse_invite_link( invite_link = do_create_multiuse_invite_link(

View File

@ -96,8 +96,8 @@ def get_messages_backend(
anchor = parse_anchor_value(anchor_val, use_first_unread_anchor_val) anchor = parse_anchor_value(anchor_val, use_first_unread_anchor_val)
if num_before + num_after > MAX_MESSAGES_PER_FETCH: if num_before + num_after > MAX_MESSAGES_PER_FETCH:
raise JsonableError( raise JsonableError(
_("Too many messages requested (maximum {}).").format( _("Too many messages requested (maximum {max_messages}).").format(
MAX_MESSAGES_PER_FETCH, max_messages=MAX_MESSAGES_PER_FETCH,
) )
) )
if num_before > 0 and num_after > 0 and not include_anchor: if num_before > 0 and num_after > 0 and not include_anchor:

View File

@ -166,7 +166,7 @@ def mark_topic_as_read(
) )
if not topic_exists: if not topic_exists:
raise JsonableError(_("No such topic '{}'").format(topic_name)) raise JsonableError(_("No such topic '{topic}'").format(topic=topic_name))
count = do_mark_stream_messages_as_read(user_profile, stream.recipient_id, topic_name) count = do_mark_stream_messages_as_read(user_profile, stream.recipient_id, topic_name)

View File

@ -143,7 +143,7 @@ def update_active_status_backend(
) -> HttpResponse: ) -> HttpResponse:
status_val = UserPresence.status_from_string(status) status_val = UserPresence.status_from_string(status)
if status_val is None: if status_val is None:
raise JsonableError(_("Invalid status: {}").format(status)) raise JsonableError(_("Invalid status: {status}").format(status=status))
elif user_profile.presence_enabled: elif user_profile.presence_enabled:
client = RequestNotes.get_notes(request).client client = RequestNotes.get_notes(request).client
assert client is not None assert client is not None

View File

@ -157,7 +157,7 @@ def update_realm(
# Additional validation/error checking beyond types go here, so # Additional validation/error checking beyond types go here, so
# the entire request can succeed or fail atomically. # the entire request can succeed or fail atomically.
if default_language is not None and default_language not in get_available_language_codes(): if default_language is not None and default_language not in get_available_language_codes():
raise JsonableError(_("Invalid language '{}'").format(default_language)) raise JsonableError(_("Invalid language '{language}'").format(language=default_language))
if authentication_methods is not None: if authentication_methods is not None:
if not user_profile.is_realm_owner: if not user_profile.is_realm_owner:
raise OrganizationOwnerRequiredError raise OrganizationOwnerRequiredError
@ -166,11 +166,17 @@ def update_realm(
if video_chat_provider is not None and video_chat_provider not in { if video_chat_provider is not None and video_chat_provider not in {
p["id"] for p in Realm.VIDEO_CHAT_PROVIDERS.values() p["id"] for p in Realm.VIDEO_CHAT_PROVIDERS.values()
}: }:
raise JsonableError(_("Invalid video_chat_provider {}").format(video_chat_provider)) raise JsonableError(
_("Invalid video_chat_provider {video_chat_provider}").format(
video_chat_provider=video_chat_provider
)
)
if giphy_rating is not None and giphy_rating not in { if giphy_rating is not None and giphy_rating not in {
p["id"] for p in Realm.GIPHY_RATING_OPTIONS.values() p["id"] for p in Realm.GIPHY_RATING_OPTIONS.values()
}: }:
raise JsonableError(_("Invalid giphy_rating {}").format(giphy_rating)) raise JsonableError(
_("Invalid giphy_rating {giphy_rating}").format(giphy_rating=giphy_rating)
)
message_retention_days: Optional[int] = None message_retention_days: Optional[int] = None
if message_retention_days_raw is not None: if message_retention_days_raw is not None:

View File

@ -33,7 +33,7 @@ def create_realm_domain(
try: try:
validate_domain(domain) validate_domain(domain)
except ValidationError as e: except ValidationError as e:
raise JsonableError(_("Invalid domain: {}").format(e.messages[0])) raise JsonableError(_("Invalid domain: {error}").format(error=e.messages[0]))
if RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists(): if RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists():
raise JsonableError( raise JsonableError(
_("The domain {domain} is already a part of your organization.").format(domain=domain) _("The domain {domain} is already a part of your organization.").format(domain=domain)

View File

@ -45,8 +45,8 @@ def upload_emoji(
assert emoji_file.size is not None assert emoji_file.size is not None
if (settings.MAX_EMOJI_FILE_SIZE_MIB * 1024 * 1024) < emoji_file.size: if (settings.MAX_EMOJI_FILE_SIZE_MIB * 1024 * 1024) < emoji_file.size:
raise JsonableError( raise JsonableError(
_("Uploaded file is larger than the allowed limit of {} MiB").format( _("Uploaded file is larger than the allowed limit of {max_size} MiB").format(
settings.MAX_EMOJI_FILE_SIZE_MIB, max_size=settings.MAX_EMOJI_FILE_SIZE_MIB,
) )
) )
@ -58,7 +58,9 @@ def delete_emoji(request: HttpRequest, user_profile: UserProfile, emoji_name: st
if not RealmEmoji.objects.filter( if not RealmEmoji.objects.filter(
realm=user_profile.realm, name=emoji_name, deactivated=False realm=user_profile.realm, name=emoji_name, deactivated=False
).exists(): ).exists():
raise ResourceNotFoundError(_("Emoji '{}' does not exist").format(emoji_name)) raise ResourceNotFoundError(
_("Emoji '{emoji_name}' does not exist").format(emoji_name=emoji_name)
)
check_remove_custom_emoji(user_profile, emoji_name) check_remove_custom_emoji(user_profile, emoji_name)
do_remove_realm_emoji(user_profile.realm, emoji_name, acting_user=user_profile) do_remove_realm_emoji(user_profile.realm, emoji_name, acting_user=user_profile)
return json_success(request) return json_success(request)

View File

@ -24,8 +24,8 @@ def upload_icon(request: HttpRequest, user_profile: UserProfile) -> HttpResponse
assert icon_file.size is not None assert icon_file.size is not None
if (settings.MAX_ICON_FILE_SIZE_MIB * 1024 * 1024) < icon_file.size: if (settings.MAX_ICON_FILE_SIZE_MIB * 1024 * 1024) < icon_file.size:
raise JsonableError( raise JsonableError(
_("Uploaded file is larger than the allowed limit of {} MiB").format( _("Uploaded file is larger than the allowed limit of {max_size} MiB").format(
settings.MAX_ICON_FILE_SIZE_MIB, max_size=settings.MAX_ICON_FILE_SIZE_MIB,
) )
) )
upload_icon_image(icon_file, user_profile) upload_icon_image(icon_file, user_profile)

View File

@ -30,8 +30,8 @@ def upload_logo(
assert logo_file.size is not None assert logo_file.size is not None
if (settings.MAX_LOGO_FILE_SIZE_MIB * 1024 * 1024) < logo_file.size: if (settings.MAX_LOGO_FILE_SIZE_MIB * 1024 * 1024) < logo_file.size:
raise JsonableError( raise JsonableError(
_("Uploaded file is larger than the allowed limit of {} MiB").format( _("Uploaded file is larger than the allowed limit of {max_size} MiB").format(
settings.MAX_LOGO_FILE_SIZE_MIB, max_size=settings.MAX_LOGO_FILE_SIZE_MIB,
) )
) )
upload_logo_image(logo_file, user_profile, night) upload_logo_image(logo_file, user_profile, night)

View File

@ -1052,11 +1052,15 @@ def update_subscription_properties_backend(
value = change["value"] value = change["value"]
if property not in property_converters: if property not in property_converters:
raise JsonableError(_("Unknown subscription property: {}").format(property)) raise JsonableError(
_("Unknown subscription property: {property}").format(property=property)
)
(stream, sub) = access_stream_by_id(user_profile, stream_id) (stream, sub) = access_stream_by_id(user_profile, stream_id)
if sub is None: if sub is None:
raise JsonableError(_("Not subscribed to stream id {}").format(stream_id)) raise JsonableError(
_("Not subscribed to stream id {stream_id}").format(stream_id=stream_id)
)
try: try:
value = property_converters[property](property, value) value = property_converters[property](property, value)

View File

@ -275,8 +275,8 @@ def upload_file_backend(request: HttpRequest, user_profile: UserProfile) -> Http
assert file_size is not None assert file_size is not None
if settings.MAX_FILE_UPLOAD_SIZE * 1024 * 1024 < file_size: if settings.MAX_FILE_UPLOAD_SIZE * 1024 * 1024 < file_size:
raise JsonableError( raise JsonableError(
_("Uploaded file is larger than the allowed limit of {} MiB").format( _("Uploaded file is larger than the allowed limit of {max_size} MiB").format(
settings.MAX_FILE_UPLOAD_SIZE, max_size=settings.MAX_FILE_UPLOAD_SIZE,
) )
) )
check_upload_within_quota(user_profile.realm, file_size) check_upload_within_quota(user_profile.realm, file_size)

View File

@ -265,7 +265,9 @@ def remove_members_from_group_backend(
group_member_ids = get_user_group_direct_member_ids(user_group) group_member_ids = get_user_group_direct_member_ids(user_group)
for member in members: for member in members:
if member not in group_member_ids: if member not in group_member_ids:
raise JsonableError(_("There is no member '{}' in this user group").format(member)) raise JsonableError(
_("There is no member '{user_id}' in this user group").format(user_id=member)
)
user_profile_ids = [user.id for user in user_profiles] user_profile_ids = [user.id for user in user_profiles]
remove_members_from_user_group(user_group, user_profile_ids, acting_user=user_profile) remove_members_from_user_group(user_group, user_profile_ids, acting_user=user_profile)

View File

@ -130,7 +130,11 @@ def check_settings_values(
and notification_sound not in get_available_notification_sounds() and notification_sound not in get_available_notification_sounds()
and notification_sound != "none" and notification_sound != "none"
): ):
raise JsonableError(_("Invalid notification sound '{}'").format(notification_sound)) raise JsonableError(
_("Invalid notification sound '{notification_sound}'").format(
notification_sound=notification_sound
)
)
if email_notifications_batching_period_seconds is not None and ( if email_notifications_batching_period_seconds is not None and (
email_notifications_batching_period_seconds <= 0 email_notifications_batching_period_seconds <= 0
@ -138,8 +142,8 @@ def check_settings_values(
): ):
# We set a limit of one week for the batching period # We set a limit of one week for the batching period
raise JsonableError( raise JsonableError(
_("Invalid email batching period: {} seconds").format( _("Invalid email batching period: {seconds} seconds").format(
email_notifications_batching_period_seconds seconds=email_notifications_batching_period_seconds
) )
) )
@ -275,8 +279,8 @@ def json_change_settings(
assert e.secs_to_freedom is not None assert e.secs_to_freedom is not None
secs_to_freedom = int(e.secs_to_freedom) secs_to_freedom = int(e.secs_to_freedom)
raise JsonableError( raise JsonableError(
_("You're making too many attempts! Try again in {} seconds.").format( _("You're making too many attempts! Try again in {seconds} seconds.").format(
secs_to_freedom seconds=secs_to_freedom
), ),
) )
@ -364,8 +368,8 @@ def set_avatar_backend(request: HttpRequest, user_profile: UserProfile) -> HttpR
assert user_file.size is not None assert user_file.size is not None
if (settings.MAX_AVATAR_FILE_SIZE_MIB * 1024 * 1024) < user_file.size: if (settings.MAX_AVATAR_FILE_SIZE_MIB * 1024 * 1024) < user_file.size:
raise JsonableError( raise JsonableError(
_("Uploaded file is larger than the allowed limit of {} MiB").format( _("Uploaded file is larger than the allowed limit of {max_size} MiB").format(
settings.MAX_AVATAR_FILE_SIZE_MIB, max_size=settings.MAX_AVATAR_FILE_SIZE_MIB,
) )
) )
upload_avatar_image(user_file, user_profile, user_profile) upload_avatar_image(user_file, user_profile, user_profile)

View File

@ -709,7 +709,7 @@ def create_user_backend(
try: try:
get_user_by_delivery_email(email, user_profile.realm) get_user_by_delivery_email(email, user_profile.realm)
raise JsonableError(_("Email '{}' already in use").format(email)) raise JsonableError(_("Email '{email}' already in use").format(email=email))
except UserProfile.DoesNotExist: except UserProfile.DoesNotExist:
pass pass

View File

@ -53,7 +53,7 @@ def api_wordpress_webhook(
data = WP_LOGIN_TEMPLATE.format(name=user_login) data = WP_LOGIN_TEMPLATE.format(name=user_login)
else: else:
raise JsonableError(_("Unknown WordPress webhook action: {}").format(hook)) raise JsonableError(_("Unknown WordPress webhook action: {hook}").format(hook=hook))
topic = "WordPress notification" topic = "WordPress notification"

View File

@ -106,7 +106,7 @@ def register_remote_server(
url_validator = URLValidator() url_validator = URLValidator()
url_validator("http://" + hostname) url_validator("http://" + hostname)
except ValidationError: except ValidationError:
raise JsonableError(_("{} is not a valid hostname").format(hostname)) raise JsonableError(_("{hostname} is not a valid hostname").format(hostname=hostname))
try: try:
validate_email(contact_email) validate_email(contact_email)
@ -370,7 +370,7 @@ def validate_incoming_table_data(
last_id = get_last_id_from_server(server, model) last_id = get_last_id_from_server(server, model)
for row in rows: for row in rows:
if is_count_stat and row["property"] not in COUNT_STATS: if is_count_stat and row["property"] not in COUNT_STATS:
raise JsonableError(_("Invalid property {}").format(row["property"])) raise JsonableError(_("Invalid property {property}").format(property=row["property"]))
if row["id"] <= last_id: if row["id"] <= last_id:
raise JsonableError(_("Data is out of order.")) raise JsonableError(_("Data is out of order."))
last_id = row["id"] last_id = row["id"]