Files
upstream-zulip/zerver/lib/home.py
upstream-mirror 64898b2c79
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
2026-09-15 15:05:30 +05:00

222 lines
8.2 KiB
Python

import calendar
import time
from dataclasses import dataclass
from urllib.parse import urlsplit
from django.conf import settings
from django.http import HttpRequest
from django.utils import translation
from two_factor.utils import default_device
from zerver.context_processors import get_apps_page_url
from zerver.lib.events import ClientCapabilities, do_events_register
from zerver.lib.i18n import (
get_and_set_request_language,
get_language_list,
get_language_translation_data,
)
from zerver.lib.narrow_helpers import NeverNegatedNarrowTerm
from zerver.lib.realm_description import get_realm_rendered_description
from zerver.lib.request import RequestNotes
from zerver.lib.workplace_users import (
realm_eligible_for_non_workplace_pricing,
realm_on_discounted_cloud_plan,
)
from zerver.models import Realm, Stream, UserProfile
from zerver.views.message_flags import get_latest_update_message_flag_activity
@dataclass
class BillingInfo:
show_billing: bool
show_plans: bool
sponsorship_pending: bool
show_remote_billing: bool
@dataclass
class UserPermissionInfo:
color_scheme: int
is_guest: bool
is_realm_admin: bool
is_realm_owner: bool
def get_furthest_read_time(user_profile: UserProfile | None) -> float | None:
if user_profile is None:
return time.time()
user_activity = get_latest_update_message_flag_activity(user_profile)
if user_activity is None:
return None
return calendar.timegm(user_activity.last_visit.utctimetuple())
def promote_sponsoring_zulip_in_realm(realm: Realm) -> bool:
if not settings.PROMOTE_SPONSORING_ZULIP:
return False
# If PROMOTE_SPONSORING_ZULIP is enabled, advertise sponsoring
# Zulip in the gear menu of non-paying organizations.
return realm.plan_type in [Realm.PLAN_TYPE_STANDARD_FREE, Realm.PLAN_TYPE_SELF_HOSTED]
def get_user_permission_info(user_profile: UserProfile | None) -> UserPermissionInfo:
if user_profile is not None:
return UserPermissionInfo(
color_scheme=user_profile.color_scheme,
is_guest=user_profile.is_guest,
is_realm_owner=user_profile.is_realm_owner,
is_realm_admin=user_profile.is_realm_admin,
)
else:
return UserPermissionInfo(
color_scheme=UserProfile.COLOR_SCHEME_AUTOMATIC,
is_guest=False,
is_realm_admin=False,
is_realm_owner=False,
)
def build_page_params_for_home_page_load(
request: HttpRequest,
user_profile: UserProfile | None,
realm: Realm,
insecure_desktop_app: bool,
narrow: list[NeverNegatedNarrowTerm],
narrow_stream: Stream | None,
narrow_topic_name: str | None,
) -> tuple[int, dict[str, object]]:
"""
This function computes page_params for when we load the home page.
The page_params data structure gets sent to the client.
"""
client_capabilities = ClientCapabilities(
notification_settings_null=True,
bulk_message_deletion=True,
user_avatar_url_field_optional=True,
stream_typing_notifications=True,
linkifier_url_template=True,
user_list_incomplete=True,
include_deactivated_groups=True,
archived_channels=True,
empty_topic_name=True,
simplified_presence_events=True,
individual_emoji_changes=True,
)
# When the client triggers a reload (e.g., after an expired event
# queue), it navigates to /?state_data=deferred. In that case, we
# skip the expensive do_events_register() call and let the client
# fetch state via the /json/register API instead. This makes the
# HTML response much smaller and avoids partial-transfer failures
# on flaky networks (common in Firefox background tabs resuming
# from laptop suspend). See #36094.
is_client_reload = request.GET.get("state_data") == "deferred" and user_profile is not None
if user_profile is not None and not is_client_reload:
client = RequestNotes.get_notes(request).client
assert client is not None
state_data = do_events_register(
user_profile,
realm,
client,
apply_markdown=True,
client_gravatar=True,
slim_presence=True,
presence_last_update_id_fetched_by_client=-1,
presence_history_limit_days=settings.PRESENCE_HISTORY_LIMIT_DAYS_FOR_WEB_APP,
client_capabilities=client_capabilities,
narrow=narrow,
include_streams=False,
include_subscribers="partial",
)
queue_id = state_data["queue_id"]
default_language = state_data["user_settings"]["default_language"]
elif user_profile is not None:
# Client-triggered reload: the client will fetch state_data
# via /json/register after the page loads.
state_data = None
queue_id = None
default_language = user_profile.default_language
else:
# The spectator client will be fetching the /register response
# for spectators via the API.
state_data = None
queue_id = None
default_language = realm.default_language
if user_profile is None:
request_language = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME, default_language)
split_url = urlsplit(request.build_absolute_uri())
show_try_zulip_modal = (
settings.DEVELOPMENT or split_url.hostname == "chat.zulip.org"
) and split_url.query == "show_try_zulip_modal"
else:
request_language = get_and_set_request_language(
request,
default_language,
translation.get_language_from_path(request.path_info),
)
show_try_zulip_modal = False
furthest_read_time = get_furthest_read_time(user_profile)
two_fa_enabled = settings.TWO_FACTOR_AUTHENTICATION_ENABLED and user_profile is not None
# Pass parameters to the client-side JavaScript code.
# These end up in a JavaScript Object named 'page_params'.
#
# Sync this with home_params_schema in base_page_params.ts.
page_params: dict[str, object] = dict(
page_type="home",
## Server settings.
test_suite=settings.TEST_SUITE,
insecure_desktop_app=insecure_desktop_app,
login_page=settings.HOME_NOT_LOGGED_IN,
warn_no_email=settings.WARN_NO_EMAIL,
# Only show marketing email settings if on Zulip Cloud
corporate_enabled=settings.CORPORATE_ENABLED,
## Misc. extra data.
language_list=get_language_list(),
furthest_read_time=furthest_read_time,
embedded_bots_enabled=settings.EMBEDDED_BOTS_ENABLED,
two_fa_enabled=two_fa_enabled,
apps_page_url=get_apps_page_url(),
promote_sponsoring_zulip=promote_sponsoring_zulip_in_realm(realm),
# Adding two_fa_enabled as condition saves us 3 queries when
# 2FA is not enabled.
two_fa_enabled_user=two_fa_enabled and bool(default_device(user_profile)),
is_spectator=user_profile is None,
presence_history_limit_days_for_web_app=settings.PRESENCE_HISTORY_LIMIT_DAYS_FOR_WEB_APP,
# The client will fetch state_data (and create an event
# queue) via /json/register for spectators and reloads.
no_event_queue=user_profile is None or is_client_reload,
show_try_zulip_modal=show_try_zulip_modal,
non_workplace_pricing_eligible=realm_eligible_for_non_workplace_pricing(realm),
is_cloud_realm_with_discounted_plan=realm_on_discounted_cloud_plan(realm),
)
page_params["state_data"] = state_data
if narrow_stream is not None:
page_params["narrow_stream"] = narrow_stream.name
if narrow_topic_name is not None:
page_params["narrow_topic"] = narrow_topic_name
page_params["narrow"] = [
dict(operator=term.operator, operand=term.operand) for term in narrow
]
page_params["translation_data"] = get_language_translation_data(request_language)
# This is used by `admin.ts` to display realm description for non-administrator
# logged-in users.
page_params["realm_rendered_description"] = get_realm_rendered_description(realm)
if user_profile is None:
page_params["language_cookie_name"] = settings.LANGUAGE_COOKIE_NAME
return queue_id, page_params