From 2ab71fb67dd98dd207bf779f9a3982a4cdc24881 Mon Sep 17 00:00:00 2001 From: Sahil Batra Date: Wed, 8 Nov 2023 15:43:25 +0530 Subject: [PATCH] users: Update user_profile_to_user_row to return a TypedDict. This commit updates user_profile_to_user_row to return a TypedDict and also updates the return type of get_realm_user_dicts to be a TypedDict. This commit is a prep commit for feature of restricting user access such that code can be easy to read and understand when we add that feature. --- zerver/lib/types.py | 19 +++++++++++++++++++ zerver/lib/users.py | 33 ++++++++++++++++++++++++--------- zerver/models.py | 3 ++- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/zerver/lib/types.py b/zerver/lib/types.py index ef59a6ef3f..baea1af42e 100644 --- a/zerver/lib/types.py +++ b/zerver/lib/types.py @@ -298,3 +298,22 @@ class ServerSupportedPermissionSettings: realm: Dict[str, GroupPermissionSetting] stream: Dict[str, GroupPermissionSetting] group: Dict[str, GroupPermissionSetting] + + +class RawUserDict(TypedDict): + id: int + full_name: str + email: str + avatar_source: str + avatar_version: int + is_active: bool + role: int + is_billing_admin: bool + is_bot: bool + timezone: str + date_joined: datetime.datetime + bot_owner_id: Optional[int] + delivery_email: str + bot_type: Optional[int] + long_term_idle: bool + email_address_visibility: int diff --git a/zerver/lib/users.py b/zerver/lib/users.py index 4835cb7380..888600eb78 100644 --- a/zerver/lib/users.py +++ b/zerver/lib/users.py @@ -7,20 +7,19 @@ import dateutil.parser as date_parser from django.conf import settings from django.core.exceptions import ValidationError from django.db.models import QuerySet -from django.forms.models import model_to_dict from django.utils.translation import gettext as _ from django_otp.middleware import is_verified from zulip_bots.custom_exceptions import ConfigValidationError from zerver.lib.avatar import avatar_url, get_avatar_field -from zerver.lib.cache import cache_with_key, get_cross_realm_dicts_key, realm_user_dict_fields +from zerver.lib.cache import cache_with_key, get_cross_realm_dicts_key from zerver.lib.exceptions import ( JsonableError, OrganizationAdministratorRequiredError, OrganizationOwnerRequiredError, ) from zerver.lib.timezone import canonicalize_timezone -from zerver.lib.types import ProfileDataElementUpdateDict, ProfileDataElementValue +from zerver.lib.types import ProfileDataElementUpdateDict, ProfileDataElementValue, RawUserDict from zerver.models import ( CustomProfileField, CustomProfileFieldValue, @@ -388,7 +387,7 @@ def can_access_delivery_email( def format_user_row( realm_id: int, acting_user: Optional[UserProfile], - row: Dict[str, Any], + row: RawUserDict, client_gravatar: bool, user_avatar_url_field_optional: bool, custom_profile_field_data: Optional[Dict[str, Any]] = None, @@ -425,6 +424,7 @@ def format_user_row( # Only send day level precision date_joined data to spectators. del result["is_billing_admin"] del result["timezone"] + assert isinstance(result["date_joined"], str) result["date_joined"] = str(date_parser.parse(result["date_joined"]).date()) # Zulip clients that support using `GET /avatar/{user_id}` as a @@ -476,7 +476,7 @@ def format_user_row( return result -def user_profile_to_user_row(user_profile: UserProfile) -> Dict[str, Any]: +def user_profile_to_user_row(user_profile: UserProfile) -> RawUserDict: # What we're trying to do is simulate the user_profile having been # fetched from a QuerySet using `.values(*realm_user_dict_fields)` # even though we fetched UserProfile objects. This is messier @@ -492,10 +492,25 @@ def user_profile_to_user_row(user_profile: UserProfile) -> Dict[str, Any]: # This could be potentially simplified in the future by # changing realm_user_dict_fields to name the bot owner with # the less readable `bot_owner` (instead of `bot_owner_id`). - user_row = model_to_dict(user_profile, fields=[*realm_user_dict_fields, "bot_owner"]) - user_row["bot_owner_id"] = user_row["bot_owner"] - del user_row["bot_owner"] - return user_row + + return RawUserDict( + id=user_profile.id, + full_name=user_profile.full_name, + email=user_profile.email, + avatar_source=user_profile.avatar_source, + avatar_version=user_profile.avatar_version, + is_active=user_profile.is_active, + role=user_profile.role, + is_billing_admin=user_profile.is_billing_admin, + is_bot=user_profile.is_bot, + timezone=user_profile.timezone, + date_joined=user_profile.date_joined, + bot_owner_id=user_profile.bot_owner_id, + delivery_email=user_profile.delivery_email, + bot_type=user_profile.bot_type, + long_term_idle=user_profile.long_term_idle, + email_address_visibility=user_profile.email_address_visibility, + ) @cache_with_key(get_cross_realm_dicts_key) diff --git a/zerver/models.py b/zerver/models.py index c464cc34fa..1edab6be75 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -100,6 +100,7 @@ from zerver.lib.types import ( ProfileData, ProfileDataElementBase, ProfileDataElementValue, + RawUserDict, RealmPlaygroundDict, RealmUserValidator, UnspecifiedValue, @@ -4173,7 +4174,7 @@ def get_user_by_id_in_realm_including_cross_realm( @cache_with_key(realm_user_dicts_cache_key, timeout=3600 * 24 * 7) -def get_realm_user_dicts(realm_id: int) -> List[Dict[str, Any]]: +def get_realm_user_dicts(realm_id: int) -> List[RawUserDict]: return list( UserProfile.objects.filter( realm_id=realm_id,