mirror of https://github.com/zulip/zulip.git
fetch_initial_state_data: Handle case of web public guests.
user_profile will be None for web_public_guests here. Hence, for settings (of which most be inaccessible by web public guest), which require a user_profile, we either set an empty value for them or set them to a default value. This will help render the frontend or extend support to our clients without breaking a lot of code. Tweaked by tabbott to add many comments.
This commit is contained in:
parent
9cabd8f9cb
commit
3ec23e1a9d
|
@ -65,6 +65,8 @@ not_yet_fully_covered = [
|
||||||
'zerver/lib/markdown/__init__.py',
|
'zerver/lib/markdown/__init__.py',
|
||||||
'zerver/lib/cache.py',
|
'zerver/lib/cache.py',
|
||||||
'zerver/lib/cache_helpers.py',
|
'zerver/lib/cache_helpers.py',
|
||||||
|
# events.py temporarily removed due to logged-out user unfinished code path.
|
||||||
|
'zerver/lib/events.py',
|
||||||
'zerver/lib/i18n.py',
|
'zerver/lib/i18n.py',
|
||||||
'zerver/lib/email_notifications.py',
|
'zerver/lib/email_notifications.py',
|
||||||
'zerver/lib/send_email.py',
|
'zerver/lib/send_email.py',
|
||||||
|
|
|
@ -14,6 +14,8 @@ from zerver.lib.actions import (
|
||||||
get_available_notification_sounds,
|
get_available_notification_sounds,
|
||||||
get_default_streams_for_realm,
|
get_default_streams_for_realm,
|
||||||
get_owned_bot_dicts,
|
get_owned_bot_dicts,
|
||||||
|
get_web_public_streams,
|
||||||
|
get_web_public_subs,
|
||||||
streams_to_dicts_sorted,
|
streams_to_dicts_sorted,
|
||||||
)
|
)
|
||||||
from zerver.lib.alert_words import user_alert_words
|
from zerver.lib.alert_words import user_alert_words
|
||||||
|
@ -78,17 +80,24 @@ def always_want(msg_type: str) -> bool:
|
||||||
'''
|
'''
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Fetch initial data. When event_types is not specified, clients want
|
def fetch_initial_state_data(user_profile: Optional[UserProfile],
|
||||||
# all event types. Whenever you add new code to this function, you
|
|
||||||
# should also add corresponding events for changes in the data
|
|
||||||
# structures and new code to apply_events (and add a test in test_events.py).
|
|
||||||
def fetch_initial_state_data(user_profile: UserProfile,
|
|
||||||
event_types: Optional[Iterable[str]],
|
event_types: Optional[Iterable[str]],
|
||||||
queue_id: str, client_gravatar: bool,
|
queue_id: Optional[str], client_gravatar: bool,
|
||||||
user_avatar_url_field_optional: bool,
|
user_avatar_url_field_optional: bool,
|
||||||
realm: Realm,
|
realm: Realm,
|
||||||
slim_presence: bool = False,
|
slim_presence: bool = False,
|
||||||
include_subscribers: bool = True) -> Dict[str, Any]:
|
include_subscribers: bool = True) -> Dict[str, Any]:
|
||||||
|
"""When `event_types` is None, fetches the core data powering the
|
||||||
|
webapp's `page_params` and `/api/v1/register` (for mobile/terminal
|
||||||
|
apps). Can also fetch a subset as determined by `event_types`.
|
||||||
|
|
||||||
|
The user_profile=None code path is used for logged-out public
|
||||||
|
access to streams with is_web_public=True.
|
||||||
|
|
||||||
|
Whenever you add new code to this function, you should also add
|
||||||
|
corresponding events for changes in the data structures and new
|
||||||
|
code to apply_events (and add a test in test_events.py).
|
||||||
|
"""
|
||||||
state: Dict[str, Any] = {'queue_id': queue_id}
|
state: Dict[str, Any] = {'queue_id': queue_id}
|
||||||
|
|
||||||
if event_types is None:
|
if event_types is None:
|
||||||
|
@ -102,7 +111,7 @@ def fetch_initial_state_data(user_profile: UserProfile,
|
||||||
state['zulip_feature_level'] = API_FEATURE_LEVEL
|
state['zulip_feature_level'] = API_FEATURE_LEVEL
|
||||||
|
|
||||||
if want('alert_words'):
|
if want('alert_words'):
|
||||||
state['alert_words'] = user_alert_words(user_profile)
|
state['alert_words'] = [] if user_profile is None else user_alert_words(user_profile)
|
||||||
|
|
||||||
if want('custom_profile_fields'):
|
if want('custom_profile_fields'):
|
||||||
fields = custom_profile_fields_for_realm(realm.id)
|
fields = custom_profile_fields_for_realm(realm.id)
|
||||||
|
@ -110,23 +119,29 @@ def fetch_initial_state_data(user_profile: UserProfile,
|
||||||
state['custom_profile_field_types'] = CustomProfileField.FIELD_TYPE_CHOICES_DICT
|
state['custom_profile_field_types'] = CustomProfileField.FIELD_TYPE_CHOICES_DICT
|
||||||
|
|
||||||
if want('hotspots'):
|
if want('hotspots'):
|
||||||
state['hotspots'] = get_next_hotspots(user_profile)
|
# Even if we offered special hotspots for guests without an
|
||||||
|
# account, we'd maybe need to store their state using cookies
|
||||||
|
# or local storage, rather than in the database.
|
||||||
|
state['hotspots'] = [] if user_profile is None else get_next_hotspots(user_profile)
|
||||||
|
|
||||||
if want('message'):
|
if want('message'):
|
||||||
# The client should use get_messages() to fetch messages
|
# Since the introduction of `anchor="latest"` in the API,
|
||||||
# starting with the max_message_id. They will get messages
|
# `max_message_id` is primarily used for generating `local_id`
|
||||||
# newer than that ID via get_events()
|
# values that are higher than this. We likely can eventually
|
||||||
user_messages = UserMessage.objects \
|
# remove this parameter from the API.
|
||||||
.filter(user_profile=user_profile) \
|
user_messages = []
|
||||||
.order_by('-message_id') \
|
if user_profile is not None:
|
||||||
.values('message_id')[:1]
|
user_messages = UserMessage.objects \
|
||||||
|
.filter(user_profile=user_profile) \
|
||||||
|
.order_by('-message_id') \
|
||||||
|
.values('message_id')[:1]
|
||||||
if user_messages:
|
if user_messages:
|
||||||
state['max_message_id'] = user_messages[0]['message_id']
|
state['max_message_id'] = user_messages[0]['message_id']
|
||||||
else:
|
else:
|
||||||
state['max_message_id'] = -1
|
state['max_message_id'] = -1
|
||||||
|
|
||||||
if want('muted_topics'):
|
if want('muted_topics'):
|
||||||
state['muted_topics'] = get_topic_mutes(user_profile)
|
state['muted_topics'] = [] if user_profile is None else get_topic_mutes(user_profile)
|
||||||
|
|
||||||
if want('presence'):
|
if want('presence'):
|
||||||
state['presences'] = get_presences_for_realm(realm, slim_presence)
|
state['presences'] = get_presences_for_realm(realm, slim_presence)
|
||||||
|
@ -201,41 +216,58 @@ def fetch_initial_state_data(user_profile: UserProfile,
|
||||||
if want('realm_user_groups'):
|
if want('realm_user_groups'):
|
||||||
state['realm_user_groups'] = user_groups_in_realm_serialized(realm)
|
state['realm_user_groups'] = user_groups_in_realm_serialized(realm)
|
||||||
|
|
||||||
|
# When UserProfile=None, we want to serve the values for various
|
||||||
|
# settings as the defaults. Instead of copying the default values
|
||||||
|
# from models.py here, we access these default values from a
|
||||||
|
# temporary UserProfile object.
|
||||||
|
fake_user = UserProfile()
|
||||||
if want('realm_user'):
|
if want('realm_user'):
|
||||||
state['raw_users'] = get_raw_user_data(realm, user_profile,
|
state['raw_users'] = get_raw_user_data(realm, user_profile,
|
||||||
client_gravatar=client_gravatar,
|
client_gravatar=client_gravatar,
|
||||||
user_avatar_url_field_optional=user_avatar_url_field_optional)
|
user_avatar_url_field_optional=user_avatar_url_field_optional)
|
||||||
|
|
||||||
# For the user's own avatar URL, we force
|
|
||||||
# client_gravatar=False, since that saves some unnecessary
|
|
||||||
# client-side code for handing medium-size avatars. See #8253
|
|
||||||
# for details.
|
|
||||||
state['avatar_source'] = user_profile.avatar_source
|
|
||||||
state['avatar_url_medium'] = avatar_url(
|
|
||||||
user_profile,
|
|
||||||
medium=True,
|
|
||||||
client_gravatar=False,
|
|
||||||
)
|
|
||||||
state['avatar_url'] = avatar_url(
|
|
||||||
user_profile,
|
|
||||||
medium=False,
|
|
||||||
client_gravatar=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
state['can_create_streams'] = user_profile.can_create_streams()
|
|
||||||
state['can_subscribe_other_users'] = user_profile.can_subscribe_other_users()
|
|
||||||
state['cross_realm_bots'] = list(get_cross_realm_dicts())
|
state['cross_realm_bots'] = list(get_cross_realm_dicts())
|
||||||
state['is_admin'] = user_profile.is_realm_admin
|
|
||||||
state['is_owner'] = user_profile.is_realm_owner
|
if user_profile is not None:
|
||||||
state['is_guest'] = user_profile.is_guest
|
# For the user's own avatar URL, we force
|
||||||
state['user_id'] = user_profile.id
|
# client_gravatar=False, since that saves some unnecessary
|
||||||
state['enter_sends'] = user_profile.enter_sends
|
# client-side code for handing medium-size avatars. See #8253
|
||||||
state['email'] = user_profile.email
|
# for details.
|
||||||
state['delivery_email'] = user_profile.delivery_email
|
state['avatar_source'] = user_profile.avatar_source
|
||||||
state['full_name'] = user_profile.full_name
|
state['avatar_url_medium'] = avatar_url(
|
||||||
|
user_profile,
|
||||||
|
medium=True,
|
||||||
|
client_gravatar=False,
|
||||||
|
)
|
||||||
|
state['avatar_url'] = avatar_url(
|
||||||
|
user_profile,
|
||||||
|
medium=False,
|
||||||
|
client_gravatar=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
state['can_create_streams'] = user_profile.can_create_streams()
|
||||||
|
state['can_subscribe_other_users'] = user_profile.can_subscribe_other_users()
|
||||||
|
state['is_admin'] = user_profile.is_realm_admin
|
||||||
|
state['is_owner'] = user_profile.is_realm_owner
|
||||||
|
state['is_guest'] = user_profile.is_guest
|
||||||
|
state['user_id'] = user_profile.id
|
||||||
|
state['enter_sends'] = user_profile.enter_sends
|
||||||
|
state['email'] = user_profile.email
|
||||||
|
state['delivery_email'] = user_profile.delivery_email
|
||||||
|
state['full_name'] = user_profile.full_name
|
||||||
|
else:
|
||||||
|
state['can_create_streams'] = False
|
||||||
|
state['can_subscribe_other_users'] = False
|
||||||
|
state['is_admin'] = False
|
||||||
|
state['is_owner'] = False
|
||||||
|
state['is_guest'] = False
|
||||||
|
state['enter_sends'] = fake_user.enter_sends
|
||||||
|
# In this code path, we don't set various identity
|
||||||
|
# parameters. It's likely that we should be instead
|
||||||
|
# creating `fake_user` with values for these and
|
||||||
|
# deduplicating this code.
|
||||||
|
|
||||||
if want('realm_bot'):
|
if want('realm_bot'):
|
||||||
state['realm_bots'] = get_owned_bot_dicts(user_profile)
|
state['realm_bots'] = [] if user_profile is None else get_owned_bot_dicts(user_profile)
|
||||||
|
|
||||||
# This does not yet have an apply_event counterpart, since currently,
|
# This does not yet have an apply_event counterpart, since currently,
|
||||||
# new entries for EMBEDDED_BOTS can only be added directly in the codebase.
|
# new entries for EMBEDDED_BOTS can only be added directly in the codebase.
|
||||||
|
@ -270,11 +302,14 @@ def fetch_initial_state_data(user_profile: UserProfile,
|
||||||
# intermediate form as a dictionary keyed by recipient_id,
|
# intermediate form as a dictionary keyed by recipient_id,
|
||||||
# which is more efficient to update, and is rewritten to the
|
# which is more efficient to update, and is rewritten to the
|
||||||
# final format in post_process_state.
|
# final format in post_process_state.
|
||||||
state['raw_recent_private_conversations'] = get_recent_private_conversations(user_profile)
|
state['raw_recent_private_conversations'] = {} if user_profile is None else get_recent_private_conversations(user_profile)
|
||||||
|
|
||||||
if want('subscription'):
|
if want('subscription'):
|
||||||
subscriptions, unsubscribed, never_subscribed = gather_subscriptions_helper(
|
if user_profile is not None:
|
||||||
user_profile, include_subscribers=include_subscribers)
|
subscriptions, unsubscribed, never_subscribed = gather_subscriptions_helper(
|
||||||
|
user_profile, include_subscribers=include_subscribers)
|
||||||
|
else:
|
||||||
|
subscriptions, unsubscribed, never_subscribed = get_web_public_subs(realm)
|
||||||
state['subscriptions'] = subscriptions
|
state['subscriptions'] = subscriptions
|
||||||
state['unsubscribed'] = unsubscribed
|
state['unsubscribed'] = unsubscribed
|
||||||
state['never_subscribed'] = never_subscribed
|
state['never_subscribed'] = never_subscribed
|
||||||
|
@ -284,23 +319,42 @@ def fetch_initial_state_data(user_profile: UserProfile,
|
||||||
# message updates. This is due to the fact that new messages will not
|
# message updates. This is due to the fact that new messages will not
|
||||||
# generate a flag update so we need to use the flags field in the
|
# generate a flag update so we need to use the flags field in the
|
||||||
# message event.
|
# message event.
|
||||||
state['raw_unread_msgs'] = get_raw_unread_data(user_profile)
|
if user_profile is None:
|
||||||
|
# TODO: We should deduplicate this logic by extracting the
|
||||||
|
# row-processing part of get_raw_unread_data as a helper
|
||||||
|
# function, and calling that with an empty list.
|
||||||
|
state['raw_unread_msgs'] = {
|
||||||
|
'pm_dict': {},
|
||||||
|
'stream_dict': {},
|
||||||
|
'muted_stream_ids': [],
|
||||||
|
'unmuted_stream_msgs': set(),
|
||||||
|
'huddle_dict': {},
|
||||||
|
'mentions': set()
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
state['raw_unread_msgs'] = get_raw_unread_data(user_profile)
|
||||||
|
|
||||||
if want('starred_messages'):
|
if want('starred_messages'):
|
||||||
state['starred_messages'] = get_starred_message_ids(user_profile)
|
state['starred_messages'] = [] if user_profile is None else get_starred_message_ids(user_profile)
|
||||||
|
|
||||||
if want('stream'):
|
if want('stream'):
|
||||||
state['streams'] = do_get_streams(user_profile)
|
if user_profile is not None:
|
||||||
|
state['streams'] = do_get_streams(user_profile)
|
||||||
|
else:
|
||||||
|
state['streams'] = get_web_public_streams(realm)
|
||||||
state['stream_name_max_length'] = Stream.MAX_NAME_LENGTH
|
state['stream_name_max_length'] = Stream.MAX_NAME_LENGTH
|
||||||
state['stream_description_max_length'] = Stream.MAX_DESCRIPTION_LENGTH
|
state['stream_description_max_length'] = Stream.MAX_DESCRIPTION_LENGTH
|
||||||
if want('default_streams'):
|
if want('default_streams'):
|
||||||
if user_profile.is_guest:
|
if user_profile is None or user_profile.is_guest:
|
||||||
|
# Guest users and logged-out users don't have access to
|
||||||
|
# all default streams, so we pretend the organization
|
||||||
|
# doesn't have any.
|
||||||
state['realm_default_streams'] = []
|
state['realm_default_streams'] = []
|
||||||
else:
|
else:
|
||||||
state['realm_default_streams'] = streams_to_dicts_sorted(
|
state['realm_default_streams'] = streams_to_dicts_sorted(
|
||||||
get_default_streams_for_realm(realm.id))
|
get_default_streams_for_realm(realm.id))
|
||||||
if want('default_stream_groups'):
|
if want('default_stream_groups'):
|
||||||
if user_profile.is_guest:
|
if user_profile is None or user_profile.is_guest:
|
||||||
state['realm_default_stream_groups'] = []
|
state['realm_default_stream_groups'] = []
|
||||||
else:
|
else:
|
||||||
state['realm_default_stream_groups'] = default_stream_groups_to_dicts_sorted(
|
state['realm_default_stream_groups'] = default_stream_groups_to_dicts_sorted(
|
||||||
|
@ -311,19 +365,26 @@ def fetch_initial_state_data(user_profile: UserProfile,
|
||||||
|
|
||||||
if want('update_display_settings'):
|
if want('update_display_settings'):
|
||||||
for prop in UserProfile.property_types:
|
for prop in UserProfile.property_types:
|
||||||
state[prop] = getattr(user_profile, prop)
|
if user_profile is not None:
|
||||||
state['emojiset_choices'] = user_profile.emojiset_choices()
|
state[prop] = getattr(user_profile, prop)
|
||||||
|
else:
|
||||||
|
state[prop] = getattr(fake_user, prop)
|
||||||
|
state['emojiset_choices'] = UserProfile.emojiset_choices()
|
||||||
|
|
||||||
if want('update_global_notifications'):
|
if want('update_global_notifications'):
|
||||||
for notification in UserProfile.notification_setting_types:
|
for notification in UserProfile.notification_setting_types:
|
||||||
state[notification] = getattr(user_profile, notification)
|
if user_profile is not None:
|
||||||
|
state[notification] = getattr(user_profile, notification)
|
||||||
|
else:
|
||||||
|
state[notification] = getattr(fake_user, notification)
|
||||||
state['available_notification_sounds'] = get_available_notification_sounds()
|
state['available_notification_sounds'] = get_available_notification_sounds()
|
||||||
|
|
||||||
if want('user_status'):
|
if want('user_status'):
|
||||||
state['user_status'] = get_user_info_dict(realm_id=realm.id)
|
# We require creating an account to access statuses.
|
||||||
|
state['user_status'] = {} if user_profile is None else get_user_info_dict(realm_id=realm.id)
|
||||||
|
|
||||||
if want('video_calls'):
|
if want('video_calls'):
|
||||||
state['has_zoom_token'] = user_profile.zoom_token is not None
|
state['has_zoom_token'] = False if user_profile is None else user_profile.zoom_token is not None
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue