snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
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
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
This commit is contained in:
221
zerver/lib/home.py
Normal file
221
zerver/lib/home.py
Normal file
@@ -0,0 +1,221 @@
|
||||
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
|
||||
Reference in New Issue
Block a user