diff --git a/analytics/tests/test_stats_views.py b/analytics/tests/test_stats_views.py index 2591a40044..97959a0fc0 100644 --- a/analytics/tests/test_stats_views.py +++ b/analytics/tests/test_stats_views.py @@ -10,7 +10,8 @@ from analytics.models import FillState, RealmCount, StreamCount, UserCount from analytics.views.stats import rewrite_client_arrays, sort_by_totals, sort_client_labels from zerver.lib.test_classes import ZulipTestCase from zerver.lib.timestamp import ceiling_to_day, ceiling_to_hour, datetime_to_timestamp -from zerver.models import Client, get_realm +from zerver.models import Client +from zerver.models.realms import get_realm class TestStatsEndpoint(ZulipTestCase): diff --git a/analytics/tests/test_support_views.py b/analytics/tests/test_support_views.py index a162edfcbf..922b1bd79a 100644 --- a/analytics/tests/test_support_views.py +++ b/analytics/tests/test_support_views.py @@ -23,16 +23,8 @@ from zerver.actions.realm_settings import do_change_realm_org_type, do_send_real from zerver.actions.user_settings import do_change_user_setting from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import reset_email_visibility_to_everyone_in_zulip_realm -from zerver.models import ( - MultiuseInvite, - OrgTypeEnum, - PreregistrationUser, - Realm, - UserMessage, - UserProfile, - get_org_type_display_name, - get_realm, -) +from zerver.models import MultiuseInvite, PreregistrationUser, Realm, UserMessage, UserProfile +from zerver.models.realms import OrgTypeEnum, get_org_type_display_name, get_realm from zilencer.lib.remote_counts import MissingDataError if TYPE_CHECKING: diff --git a/analytics/views/installation_activity.py b/analytics/views/installation_activity.py index 5144e969c8..58f4aabc36 100644 --- a/analytics/views/installation_activity.py +++ b/analytics/views/installation_activity.py @@ -25,7 +25,8 @@ from analytics.views.activity_common import ( from analytics.views.support import get_plan_type_string from zerver.decorator import require_server_admin from zerver.lib.request import has_request_variables -from zerver.models import Realm, get_org_type_display_name +from zerver.models import Realm +from zerver.models.realms import get_org_type_display_name if settings.BILLING_ENABLED: from corporate.lib.analytics import ( diff --git a/analytics/views/stats.py b/analytics/views/stats.py index 9366945151..a12873bf55 100644 --- a/analytics/views/stats.py +++ b/analytics/views/stats.py @@ -36,7 +36,8 @@ from zerver.lib.response import json_success from zerver.lib.streams import access_stream_by_id from zerver.lib.timestamp import convert_to_UTC from zerver.lib.validator import to_non_negative_int -from zerver.models import Client, Realm, Stream, UserProfile, get_realm +from zerver.models import Client, Realm, Stream, UserProfile +from zerver.models.realms import get_realm if settings.ZILENCER_ENABLED: from zilencer.models import RemoteInstallationCount, RemoteRealmCount, RemoteZulipServer diff --git a/analytics/views/support.py b/analytics/views/support.py index 06ee893051..d16ef66e48 100644 --- a/analytics/views/support.py +++ b/analytics/views/support.py @@ -40,9 +40,8 @@ from zerver.models import ( Realm, RealmReactivationStatus, UserProfile, - get_org_type_display_name, - get_realm, ) +from zerver.models.realms import get_org_type_display_name, get_realm from zerver.models.users import get_user_profile_by_id from zerver.views.invite import get_invitee_emails_set diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index df861af806..51b76b29ec 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -50,7 +50,8 @@ from zerver.lib.send_email import ( from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime from zerver.lib.url_encoding import append_url_query_string from zerver.lib.utils import assert_is_not_none -from zerver.models import Realm, RealmAuditLog, UserProfile, get_org_type_display_name, get_realm +from zerver.models import Realm, RealmAuditLog, UserProfile +from zerver.models.realms import get_org_type_display_name, get_realm from zerver.models.users import get_system_bot from zilencer.lib.remote_counts import MissingDataError from zilencer.models import ( diff --git a/corporate/lib/support.py b/corporate/lib/support.py index eb4ffa5c80..4076245993 100644 --- a/corporate/lib/support.py +++ b/corporate/lib/support.py @@ -14,7 +14,8 @@ from corporate.models import ( ZulipSponsorshipRequest, get_current_plan_by_customer, ) -from zerver.models import Realm, get_org_type_display_name, get_realm +from zerver.models import Realm +from zerver.models.realms import get_org_type_display_name, get_realm from zilencer.lib.remote_counts import MissingDataError diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index 4653c3594f..35f98e12cc 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -102,7 +102,8 @@ from zerver.lib.remote_server import send_server_data_to_push_bouncer from zerver.lib.test_classes import ZulipTestCase from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime from zerver.lib.utils import assert_is_not_none -from zerver.models import Message, Realm, RealmAuditLog, Recipient, UserProfile, get_realm +from zerver.models import Message, Realm, RealmAuditLog, Recipient, UserProfile +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot from zilencer.lib.remote_counts import MissingDataError from zilencer.models import ( diff --git a/docs/tutorials/new-feature-tutorial.md b/docs/tutorials/new-feature-tutorial.md index 6345753fe4..0e34ee02fa 100644 --- a/docs/tutorials/new-feature-tutorial.md +++ b/docs/tutorials/new-feature-tutorial.md @@ -35,7 +35,7 @@ organization in Zulip). The following files are involved in the process: **Backend** -- `zerver/models/__init__.py`: Defines the database model. +- `zerver/models/realms.py`: Defines the database model. - `zerver/views/realm.py`: The view function that implements the API endpoint for editing realm objects. - `zerver/actions/realm_settings.py`: Contains code for updating and interacting with the database. @@ -73,7 +73,7 @@ organization in Zulip). The following files are involved in the process: ### Adding a field to the database **Update the model:** The server accesses the underlying database in -`zerver/models/__init__.py`. Add a new field in the appropriate class. +`zerver/models/realms.py`. Add a new field in the appropriate class. **Create and run the migration:** To create and apply a migration, run the following commands: @@ -185,10 +185,10 @@ task of requiring messages to have a topic, you can [view this commit](https://g First, update the database and model to store the new setting. Add a new boolean field, `mandatory_topics`, to the Realm model in -`zerver/models/__init__.py`. +`zerver/models/realms.py`. ```diff - # zerver/models/__init__.py + # zerver/models/realms.py class Realm(models.Model): # ... @@ -205,7 +205,7 @@ is the field's type. Add the new field to the `property_types` dictionary. ```diff - # zerver/models/__init__.py + # zerver/models/realms.py class Realm(models.Model) # ... diff --git a/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time b/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time index bcf3842ef9..7ff669e565 100755 --- a/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time +++ b/puppet/zulip/files/nagios_plugins/zulip_app_frontend/check_send_receive_time @@ -66,7 +66,7 @@ django.setup() from django.conf import settings -from zerver.models import get_realm +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot states = { diff --git a/tools/generate-integration-docs-screenshot b/tools/generate-integration-docs-screenshot index e00d5fb30e..1f471961b1 100755 --- a/tools/generate-integration-docs-screenshot +++ b/tools/generate-integration-docs-screenshot @@ -50,7 +50,8 @@ from zerver.lib.storage import static_path from zerver.lib.streams import create_stream_if_needed from zerver.lib.upload import upload_avatar_image from zerver.lib.webhooks.common import get_fixture_http_headers -from zerver.models import Message, UserProfile, get_realm +from zerver.models import Message, UserProfile +from zerver.models.realms import get_realm from zerver.models.users import get_user_by_delivery_email diff --git a/tools/semgrep-py.yml b/tools/semgrep-py.yml index a83ac442f8..dad2e7bf91 100644 --- a/tools/semgrep-py.yml +++ b/tools/semgrep-py.yml @@ -50,7 +50,7 @@ rules: - pattern-not: from zerver.models import filter_pattern_validator - pattern-not: from zerver.models import url_template_validator - pattern-not: from zerver.models import generate_email_token_for_stream - - pattern-not: from zerver.models import generate_realm_uuid_owner_secret + - pattern-not: from zerver.models.realms import generate_realm_uuid_owner_secret - pattern-either: - pattern: from zerver import $X - pattern: from analytics import $X diff --git a/tools/test-api b/tools/test-api index 8a371641e9..0eea5f7446 100755 --- a/tools/test-api +++ b/tools/test-api @@ -36,7 +36,7 @@ with test_server_running( from zerver.actions.users import change_user_is_active from zerver.lib.test_helpers import reset_email_visibility_to_everyone_in_zulip_realm from zerver.lib.users import get_api_key - from zerver.models import get_realm + from zerver.models.realms import get_realm from zerver.models.users import get_user from zerver.openapi.javascript_examples import test_js_bindings from zerver.openapi.python_examples import ( diff --git a/zerver/actions/create_realm.py b/zerver/actions/create_realm.py index a87b851bd3..ca38c889a6 100644 --- a/zerver/actions/create_realm.py +++ b/zerver/actions/create_realm.py @@ -31,9 +31,8 @@ from zerver.models import ( RealmUserDefault, Stream, UserProfile, - get_org_type_display_name, - get_realm, ) +from zerver.models.realms import get_org_type_display_name, get_realm from zerver.models.users import get_system_bot from zproject.backends import all_implemented_backend_names diff --git a/zerver/actions/realm_domains.py b/zerver/actions/realm_domains.py index ed03e9fd0b..1ea401beae 100644 --- a/zerver/actions/realm_domains.py +++ b/zerver/actions/realm_domains.py @@ -4,14 +4,8 @@ from django.db import transaction from django.utils.timezone import now as timezone_now from zerver.actions.realm_settings import do_set_realm_property -from zerver.models import ( - Realm, - RealmAuditLog, - RealmDomain, - RealmDomainDict, - UserProfile, - get_realm_domains, -) +from zerver.models import Realm, RealmAuditLog, RealmDomain, UserProfile +from zerver.models.realms import RealmDomainDict, get_realm_domains from zerver.models.users import active_user_ids from zerver.tornado.django_api import send_event_on_commit diff --git a/zerver/actions/realm_settings.py b/zerver/actions/realm_settings.py index 4817bfae14..1005bda63c 100644 --- a/zerver/actions/realm_settings.py +++ b/zerver/actions/realm_settings.py @@ -33,9 +33,9 @@ from zerver.models import ( Subscription, UserGroup, UserProfile, - get_realm, ) from zerver.models.groups import SystemGroups +from zerver.models.realms import get_realm from zerver.models.users import active_user_ids from zerver.tornado.django_api import send_event, send_event_on_commit diff --git a/zerver/actions/users.py b/zerver/actions/users.py index bda9c80aa9..8badc9f7b4 100644 --- a/zerver/actions/users.py +++ b/zerver/actions/users.py @@ -42,8 +42,8 @@ from zerver.models import ( UserGroupMembership, UserProfile, get_bot_services, - get_fake_email_domain, ) +from zerver.models.realms import get_fake_email_domain from zerver.models.users import ( active_non_guest_user_ids, active_user_ids, diff --git a/zerver/context_processors.py b/zerver/context_processors.py index 9ecf9fcf7f..b7d8df6565 100644 --- a/zerver/context_processors.py +++ b/zerver/context_processors.py @@ -20,7 +20,8 @@ from zerver.lib.realm_icon import get_realm_icon_url from zerver.lib.request import RequestNotes from zerver.lib.send_email import FromAddress from zerver.lib.subdomains import get_subdomain, is_root_domain_available -from zerver.models import Realm, UserProfile, get_realm +from zerver.models import Realm, UserProfile +from zerver.models.realms import get_realm from zproject.backends import ( AUTH_BACKEND_NAME_MAP, auth_enabled_helper, diff --git a/zerver/forms.py b/zerver/forms.py index ac53d952ef..fdf253787b 100644 --- a/zerver/forms.py +++ b/zerver/forms.py @@ -35,12 +35,11 @@ from zerver.lib.send_email import FromAddress, send_email from zerver.lib.soft_deactivation import queue_soft_reactivation from zerver.lib.subdomains import get_subdomain, is_root_domain_available from zerver.lib.users import check_full_name -from zerver.models import ( +from zerver.models import Realm, UserProfile +from zerver.models.realms import ( DisposableEmailError, DomainNotAllowedForRealmError, EmailContainsPlusError, - Realm, - UserProfile, get_realm, ) from zerver.models.users import get_user_by_delivery_email, is_cross_realm_bot_email diff --git a/zerver/lib/cache.py b/zerver/lib/cache.py index 6725ca9504..4f74a1a874 100644 --- a/zerver/lib/cache.py +++ b/zerver/lib/cache.py @@ -591,7 +591,7 @@ def flush_muting_users_cache(*, instance: "MutedUser", **kwargs: object) -> None cache_delete(get_muting_users_cache_key(mute_object.muted_user_id)) -# Called by models/__init__.py to flush various caches whenever we save +# Called by models/realms.py to flush various caches whenever we save # a Realm object. The main tricky thing here is that Realm info is # generally cached indirectly through user_profile objects. def flush_realm( diff --git a/zerver/lib/create_user.py b/zerver/lib/create_user.py index b5a969df62..cc4776dbd1 100644 --- a/zerver/lib/create_user.py +++ b/zerver/lib/create_user.py @@ -18,8 +18,8 @@ from zerver.models import ( Subscription, UserBaseSettings, UserProfile, - get_fake_email_domain, ) +from zerver.models.realms import get_fake_email_domain def copy_default_settings( diff --git a/zerver/lib/email_validation.py b/zerver/lib/email_validation.py index be7416cdcf..b8d418863c 100644 --- a/zerver/lib/email_validation.py +++ b/zerver/lib/email_validation.py @@ -8,12 +8,11 @@ from django.utils.translation import gettext as _ from zerver.lib.name_restrictions import is_disposable_domain # TODO: Move DisposableEmailError, etc. into here. -from zerver.models import ( +from zerver.models import Realm, RealmDomain +from zerver.models.realms import ( DisposableEmailError, DomainNotAllowedForRealmError, EmailContainsPlusError, - Realm, - RealmDomain, ) from zerver.models.users import get_users_by_delivery_email, is_cross_realm_bot_email diff --git a/zerver/lib/events.py b/zerver/lib/events.py index bd50e91064..9c60f83f88 100644 --- a/zerver/lib/events.py +++ b/zerver/lib/events.py @@ -82,11 +82,11 @@ from zerver.models import ( custom_profile_fields_for_realm, get_all_custom_emoji_for_realm, get_default_stream_groups, - get_realm_domains, get_realm_playgrounds, linkifiers_for_realm, ) from zerver.models.constants import MAX_TOPIC_NAME_LENGTH +from zerver.models.realms import get_realm_domains from zerver.tornado.django_api import get_user_events, request_event_queue from zproject.backends import email_auth_enabled, password_auth_enabled diff --git a/zerver/lib/export.py b/zerver/lib/export.py index 46dffacf27..136e582f86 100644 --- a/zerver/lib/export.py +++ b/zerver/lib/export.py @@ -69,8 +69,8 @@ from zerver.models import ( UserProfile, UserStatus, UserTopic, - get_realm, ) +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot, get_user_profile_by_id # Custom mypy types follow: diff --git a/zerver/lib/import_realm.py b/zerver/lib/import_realm.py index c84299ae20..6347214856 100644 --- a/zerver/lib/import_realm.py +++ b/zerver/lib/import_realm.py @@ -77,9 +77,9 @@ from zerver.models import ( UserStatus, UserTopic, get_huddle_hash, - get_realm, ) from zerver.models.groups import SystemGroups +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot, get_user_profile_by_id realm_tables = [ diff --git a/zerver/lib/message.py b/zerver/lib/message.py index 322c17d05c..f66fed5c66 100644 --- a/zerver/lib/message.py +++ b/zerver/lib/message.py @@ -75,11 +75,11 @@ from zerver.models import ( UserProfile, UserTopic, get_display_recipient_by_id, - get_fake_email_domain, get_usermessage_by_message_id, query_for_ids, ) from zerver.models.constants import MAX_TOPIC_NAME_LENGTH +from zerver.models.realms import get_fake_email_domain class MessageDetailsDict(TypedDict, total=False): diff --git a/zerver/lib/push_notifications.py b/zerver/lib/push_notifications.py index dd66779fb7..3a51ff762a 100644 --- a/zerver/lib/push_notifications.py +++ b/zerver/lib/push_notifications.py @@ -65,8 +65,8 @@ from zerver.models import ( UserMessage, UserProfile, get_display_recipient, - get_fake_email_domain, ) +from zerver.models.realms import get_fake_email_domain from zerver.models.users import get_user_profile_by_id if TYPE_CHECKING: diff --git a/zerver/lib/remote_server.py b/zerver/lib/remote_server.py index 1dc0fc6c3c..3d92b01493 100644 --- a/zerver/lib/remote_server.py +++ b/zerver/lib/remote_server.py @@ -22,7 +22,8 @@ from zerver.lib.exceptions import ( ) from zerver.lib.outgoing_http import OutgoingSession from zerver.lib.queue import queue_event_on_commit -from zerver.models import OrgTypeEnum, Realm, RealmAuditLog +from zerver.models import Realm, RealmAuditLog +from zerver.models.realms import OrgTypeEnum class PushBouncerSession(OutgoingSession): diff --git a/zerver/lib/scim.py b/zerver/lib/scim.py index ceb0d80b48..24971c1bc8 100644 --- a/zerver/lib/scim.py +++ b/zerver/lib/scim.py @@ -15,11 +15,11 @@ from zerver.actions.users import do_change_user_role, do_deactivate_user from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm from zerver.lib.request import RequestNotes from zerver.lib.subdomains import get_subdomain -from zerver.models import ( +from zerver.models import UserProfile +from zerver.models.realms import ( DisposableEmailError, DomainNotAllowedForRealmError, EmailContainsPlusError, - UserProfile, ) diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index 7399a6db11..dc767291ef 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -107,12 +107,11 @@ from zerver.models import ( UserMessage, UserProfile, UserStatus, - clear_supported_auth_backends_cache, - get_realm, get_realm_stream, get_stream, ) from zerver.models.groups import SystemGroups +from zerver.models.realms import clear_supported_auth_backends_cache, get_realm from zerver.models.users import get_system_bot, get_user, get_user_by_delivery_email from zerver.openapi.openapi import validate_against_openapi_schema, validate_request from zerver.tornado.event_queue import clear_client_event_queues_for_testing diff --git a/zerver/lib/test_helpers.py b/zerver/lib/test_helpers.py index e31576932d..ef5021e712 100644 --- a/zerver/lib/test_helpers.py +++ b/zerver/lib/test_helpers.py @@ -60,9 +60,9 @@ from zerver.models import ( UserMessage, UserProfile, get_client, - get_realm, get_stream, ) +from zerver.models.realms import get_realm from zerver.tornado.handlers import AsyncDjangoHandler, allocate_handler_id from zilencer.models import RemoteZulipServer from zproject.backends import ExternalAuthDataDict, ExternalAuthResult diff --git a/zerver/lib/users.py b/zerver/lib/users.py index 6a26218300..b693d6502c 100644 --- a/zerver/lib/users.py +++ b/zerver/lib/users.py @@ -36,9 +36,9 @@ from zerver.models import ( Subscription, UserMessage, UserProfile, - get_fake_email_domain, ) from zerver.models.groups import SystemGroups +from zerver.models.realms import get_fake_email_domain from zerver.models.users import ( active_non_guest_user_ids, active_user_ids, diff --git a/zerver/management/commands/realm_domain.py b/zerver/management/commands/realm_domain.py index f8c70956e8..e33ab28711 100644 --- a/zerver/management/commands/realm_domain.py +++ b/zerver/management/commands/realm_domain.py @@ -9,7 +9,8 @@ from typing_extensions import override from zerver.lib.domains import validate_domain from zerver.lib.management import ZulipBaseCommand -from zerver.models import RealmDomain, get_realm_domains +from zerver.models import RealmDomain +from zerver.models.realms import get_realm_domains class Command(ZulipBaseCommand): diff --git a/zerver/management/commands/send_to_email_mirror.py b/zerver/management/commands/send_to_email_mirror.py index 8674f07ce6..02c65368de 100644 --- a/zerver/management/commands/send_to_email_mirror.py +++ b/zerver/management/commands/send_to_email_mirror.py @@ -13,7 +13,8 @@ from typing_extensions import override from zerver.lib.email_mirror import mirror_email_message from zerver.lib.email_mirror_helpers import encode_email_address from zerver.lib.management import ZulipBaseCommand -from zerver.models import Realm, get_realm, get_stream +from zerver.models import Realm, get_stream +from zerver.models.realms import get_realm # This command loads an email from a specified file and sends it # to the email mirror. Simple emails can be passed in a JSON file, diff --git a/zerver/management/commands/send_webhook_fixture_message.py b/zerver/management/commands/send_webhook_fixture_message.py index d88f04055f..3e6456d034 100644 --- a/zerver/management/commands/send_webhook_fixture_message.py +++ b/zerver/management/commands/send_webhook_fixture_message.py @@ -9,7 +9,7 @@ from typing_extensions import override from zerver.lib.management import ZulipBaseCommand from zerver.lib.webhooks.common import standardize_headers -from zerver.models import get_realm +from zerver.models.realms import get_realm class Command(ZulipBaseCommand): diff --git a/zerver/middleware.py b/zerver/middleware.py index cd1b3a9b29..5e4c7ecc78 100644 --- a/zerver/middleware.py +++ b/zerver/middleware.py @@ -42,7 +42,8 @@ from zerver.lib.response import ( ) from zerver.lib.subdomains import get_subdomain from zerver.lib.user_agent import parse_user_agent -from zerver.models import Realm, get_realm +from zerver.models import Realm +from zerver.models.realms import get_realm ParamT = ParamSpec("ParamT") logger = logging.getLogger("zulip.requests") diff --git a/zerver/migrations/0481_alter_realm_uuid_alter_realm_uuid_owner_secret.py b/zerver/migrations/0481_alter_realm_uuid_alter_realm_uuid_owner_secret.py index c30da3f7db..bacf183d63 100644 --- a/zerver/migrations/0481_alter_realm_uuid_alter_realm_uuid_owner_secret.py +++ b/zerver/migrations/0481_alter_realm_uuid_alter_realm_uuid_owner_secret.py @@ -4,7 +4,7 @@ import uuid from django.db import migrations, models -from zerver.models import generate_realm_uuid_owner_secret +from zerver.models.realms import generate_realm_uuid_owner_secret class Migration(migrations.Migration): diff --git a/zerver/models/__init__.py b/zerver/models/__init__.py index 3d6497c230..69e2757bec 100644 --- a/zerver/models/__init__.py +++ b/zerver/models/__init__.py @@ -6,10 +6,7 @@ import secrets import time from collections import defaultdict from datetime import datetime, timedelta, timezone -from email.headerregistry import Address -from enum import Enum from typing import ( - TYPE_CHECKING, Any, Callable, Dict, @@ -22,9 +19,7 @@ from typing import ( TypeVar, Union, ) -from uuid import uuid4 -import django.contrib.auth import orjson import re2 import uri_template @@ -37,12 +32,12 @@ from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.search import SearchVectorField from django.core.exceptions import ValidationError from django.core.serializers.json import DjangoJSONEncoder -from django.core.validators import MinLengthValidator, RegexValidator, validate_email +from django.core.validators import MinLengthValidator, RegexValidator from django.db import models, transaction from django.db.backends.base.base import BaseDatabaseWrapper -from django.db.models import CASCADE, Exists, F, OuterRef, Q, QuerySet, Sum +from django.db.models import CASCADE, Exists, F, OuterRef, Q, QuerySet from django.db.models.functions import Lower, Upper -from django.db.models.signals import post_delete, post_save, pre_delete +from django.db.models.signals import post_delete, post_save from django.db.models.sql.compiler import SQLCompiler from django.utils.timezone import now as timezone_now from django.utils.translation import gettext as _ @@ -58,20 +53,17 @@ from zerver.lib.cache import ( cache_with_key, flush_message, flush_muting_users_cache, - flush_realm, flush_stream, flush_submessage, flush_used_upload_space_cache, - get_realm_used_upload_space_cache_key, realm_alert_words_automaton_cache_key, realm_alert_words_cache_key, ) -from zerver.lib.exceptions import JsonableError, RateLimitedError +from zerver.lib.exceptions import RateLimitedError from zerver.lib.per_request_cache import ( flush_per_request_cache, return_same_value_during_entire_request, ) -from zerver.lib.pysa import mark_sanitized from zerver.lib.timestamp import datetime_to_timestamp from zerver.lib.types import ( DefaultStreamDict, @@ -89,7 +81,6 @@ from zerver.lib.types import ( UserFieldElement, Validator, ) -from zerver.lib.utils import generate_api_key from zerver.lib.validator import ( check_date, check_int, @@ -104,17 +95,14 @@ from zerver.models.groups import GroupGroupMembership as GroupGroupMembership from zerver.models.groups import SystemGroups from zerver.models.groups import UserGroup as UserGroup from zerver.models.groups import UserGroupMembership as UserGroupMembership +from zerver.models.realms import Realm as Realm +from zerver.models.realms import RealmAuthenticationMethod as RealmAuthenticationMethod +from zerver.models.realms import RealmDomain as RealmDomain from zerver.models.users import RealmUserDefault as RealmUserDefault from zerver.models.users import UserBaseSettings as UserBaseSettings from zerver.models.users import UserProfile as UserProfile from zerver.models.users import get_user_profile_by_id_in_realm -SECONDS_PER_DAY = 86400 - -if TYPE_CHECKING: - # We use ModelBackend only for typing. Importing it otherwise causes circular dependency. - from django.contrib.auth.backends import ModelBackend - class EmojiInfo(TypedDict): id: str @@ -227,967 +215,6 @@ def get_all_custom_emoji_for_realm_cache_key(realm_id: int) -> str: return f"realm_emoji:{realm_id}" -# This simple call-once caching saves ~500us in auth_enabled_helper, -# which is a significant optimization for common_context. Note that -# these values cannot change in a running production system, but do -# regularly change within unit tests; we address the latter by calling -# clear_supported_auth_backends_cache in our standard tearDown code. -supported_backends: Optional[List["ModelBackend"]] = None - - -def supported_auth_backends() -> List["ModelBackend"]: - global supported_backends - # Caching temporarily disabled for debugging - supported_backends = django.contrib.auth.get_backends() - assert supported_backends is not None - return supported_backends - - -def clear_supported_auth_backends_cache() -> None: - global supported_backends - supported_backends = None - - -class RealmAuthenticationMethod(models.Model): - """ - Tracks which authentication backends are enabled for a realm. - An enabled backend is represented in this table a row with appropriate - .realm value and .name matching the name of the target backend in the - AUTH_BACKEND_NAME_MAP dict. - """ - - realm = models.ForeignKey("Realm", on_delete=CASCADE, db_index=True) - name = models.CharField(max_length=80) - - class Meta: - unique_together = ("realm", "name") - - -def generate_realm_uuid_owner_secret() -> str: - token = generate_api_key() - - # We include a prefix to facilitate scanning for accidental - # disclosure of secrets e.g. in Github commit pushes. - return f"zuliprealm_{token}" - - -class OrgTypeEnum(Enum): - Unspecified = 0 - Business = 10 - OpenSource = 20 - EducationNonProfit = 30 - Education = 35 - Research = 40 - Event = 50 - NonProfit = 60 - Government = 70 - PoliticalGroup = 80 - Community = 90 - Personal = 100 - Other = 1000 - - -class OrgTypeDict(TypedDict): - name: str - id: int - hidden: bool - display_order: int - onboarding_zulip_guide_url: Optional[str] - - -class Realm(models.Model): # type: ignore[django-manager-missing] # django-stubs cannot resolve the custom CTEManager yet https://github.com/typeddjango/django-stubs/issues/1023 - MAX_REALM_NAME_LENGTH = 40 - MAX_REALM_DESCRIPTION_LENGTH = 1000 - MAX_REALM_SUBDOMAIN_LENGTH = 40 - MAX_REALM_REDIRECT_URL_LENGTH = 128 - - INVITES_STANDARD_REALM_DAILY_MAX = 3000 - MESSAGE_VISIBILITY_LIMITED = 10000 - SUBDOMAIN_FOR_ROOT_DOMAIN = "" - WILDCARD_MENTION_THRESHOLD = 15 - - # User-visible display name and description used on e.g. the organization homepage - name = models.CharField(max_length=MAX_REALM_NAME_LENGTH) - description = models.TextField(default="") - - # A short, identifier-like name for the organization. Used in subdomains; - # e.g. on a server at example.com, an org with string_id `foo` is reached - # at `foo.example.com`. - string_id = models.CharField(max_length=MAX_REALM_SUBDOMAIN_LENGTH, unique=True) - - # uuid and a secret for the sake of per-realm authentication with the push notification - # bouncer. - uuid = models.UUIDField(default=uuid4, unique=True) - uuid_owner_secret = models.TextField(default=generate_realm_uuid_owner_secret) - # Whether push notifications are working for this realm, and - # whether there is a specific date at which we expect that to - # cease to be the case. - push_notifications_enabled = models.BooleanField(default=False, db_index=True) - push_notifications_enabled_end_timestamp = models.DateTimeField(default=None, null=True) - - date_created = models.DateTimeField(default=timezone_now) - demo_organization_scheduled_deletion_date = models.DateTimeField(default=None, null=True) - deactivated = models.BooleanField(default=False) - - # Redirect URL if the Realm has moved to another server - deactivated_redirect = models.URLField(max_length=MAX_REALM_REDIRECT_URL_LENGTH, null=True) - - # See RealmDomain for the domains that apply for a given organization. - emails_restricted_to_domains = models.BooleanField(default=False) - - invite_required = models.BooleanField(default=True) - - _max_invites = models.IntegerField(null=True, db_column="max_invites") - disallow_disposable_email_addresses = models.BooleanField(default=True) - - # Allow users to access web-public streams without login. This - # setting also controls API access of web-public streams. - enable_spectator_access = models.BooleanField(default=False) - - # Whether organization has given permission to be advertised in the - # Zulip communities directory. - want_advertise_in_communities_directory = models.BooleanField(default=False, db_index=True) - - # Whether the organization has enabled inline image and URL previews. - inline_image_preview = models.BooleanField(default=True) - inline_url_embed_preview = models.BooleanField(default=False) - - # Whether digest emails are enabled for the organization. - digest_emails_enabled = models.BooleanField(default=False) - # Day of the week on which the digest is sent (default: Tuesday). - digest_weekday = models.SmallIntegerField(default=1) - - send_welcome_emails = models.BooleanField(default=True) - message_content_allowed_in_email_notifications = models.BooleanField(default=True) - - mandatory_topics = models.BooleanField(default=False) - - name_changes_disabled = models.BooleanField(default=False) - email_changes_disabled = models.BooleanField(default=False) - avatar_changes_disabled = models.BooleanField(default=False) - - POLICY_MEMBERS_ONLY = 1 - POLICY_ADMINS_ONLY = 2 - POLICY_FULL_MEMBERS_ONLY = 3 - POLICY_MODERATORS_ONLY = 4 - POLICY_EVERYONE = 5 - POLICY_NOBODY = 6 - POLICY_OWNERS_ONLY = 7 - - COMMON_POLICY_TYPES = [ - POLICY_MEMBERS_ONLY, - POLICY_ADMINS_ONLY, - POLICY_FULL_MEMBERS_ONLY, - POLICY_MODERATORS_ONLY, - ] - - COMMON_MESSAGE_POLICY_TYPES = [ - POLICY_MEMBERS_ONLY, - POLICY_ADMINS_ONLY, - POLICY_FULL_MEMBERS_ONLY, - POLICY_MODERATORS_ONLY, - POLICY_EVERYONE, - ] - - INVITE_TO_REALM_POLICY_TYPES = [ - POLICY_MEMBERS_ONLY, - POLICY_ADMINS_ONLY, - POLICY_FULL_MEMBERS_ONLY, - POLICY_MODERATORS_ONLY, - POLICY_NOBODY, - ] - - # We don't allow granting roles less than Moderator access to - # create web-public streams, since it's a sensitive feature that - # can be used to send spam. - CREATE_WEB_PUBLIC_STREAM_POLICY_TYPES = [ - POLICY_ADMINS_ONLY, - POLICY_MODERATORS_ONLY, - POLICY_OWNERS_ONLY, - POLICY_NOBODY, - ] - - EDIT_TOPIC_POLICY_TYPES = [ - POLICY_MEMBERS_ONLY, - POLICY_ADMINS_ONLY, - POLICY_FULL_MEMBERS_ONLY, - POLICY_MODERATORS_ONLY, - POLICY_EVERYONE, - POLICY_NOBODY, - ] - - MOVE_MESSAGES_BETWEEN_STREAMS_POLICY_TYPES = INVITE_TO_REALM_POLICY_TYPES - - DEFAULT_MOVE_MESSAGE_LIMIT_SECONDS = 7 * SECONDS_PER_DAY - - move_messages_within_stream_limit_seconds = models.PositiveIntegerField( - default=DEFAULT_MOVE_MESSAGE_LIMIT_SECONDS, null=True - ) - - move_messages_between_streams_limit_seconds = models.PositiveIntegerField( - default=DEFAULT_MOVE_MESSAGE_LIMIT_SECONDS, null=True - ) - - # Who in the organization is allowed to add custom emojis. - add_custom_emoji_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) - - # Who in the organization is allowed to create streams. - create_public_stream_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) - create_private_stream_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) - create_web_public_stream_policy = models.PositiveSmallIntegerField(default=POLICY_OWNERS_ONLY) - - # Who in the organization is allowed to delete messages they themselves sent. - delete_own_message_policy = models.PositiveSmallIntegerField(default=POLICY_ADMINS_ONLY) - - # Who in the organization is allowed to edit topics of any message. - edit_topic_policy = models.PositiveSmallIntegerField(default=POLICY_EVERYONE) - - # Who in the organization is allowed to invite other users to organization. - invite_to_realm_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) - - # UserGroup whose members are allowed to create invite link. - create_multiuse_invite_group = models.ForeignKey( - "UserGroup", on_delete=models.RESTRICT, related_name="+" - ) - - # on_delete field here is set to RESTRICT because we don't want to allow - # deleting a user group in case it is referenced by this setting. - # We are not using PROTECT since we want to allow deletion of user groups - # when realm itself is deleted. - can_access_all_users_group = models.ForeignKey( - "UserGroup", on_delete=models.RESTRICT, related_name="+" - ) - - # Who in the organization is allowed to invite other users to streams. - invite_to_stream_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) - - # Who in the organization is allowed to move messages between streams. - move_messages_between_streams_policy = models.PositiveSmallIntegerField( - default=POLICY_ADMINS_ONLY - ) - - user_group_edit_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) - - PRIVATE_MESSAGE_POLICY_UNLIMITED = 1 - PRIVATE_MESSAGE_POLICY_DISABLED = 2 - private_message_policy = models.PositiveSmallIntegerField( - default=PRIVATE_MESSAGE_POLICY_UNLIMITED - ) - PRIVATE_MESSAGE_POLICY_TYPES = [ - PRIVATE_MESSAGE_POLICY_UNLIMITED, - PRIVATE_MESSAGE_POLICY_DISABLED, - ] - - # Global policy for who is allowed to use wildcard mentions in - # streams with a large number of subscribers. Anyone can use - # wildcard mentions in small streams regardless of this setting. - WILDCARD_MENTION_POLICY_EVERYONE = 1 - WILDCARD_MENTION_POLICY_MEMBERS = 2 - WILDCARD_MENTION_POLICY_FULL_MEMBERS = 3 - WILDCARD_MENTION_POLICY_ADMINS = 5 - WILDCARD_MENTION_POLICY_NOBODY = 6 - WILDCARD_MENTION_POLICY_MODERATORS = 7 - wildcard_mention_policy = models.PositiveSmallIntegerField( - default=WILDCARD_MENTION_POLICY_ADMINS, - ) - WILDCARD_MENTION_POLICY_TYPES = [ - WILDCARD_MENTION_POLICY_EVERYONE, - WILDCARD_MENTION_POLICY_MEMBERS, - WILDCARD_MENTION_POLICY_FULL_MEMBERS, - WILDCARD_MENTION_POLICY_ADMINS, - WILDCARD_MENTION_POLICY_NOBODY, - WILDCARD_MENTION_POLICY_MODERATORS, - ] - - # Threshold in days for new users to create streams, and potentially take - # some other actions. - waiting_period_threshold = models.PositiveIntegerField(default=0) - - DEFAULT_MESSAGE_CONTENT_DELETE_LIMIT_SECONDS = ( - 600 # if changed, also change in admin.js, setting_org.js - ) - MESSAGE_TIME_LIMIT_SETTING_SPECIAL_VALUES_MAP = { - "unlimited": None, - } - message_content_delete_limit_seconds = models.PositiveIntegerField( - default=DEFAULT_MESSAGE_CONTENT_DELETE_LIMIT_SECONDS, null=True - ) - - allow_message_editing = models.BooleanField(default=True) - DEFAULT_MESSAGE_CONTENT_EDIT_LIMIT_SECONDS = ( - 600 # if changed, also change in admin.js, setting_org.js - ) - message_content_edit_limit_seconds = models.PositiveIntegerField( - default=DEFAULT_MESSAGE_CONTENT_EDIT_LIMIT_SECONDS, null=True - ) - - # Whether users have access to message edit history - allow_edit_history = models.BooleanField(default=True) - - # Defaults for new users - default_language = models.CharField(default="en", max_length=MAX_LANGUAGE_ID_LENGTH) - - DEFAULT_NOTIFICATION_STREAM_NAME = "general" - INITIAL_PRIVATE_STREAM_NAME = "core team" - STREAM_EVENTS_NOTIFICATION_TOPIC = gettext_lazy("stream events") - notifications_stream = models.ForeignKey( - "Stream", - related_name="+", - null=True, - blank=True, - on_delete=models.SET_NULL, - ) - signup_notifications_stream = models.ForeignKey( - "Stream", - related_name="+", - null=True, - blank=True, - on_delete=models.SET_NULL, - ) - - MESSAGE_RETENTION_SPECIAL_VALUES_MAP = { - "unlimited": -1, - } - # For old messages being automatically deleted - message_retention_days = models.IntegerField(null=False, default=-1) - - # When non-null, all but the latest this many messages in the organization - # are inaccessible to users (but not deleted). - message_visibility_limit = models.IntegerField(null=True) - - # Messages older than this message ID in the organization are inaccessible. - first_visible_message_id = models.IntegerField(default=0) - - # Valid org types - ORG_TYPES: Dict[str, OrgTypeDict] = { - "unspecified": { - "name": "Unspecified", - "id": OrgTypeEnum.Unspecified.value, - "hidden": True, - "display_order": 0, - "onboarding_zulip_guide_url": None, - }, - "business": { - "name": "Business", - "id": OrgTypeEnum.Business.value, - "hidden": False, - "display_order": 1, - "onboarding_zulip_guide_url": "https://zulip.com/for/business/", - }, - "opensource": { - "name": "Open-source project", - "id": OrgTypeEnum.OpenSource.value, - "hidden": False, - "display_order": 2, - "onboarding_zulip_guide_url": "https://zulip.com/for/open-source/", - }, - "education_nonprofit": { - "name": "Education (non-profit)", - "id": OrgTypeEnum.EducationNonProfit.value, - "hidden": False, - "display_order": 3, - "onboarding_zulip_guide_url": "https://zulip.com/for/education/", - }, - "education": { - "name": "Education (for-profit)", - "id": OrgTypeEnum.Education.value, - "hidden": False, - "display_order": 4, - "onboarding_zulip_guide_url": "https://zulip.com/for/education/", - }, - "research": { - "name": "Research", - "id": OrgTypeEnum.Research.value, - "hidden": False, - "display_order": 5, - "onboarding_zulip_guide_url": "https://zulip.com/for/research/", - }, - "event": { - "name": "Event or conference", - "id": OrgTypeEnum.Event.value, - "hidden": False, - "display_order": 6, - "onboarding_zulip_guide_url": "https://zulip.com/for/events/", - }, - "nonprofit": { - "name": "Non-profit (registered)", - "id": OrgTypeEnum.NonProfit.value, - "hidden": False, - "display_order": 7, - "onboarding_zulip_guide_url": "https://zulip.com/for/communities/", - }, - "government": { - "name": "Government", - "id": OrgTypeEnum.Government.value, - "hidden": False, - "display_order": 8, - "onboarding_zulip_guide_url": None, - }, - "political_group": { - "name": "Political group", - "id": OrgTypeEnum.PoliticalGroup.value, - "hidden": False, - "display_order": 9, - "onboarding_zulip_guide_url": None, - }, - "community": { - "name": "Community", - "id": OrgTypeEnum.Community.value, - "hidden": False, - "display_order": 10, - "onboarding_zulip_guide_url": "https://zulip.com/for/communities/", - }, - "personal": { - "name": "Personal", - "id": OrgTypeEnum.Personal.value, - "hidden": False, - "display_order": 100, - "onboarding_zulip_guide_url": None, - }, - "other": { - "name": "Other", - "id": OrgTypeEnum.Other.value, - "hidden": False, - "display_order": 1000, - "onboarding_zulip_guide_url": None, - }, - } - - ORG_TYPE_IDS: List[int] = [t["id"] for t in ORG_TYPES.values()] - - org_type = models.PositiveSmallIntegerField( - default=ORG_TYPES["unspecified"]["id"], - choices=[(t["id"], t["name"]) for t in ORG_TYPES.values()], - ) - - UPGRADE_TEXT_STANDARD = gettext_lazy("Available on Zulip Cloud Standard. Upgrade to access.") - UPGRADE_TEXT_PLUS = gettext_lazy("Available on Zulip Cloud Plus. Upgrade to access.") - # plan_type controls various features around resource/feature - # limitations for a Zulip organization on multi-tenant installations - # like Zulip Cloud. - PLAN_TYPE_SELF_HOSTED = 1 - PLAN_TYPE_LIMITED = 2 - PLAN_TYPE_STANDARD = 3 - PLAN_TYPE_STANDARD_FREE = 4 - PLAN_TYPE_PLUS = 10 - - # Used for creating realms with different plan types. - ALL_PLAN_TYPES = { - PLAN_TYPE_SELF_HOSTED: "self-hosted-plan", - PLAN_TYPE_LIMITED: "limited-plan", - PLAN_TYPE_STANDARD: "standard-plan", - PLAN_TYPE_STANDARD_FREE: "standard-free-plan", - PLAN_TYPE_PLUS: "plus-plan", - } - plan_type = models.PositiveSmallIntegerField(default=PLAN_TYPE_SELF_HOSTED) - - # This value is also being used in web/src/settings_bots.bot_creation_policy_values. - # On updating it here, update it there as well. - BOT_CREATION_EVERYONE = 1 - BOT_CREATION_LIMIT_GENERIC_BOTS = 2 - BOT_CREATION_ADMINS_ONLY = 3 - bot_creation_policy = models.PositiveSmallIntegerField(default=BOT_CREATION_EVERYONE) - BOT_CREATION_POLICY_TYPES = [ - BOT_CREATION_EVERYONE, - BOT_CREATION_LIMIT_GENERIC_BOTS, - BOT_CREATION_ADMINS_ONLY, - ] - - # See upload_quota_bytes; don't interpret upload_quota_gb directly. - UPLOAD_QUOTA_LIMITED = 5 - UPLOAD_QUOTA_STANDARD = 50 - upload_quota_gb = models.IntegerField(null=True) - - VIDEO_CHAT_PROVIDERS = { - "disabled": { - "name": "None", - "id": 0, - }, - "jitsi_meet": { - "name": "Jitsi Meet", - "id": 1, - }, - # ID 2 was used for the now-deleted Google Hangouts. - # ID 3 reserved for optional Zoom, see below. - # ID 4 reserved for optional BigBlueButton, see below. - } - - if settings.VIDEO_ZOOM_CLIENT_ID is not None and settings.VIDEO_ZOOM_CLIENT_SECRET is not None: - VIDEO_CHAT_PROVIDERS["zoom"] = { - "name": "Zoom", - "id": 3, - } - - if settings.BIG_BLUE_BUTTON_SECRET is not None and settings.BIG_BLUE_BUTTON_URL is not None: - VIDEO_CHAT_PROVIDERS["big_blue_button"] = {"name": "BigBlueButton", "id": 4} - - video_chat_provider = models.PositiveSmallIntegerField( - default=VIDEO_CHAT_PROVIDERS["jitsi_meet"]["id"] - ) - - JITSI_SERVER_SPECIAL_VALUES_MAP = {"default": None} - jitsi_server_url = models.URLField(null=True, default=None) - - # Please access this via get_giphy_rating_options. - GIPHY_RATING_OPTIONS = { - "disabled": { - "name": gettext_lazy("GIPHY integration disabled"), - "id": 0, - }, - # Source: https://github.com/Giphy/giphy-js/blob/master/packages/fetch-api/README.md#shared-options - "y": { - "name": gettext_lazy("Allow GIFs rated Y (Very young audience)"), - "id": 1, - }, - "g": { - "name": gettext_lazy("Allow GIFs rated G (General audience)"), - "id": 2, - }, - "pg": { - "name": gettext_lazy("Allow GIFs rated PG (Parental guidance)"), - "id": 3, - }, - "pg-13": { - "name": gettext_lazy("Allow GIFs rated PG-13 (Parental guidance - under 13)"), - "id": 4, - }, - "r": { - "name": gettext_lazy("Allow GIFs rated R (Restricted)"), - "id": 5, - }, - } - - # maximum rating of the GIFs that will be retrieved from GIPHY - giphy_rating = models.PositiveSmallIntegerField(default=GIPHY_RATING_OPTIONS["g"]["id"]) - - default_code_block_language = models.TextField(default="") - - # Whether read receipts are enabled in the organization. If disabled, - # they will not be available regardless of users' personal settings. - enable_read_receipts = models.BooleanField(default=False) - - # Whether clients should display "(guest)" after names of guest users. - enable_guest_user_indicator = models.BooleanField(default=True) - - # Define the types of the various automatically managed properties - property_types: Dict[str, Union[type, Tuple[type, ...]]] = dict( - add_custom_emoji_policy=int, - allow_edit_history=bool, - allow_message_editing=bool, - avatar_changes_disabled=bool, - bot_creation_policy=int, - create_private_stream_policy=int, - create_public_stream_policy=int, - create_web_public_stream_policy=int, - default_code_block_language=str, - default_language=str, - delete_own_message_policy=int, - description=str, - digest_emails_enabled=bool, - digest_weekday=int, - disallow_disposable_email_addresses=bool, - edit_topic_policy=int, - email_changes_disabled=bool, - emails_restricted_to_domains=bool, - enable_guest_user_indicator=bool, - enable_read_receipts=bool, - enable_spectator_access=bool, - giphy_rating=int, - inline_image_preview=bool, - inline_url_embed_preview=bool, - invite_required=bool, - invite_to_realm_policy=int, - invite_to_stream_policy=int, - jitsi_server_url=(str, type(None)), - mandatory_topics=bool, - message_content_allowed_in_email_notifications=bool, - message_content_edit_limit_seconds=(int, type(None)), - message_content_delete_limit_seconds=(int, type(None)), - move_messages_between_streams_limit_seconds=(int, type(None)), - move_messages_within_stream_limit_seconds=(int, type(None)), - message_retention_days=(int, type(None)), - move_messages_between_streams_policy=int, - name=str, - name_changes_disabled=bool, - private_message_policy=int, - push_notifications_enabled=bool, - send_welcome_emails=bool, - user_group_edit_policy=int, - video_chat_provider=int, - waiting_period_threshold=int, - want_advertise_in_communities_directory=bool, - wildcard_mention_policy=int, - ) - - REALM_PERMISSION_GROUP_SETTINGS: Dict[str, GroupPermissionSetting] = dict( - create_multiuse_invite_group=GroupPermissionSetting( - require_system_group=True, - allow_internet_group=False, - allow_owners_group=False, - allow_nobody_group=True, - allow_everyone_group=False, - default_group_name=SystemGroups.ADMINISTRATORS, - id_field_name="create_multiuse_invite_group_id", - ), - can_access_all_users_group=GroupPermissionSetting( - require_system_group=True, - allow_internet_group=False, - allow_owners_group=False, - allow_nobody_group=False, - allow_everyone_group=True, - default_group_name=SystemGroups.EVERYONE, - id_field_name="can_access_all_users_group_id", - allowed_system_groups=[SystemGroups.EVERYONE, SystemGroups.MEMBERS], - ), - ) - - DIGEST_WEEKDAY_VALUES = [0, 1, 2, 3, 4, 5, 6] - - # Icon is the square mobile icon. - ICON_FROM_GRAVATAR = "G" - ICON_UPLOADED = "U" - ICON_SOURCES = ( - (ICON_FROM_GRAVATAR, "Hosted by Gravatar"), - (ICON_UPLOADED, "Uploaded by administrator"), - ) - icon_source = models.CharField( - default=ICON_FROM_GRAVATAR, - choices=ICON_SOURCES, - max_length=1, - ) - icon_version = models.PositiveSmallIntegerField(default=1) - - # Logo is the horizontal logo we show in top-left of web app navbar UI. - LOGO_DEFAULT = "D" - LOGO_UPLOADED = "U" - LOGO_SOURCES = ( - (LOGO_DEFAULT, "Default to Zulip"), - (LOGO_UPLOADED, "Uploaded by administrator"), - ) - logo_source = models.CharField( - default=LOGO_DEFAULT, - choices=LOGO_SOURCES, - max_length=1, - ) - logo_version = models.PositiveSmallIntegerField(default=1) - - night_logo_source = models.CharField( - default=LOGO_DEFAULT, - choices=LOGO_SOURCES, - max_length=1, - ) - night_logo_version = models.PositiveSmallIntegerField(default=1) - - @override - def __str__(self) -> str: - return f"{self.string_id} {self.id}" - - def get_giphy_rating_options(self) -> Dict[str, Dict[str, object]]: - """Wrapper function for GIPHY_RATING_OPTIONS that ensures evaluation - of the lazily evaluated `name` field without modifying the original.""" - return { - rating_type: {"name": str(rating["name"]), "id": rating["id"]} - for rating_type, rating in self.GIPHY_RATING_OPTIONS.items() - } - - def authentication_methods_dict(self) -> Dict[str, bool]: - """Returns the mapping from authentication flags to their status, - showing only those authentication flags that are supported on - the current server (i.e. if EmailAuthBackend is not configured - on the server, this will not return an entry for "Email").""" - # This mapping needs to be imported from here due to the cyclic - # dependency. - from zproject.backends import AUTH_BACKEND_NAME_MAP, all_implemented_backend_names - - ret: Dict[str, bool] = {} - supported_backends = [type(backend) for backend in supported_auth_backends()] - - for backend_name in all_implemented_backend_names(): - backend_class = AUTH_BACKEND_NAME_MAP[backend_name] - if backend_class in supported_backends: - ret[backend_name] = False - for realm_authentication_method in RealmAuthenticationMethod.objects.filter( - realm_id=self.id - ): - backend_class = AUTH_BACKEND_NAME_MAP[realm_authentication_method.name] - if backend_class in supported_backends: - ret[realm_authentication_method.name] = True - return ret - - def get_admin_users_and_bots( - self, include_realm_owners: bool = True - ) -> QuerySet["UserProfile"]: - """Use this in contexts where we want administrative users as well as - bots with administrator privileges, like send_event calls for - notifications to all administrator users. - """ - if include_realm_owners: - roles = [UserProfile.ROLE_REALM_ADMINISTRATOR, UserProfile.ROLE_REALM_OWNER] - else: - roles = [UserProfile.ROLE_REALM_ADMINISTRATOR] - - return UserProfile.objects.filter( - realm=self, - is_active=True, - role__in=roles, - ) - - def get_human_admin_users(self, include_realm_owners: bool = True) -> QuerySet["UserProfile"]: - """Use this in contexts where we want only human users with - administrative privileges, like sending an email to all of a - realm's administrators (bots don't have real email addresses). - """ - if include_realm_owners: - roles = [UserProfile.ROLE_REALM_ADMINISTRATOR, UserProfile.ROLE_REALM_OWNER] - else: - roles = [UserProfile.ROLE_REALM_ADMINISTRATOR] - - return UserProfile.objects.filter( - realm=self, - is_bot=False, - is_active=True, - role__in=roles, - ) - - def get_human_billing_admin_and_realm_owner_users(self) -> QuerySet["UserProfile"]: - return UserProfile.objects.filter( - Q(role=UserProfile.ROLE_REALM_OWNER) | Q(is_billing_admin=True), - realm=self, - is_bot=False, - is_active=True, - ) - - def get_active_users(self) -> QuerySet["UserProfile"]: - return UserProfile.objects.filter(realm=self, is_active=True) - - def get_first_human_user(self) -> Optional["UserProfile"]: - """A useful value for communications with newly created realms. - Has a few fundamental limitations: - - * Its value will be effectively random for realms imported from Slack or - other third-party tools. - * The user may be deactivated, etc., so it's not something that's useful - for features, permissions, etc. - """ - return UserProfile.objects.filter(realm=self, is_bot=False).order_by("id").first() - - def get_human_owner_users(self) -> QuerySet["UserProfile"]: - return UserProfile.objects.filter( - realm=self, is_bot=False, role=UserProfile.ROLE_REALM_OWNER, is_active=True - ) - - def get_bot_domain(self) -> str: - return get_fake_email_domain(self.host) - - def get_notifications_stream(self) -> Optional["Stream"]: - if self.notifications_stream is not None and not self.notifications_stream.deactivated: - return self.notifications_stream - return None - - def get_signup_notifications_stream(self) -> Optional["Stream"]: - if ( - self.signup_notifications_stream is not None - and not self.signup_notifications_stream.deactivated - ): - return self.signup_notifications_stream - return None - - @property - def max_invites(self) -> int: - if self._max_invites is None: - return settings.INVITES_DEFAULT_REALM_DAILY_MAX - return self._max_invites - - @max_invites.setter - def max_invites(self, value: Optional[int]) -> None: - self._max_invites = value - - def upload_quota_bytes(self) -> Optional[int]: - if self.upload_quota_gb is None: - return None - # We describe the quota to users in "GB" or "gigabytes", but actually apply - # it as gibibytes (GiB) to be a bit more generous in case of confusion. - return self.upload_quota_gb << 30 - - # `realm` instead of `self` here to make sure the parameters of the cache key - # function matches the original method. - @cache_with_key( - lambda realm: get_realm_used_upload_space_cache_key(realm.id), timeout=3600 * 24 * 7 - ) - def currently_used_upload_space_bytes(realm) -> int: # noqa: N805 - used_space = Attachment.objects.filter(realm=realm).aggregate(Sum("size"))["size__sum"] - if used_space is None: - return 0 - return used_space - - def ensure_not_on_limited_plan(self) -> None: - if self.plan_type == Realm.PLAN_TYPE_LIMITED: - raise JsonableError(str(self.UPGRADE_TEXT_STANDARD)) - - def can_enable_restricted_user_access_for_guests(self) -> None: - if self.plan_type not in [Realm.PLAN_TYPE_PLUS, Realm.PLAN_TYPE_SELF_HOSTED]: - raise JsonableError(str(self.UPGRADE_TEXT_PLUS)) - - @property - def subdomain(self) -> str: - return self.string_id - - @property - def display_subdomain(self) -> str: - """Likely to be temporary function to avoid signup messages being sent - to an empty topic""" - if self.string_id == "": - return "." - return self.string_id - - @property - def uri(self) -> str: - return settings.EXTERNAL_URI_SCHEME + self.host - - @property - def host(self) -> str: - # Use mark sanitized to prevent false positives from Pysa thinking that - # the host is user controlled. - return mark_sanitized(self.host_for_subdomain(self.subdomain)) - - @staticmethod - def host_for_subdomain(subdomain: str) -> str: - if subdomain == Realm.SUBDOMAIN_FOR_ROOT_DOMAIN: - return settings.EXTERNAL_HOST - default_host = f"{subdomain}.{settings.EXTERNAL_HOST}" - return settings.REALM_HOSTS.get(subdomain, default_host) - - @property - def is_zephyr_mirror_realm(self) -> bool: - return self.string_id == "zephyr" - - @property - def webathena_enabled(self) -> bool: - return self.is_zephyr_mirror_realm - - @property - def presence_disabled(self) -> bool: - return self.is_zephyr_mirror_realm - - def web_public_streams_enabled(self) -> bool: - if not settings.WEB_PUBLIC_STREAMS_ENABLED: - # To help protect against accidentally web-public streams in - # self-hosted servers, we require the feature to be enabled at - # the server level before it is available to users. - return False - - if self.plan_type == Realm.PLAN_TYPE_LIMITED: - # In Zulip Cloud, we also require a paid or sponsored - # plan, to protect against the spam/abuse attacks that - # target every open Internet service that can host files. - return False - - if not self.enable_spectator_access: - return False - - return True - - def has_web_public_streams(self) -> bool: - if not self.web_public_streams_enabled(): - return False - - from zerver.lib.streams import get_web_public_streams_queryset - - return get_web_public_streams_queryset(self).exists() - - def allow_web_public_streams_access(self) -> bool: - """ - If any of the streams in the realm is web - public and `enable_spectator_access` and - settings.WEB_PUBLIC_STREAMS_ENABLED is True, - then the Realm is web-public. - """ - return self.has_web_public_streams() - - -post_save.connect(flush_realm, sender=Realm) - - -# We register realm cache flushing in a duplicate way to be run both -# pre_delete and post_delete on purpose: -# 1. pre_delete is needed because flush_realm wants to flush the UserProfile caches, -# and UserProfile objects are deleted via on_delete=CASCADE before the post_delete handler -# is called, which results in the `flush_realm` logic not having access to the details -# for the deleted users if called at that time. -# 2. post_delete is run as a precaution to reduce the risk of races where items might be -# added to the cache after the pre_delete handler but before the save. -# Note that it does not eliminate this risk, not least because it only flushes -# the realm cache, and not the user caches, for the reasons explained above. -def realm_pre_and_post_delete_handler(*, instance: Realm, **kwargs: object) -> None: - # This would be better as a functools.partial, but for some reason - # Django doesn't call it even when it's registered as a post_delete handler. - flush_realm(instance=instance, from_deletion=True) - - -pre_delete.connect(realm_pre_and_post_delete_handler, sender=Realm) -post_delete.connect(realm_pre_and_post_delete_handler, sender=Realm) - - -def get_realm(string_id: str) -> Realm: - return Realm.objects.get(string_id=string_id) - - -def get_realm_by_id(realm_id: int) -> Realm: - return Realm.objects.get(id=realm_id) - - -def name_changes_disabled(realm: Optional[Realm]) -> bool: - if realm is None: - return settings.NAME_CHANGES_DISABLED - return settings.NAME_CHANGES_DISABLED or realm.name_changes_disabled - - -def avatar_changes_disabled(realm: Realm) -> bool: - return settings.AVATAR_CHANGES_DISABLED or realm.avatar_changes_disabled - - -def get_org_type_display_name(org_type: int) -> str: - for realm_type_details in Realm.ORG_TYPES.values(): - if realm_type_details["id"] == org_type: - return realm_type_details["name"] - - return "" - - -class RealmDomain(models.Model): - """For an organization with emails_restricted_to_domains enabled, the list of - allowed domains""" - - realm = models.ForeignKey(Realm, on_delete=CASCADE) - # should always be stored lowercase - domain = models.CharField(max_length=80, db_index=True) - allow_subdomains = models.BooleanField(default=False) - - class Meta: - unique_together = ("realm", "domain") - - -class DomainNotAllowedForRealmError(Exception): - pass - - -class DisposableEmailError(Exception): - pass - - -class EmailContainsPlusError(Exception): - pass - - -class RealmDomainDict(TypedDict): - domain: str - allow_subdomains: bool - - -def get_realm_domains(realm: Realm) -> List[RealmDomainDict]: - return list(realm.realmdomain_set.values("domain", "allow_subdomains")) - - class RealmEmoji(models.Model): author = models.ForeignKey( "UserProfile", @@ -4061,30 +3088,6 @@ class BotConfigData(models.Model): unique_together = ("bot_profile", "key") -class InvalidFakeEmailDomainError(Exception): - pass - - -def get_fake_email_domain(realm_host: str) -> str: - try: - # Check that realm.host can be used to form valid email addresses. - validate_email(Address(username="bot", domain=realm_host).addr_spec) - return realm_host - except ValidationError: - pass - - try: - # Check that the fake email domain can be used to form valid email addresses. - validate_email(Address(username="bot", domain=settings.FAKE_EMAIL_DOMAIN).addr_spec) - except ValidationError: - raise InvalidFakeEmailDomainError( - settings.FAKE_EMAIL_DOMAIN + " is not a valid domain. " - "Consider setting the FAKE_EMAIL_DOMAIN setting." - ) - - return settings.FAKE_EMAIL_DOMAIN - - class AlertWord(models.Model): # Realm isn't necessary, but it's a nice denormalization. Users # never move to another realm, so it's static, and having Realm diff --git a/zerver/models/realms.py b/zerver/models/realms.py new file mode 100644 index 0000000000..f243e5dd8e --- /dev/null +++ b/zerver/models/realms.py @@ -0,0 +1,1019 @@ +from email.headerregistry import Address +from enum import Enum +from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, TypedDict, Union +from uuid import uuid4 + +import django.contrib.auth +from django.conf import settings +from django.core.exceptions import ValidationError +from django.core.validators import validate_email +from django.db import models +from django.db.models import CASCADE, Q, QuerySet, Sum +from django.db.models.signals import post_delete, post_save, pre_delete +from django.utils.timezone import now as timezone_now +from django.utils.translation import gettext_lazy +from typing_extensions import override + +from zerver.lib.cache import cache_with_key, flush_realm, get_realm_used_upload_space_cache_key +from zerver.lib.exceptions import JsonableError +from zerver.lib.pysa import mark_sanitized +from zerver.lib.types import GroupPermissionSetting +from zerver.lib.utils import generate_api_key +from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH +from zerver.models.groups import SystemGroups +from zerver.models.users import UserProfile + +if TYPE_CHECKING: + # We use ModelBackend only for typing. Importing it otherwise causes circular dependency. + from django.contrib.auth.backends import ModelBackend + + from zerver.models import Stream + +SECONDS_PER_DAY = 86400 + + +# This simple call-once caching saves ~500us in auth_enabled_helper, +# which is a significant optimization for common_context. Note that +# these values cannot change in a running production system, but do +# regularly change within unit tests; we address the latter by calling +# clear_supported_auth_backends_cache in our standard tearDown code. +supported_backends: Optional[List["ModelBackend"]] = None + + +def supported_auth_backends() -> List["ModelBackend"]: + global supported_backends + # Caching temporarily disabled for debugging + supported_backends = django.contrib.auth.get_backends() + assert supported_backends is not None + return supported_backends + + +def clear_supported_auth_backends_cache() -> None: + global supported_backends + supported_backends = None + + +class RealmAuthenticationMethod(models.Model): + """ + Tracks which authentication backends are enabled for a realm. + An enabled backend is represented in this table a row with appropriate + .realm value and .name matching the name of the target backend in the + AUTH_BACKEND_NAME_MAP dict. + """ + + realm = models.ForeignKey("Realm", on_delete=CASCADE, db_index=True) + name = models.CharField(max_length=80) + + class Meta: + unique_together = ("realm", "name") + + +def generate_realm_uuid_owner_secret() -> str: + token = generate_api_key() + + # We include a prefix to facilitate scanning for accidental + # disclosure of secrets e.g. in Github commit pushes. + return f"zuliprealm_{token}" + + +class OrgTypeEnum(Enum): + Unspecified = 0 + Business = 10 + OpenSource = 20 + EducationNonProfit = 30 + Education = 35 + Research = 40 + Event = 50 + NonProfit = 60 + Government = 70 + PoliticalGroup = 80 + Community = 90 + Personal = 100 + Other = 1000 + + +class OrgTypeDict(TypedDict): + name: str + id: int + hidden: bool + display_order: int + onboarding_zulip_guide_url: Optional[str] + + +class Realm(models.Model): # type: ignore[django-manager-missing] # django-stubs cannot resolve the custom CTEManager yet https://github.com/typeddjango/django-stubs/issues/1023 + MAX_REALM_NAME_LENGTH = 40 + MAX_REALM_DESCRIPTION_LENGTH = 1000 + MAX_REALM_SUBDOMAIN_LENGTH = 40 + MAX_REALM_REDIRECT_URL_LENGTH = 128 + + INVITES_STANDARD_REALM_DAILY_MAX = 3000 + MESSAGE_VISIBILITY_LIMITED = 10000 + SUBDOMAIN_FOR_ROOT_DOMAIN = "" + WILDCARD_MENTION_THRESHOLD = 15 + + # User-visible display name and description used on e.g. the organization homepage + name = models.CharField(max_length=MAX_REALM_NAME_LENGTH) + description = models.TextField(default="") + + # A short, identifier-like name for the organization. Used in subdomains; + # e.g. on a server at example.com, an org with string_id `foo` is reached + # at `foo.example.com`. + string_id = models.CharField(max_length=MAX_REALM_SUBDOMAIN_LENGTH, unique=True) + + # uuid and a secret for the sake of per-realm authentication with the push notification + # bouncer. + uuid = models.UUIDField(default=uuid4, unique=True) + uuid_owner_secret = models.TextField(default=generate_realm_uuid_owner_secret) + # Whether push notifications are working for this realm, and + # whether there is a specific date at which we expect that to + # cease to be the case. + push_notifications_enabled = models.BooleanField(default=False, db_index=True) + push_notifications_enabled_end_timestamp = models.DateTimeField(default=None, null=True) + + date_created = models.DateTimeField(default=timezone_now) + demo_organization_scheduled_deletion_date = models.DateTimeField(default=None, null=True) + deactivated = models.BooleanField(default=False) + + # Redirect URL if the Realm has moved to another server + deactivated_redirect = models.URLField(max_length=MAX_REALM_REDIRECT_URL_LENGTH, null=True) + + # See RealmDomain for the domains that apply for a given organization. + emails_restricted_to_domains = models.BooleanField(default=False) + + invite_required = models.BooleanField(default=True) + + _max_invites = models.IntegerField(null=True, db_column="max_invites") + disallow_disposable_email_addresses = models.BooleanField(default=True) + + # Allow users to access web-public streams without login. This + # setting also controls API access of web-public streams. + enable_spectator_access = models.BooleanField(default=False) + + # Whether organization has given permission to be advertised in the + # Zulip communities directory. + want_advertise_in_communities_directory = models.BooleanField(default=False, db_index=True) + + # Whether the organization has enabled inline image and URL previews. + inline_image_preview = models.BooleanField(default=True) + inline_url_embed_preview = models.BooleanField(default=False) + + # Whether digest emails are enabled for the organization. + digest_emails_enabled = models.BooleanField(default=False) + # Day of the week on which the digest is sent (default: Tuesday). + digest_weekday = models.SmallIntegerField(default=1) + + send_welcome_emails = models.BooleanField(default=True) + message_content_allowed_in_email_notifications = models.BooleanField(default=True) + + mandatory_topics = models.BooleanField(default=False) + + name_changes_disabled = models.BooleanField(default=False) + email_changes_disabled = models.BooleanField(default=False) + avatar_changes_disabled = models.BooleanField(default=False) + + POLICY_MEMBERS_ONLY = 1 + POLICY_ADMINS_ONLY = 2 + POLICY_FULL_MEMBERS_ONLY = 3 + POLICY_MODERATORS_ONLY = 4 + POLICY_EVERYONE = 5 + POLICY_NOBODY = 6 + POLICY_OWNERS_ONLY = 7 + + COMMON_POLICY_TYPES = [ + POLICY_MEMBERS_ONLY, + POLICY_ADMINS_ONLY, + POLICY_FULL_MEMBERS_ONLY, + POLICY_MODERATORS_ONLY, + ] + + COMMON_MESSAGE_POLICY_TYPES = [ + POLICY_MEMBERS_ONLY, + POLICY_ADMINS_ONLY, + POLICY_FULL_MEMBERS_ONLY, + POLICY_MODERATORS_ONLY, + POLICY_EVERYONE, + ] + + INVITE_TO_REALM_POLICY_TYPES = [ + POLICY_MEMBERS_ONLY, + POLICY_ADMINS_ONLY, + POLICY_FULL_MEMBERS_ONLY, + POLICY_MODERATORS_ONLY, + POLICY_NOBODY, + ] + + # We don't allow granting roles less than Moderator access to + # create web-public streams, since it's a sensitive feature that + # can be used to send spam. + CREATE_WEB_PUBLIC_STREAM_POLICY_TYPES = [ + POLICY_ADMINS_ONLY, + POLICY_MODERATORS_ONLY, + POLICY_OWNERS_ONLY, + POLICY_NOBODY, + ] + + EDIT_TOPIC_POLICY_TYPES = [ + POLICY_MEMBERS_ONLY, + POLICY_ADMINS_ONLY, + POLICY_FULL_MEMBERS_ONLY, + POLICY_MODERATORS_ONLY, + POLICY_EVERYONE, + POLICY_NOBODY, + ] + + MOVE_MESSAGES_BETWEEN_STREAMS_POLICY_TYPES = INVITE_TO_REALM_POLICY_TYPES + + DEFAULT_MOVE_MESSAGE_LIMIT_SECONDS = 7 * SECONDS_PER_DAY + + move_messages_within_stream_limit_seconds = models.PositiveIntegerField( + default=DEFAULT_MOVE_MESSAGE_LIMIT_SECONDS, null=True + ) + + move_messages_between_streams_limit_seconds = models.PositiveIntegerField( + default=DEFAULT_MOVE_MESSAGE_LIMIT_SECONDS, null=True + ) + + # Who in the organization is allowed to add custom emojis. + add_custom_emoji_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) + + # Who in the organization is allowed to create streams. + create_public_stream_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) + create_private_stream_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) + create_web_public_stream_policy = models.PositiveSmallIntegerField(default=POLICY_OWNERS_ONLY) + + # Who in the organization is allowed to delete messages they themselves sent. + delete_own_message_policy = models.PositiveSmallIntegerField(default=POLICY_ADMINS_ONLY) + + # Who in the organization is allowed to edit topics of any message. + edit_topic_policy = models.PositiveSmallIntegerField(default=POLICY_EVERYONE) + + # Who in the organization is allowed to invite other users to organization. + invite_to_realm_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) + + # UserGroup whose members are allowed to create invite link. + create_multiuse_invite_group = models.ForeignKey( + "UserGroup", on_delete=models.RESTRICT, related_name="+" + ) + + # on_delete field here is set to RESTRICT because we don't want to allow + # deleting a user group in case it is referenced by this setting. + # We are not using PROTECT since we want to allow deletion of user groups + # when realm itself is deleted. + can_access_all_users_group = models.ForeignKey( + "UserGroup", on_delete=models.RESTRICT, related_name="+" + ) + + # Who in the organization is allowed to invite other users to streams. + invite_to_stream_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) + + # Who in the organization is allowed to move messages between streams. + move_messages_between_streams_policy = models.PositiveSmallIntegerField( + default=POLICY_ADMINS_ONLY + ) + + user_group_edit_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) + + PRIVATE_MESSAGE_POLICY_UNLIMITED = 1 + PRIVATE_MESSAGE_POLICY_DISABLED = 2 + private_message_policy = models.PositiveSmallIntegerField( + default=PRIVATE_MESSAGE_POLICY_UNLIMITED + ) + PRIVATE_MESSAGE_POLICY_TYPES = [ + PRIVATE_MESSAGE_POLICY_UNLIMITED, + PRIVATE_MESSAGE_POLICY_DISABLED, + ] + + # Global policy for who is allowed to use wildcard mentions in + # streams with a large number of subscribers. Anyone can use + # wildcard mentions in small streams regardless of this setting. + WILDCARD_MENTION_POLICY_EVERYONE = 1 + WILDCARD_MENTION_POLICY_MEMBERS = 2 + WILDCARD_MENTION_POLICY_FULL_MEMBERS = 3 + WILDCARD_MENTION_POLICY_ADMINS = 5 + WILDCARD_MENTION_POLICY_NOBODY = 6 + WILDCARD_MENTION_POLICY_MODERATORS = 7 + wildcard_mention_policy = models.PositiveSmallIntegerField( + default=WILDCARD_MENTION_POLICY_ADMINS, + ) + WILDCARD_MENTION_POLICY_TYPES = [ + WILDCARD_MENTION_POLICY_EVERYONE, + WILDCARD_MENTION_POLICY_MEMBERS, + WILDCARD_MENTION_POLICY_FULL_MEMBERS, + WILDCARD_MENTION_POLICY_ADMINS, + WILDCARD_MENTION_POLICY_NOBODY, + WILDCARD_MENTION_POLICY_MODERATORS, + ] + + # Threshold in days for new users to create streams, and potentially take + # some other actions. + waiting_period_threshold = models.PositiveIntegerField(default=0) + + DEFAULT_MESSAGE_CONTENT_DELETE_LIMIT_SECONDS = ( + 600 # if changed, also change in admin.js, setting_org.js + ) + MESSAGE_TIME_LIMIT_SETTING_SPECIAL_VALUES_MAP = { + "unlimited": None, + } + message_content_delete_limit_seconds = models.PositiveIntegerField( + default=DEFAULT_MESSAGE_CONTENT_DELETE_LIMIT_SECONDS, null=True + ) + + allow_message_editing = models.BooleanField(default=True) + DEFAULT_MESSAGE_CONTENT_EDIT_LIMIT_SECONDS = ( + 600 # if changed, also change in admin.js, setting_org.js + ) + message_content_edit_limit_seconds = models.PositiveIntegerField( + default=DEFAULT_MESSAGE_CONTENT_EDIT_LIMIT_SECONDS, null=True + ) + + # Whether users have access to message edit history + allow_edit_history = models.BooleanField(default=True) + + # Defaults for new users + default_language = models.CharField(default="en", max_length=MAX_LANGUAGE_ID_LENGTH) + + DEFAULT_NOTIFICATION_STREAM_NAME = "general" + INITIAL_PRIVATE_STREAM_NAME = "core team" + STREAM_EVENTS_NOTIFICATION_TOPIC = gettext_lazy("stream events") + notifications_stream = models.ForeignKey( + "Stream", + related_name="+", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + signup_notifications_stream = models.ForeignKey( + "Stream", + related_name="+", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + + MESSAGE_RETENTION_SPECIAL_VALUES_MAP = { + "unlimited": -1, + } + # For old messages being automatically deleted + message_retention_days = models.IntegerField(null=False, default=-1) + + # When non-null, all but the latest this many messages in the organization + # are inaccessible to users (but not deleted). + message_visibility_limit = models.IntegerField(null=True) + + # Messages older than this message ID in the organization are inaccessible. + first_visible_message_id = models.IntegerField(default=0) + + # Valid org types + ORG_TYPES: Dict[str, OrgTypeDict] = { + "unspecified": { + "name": "Unspecified", + "id": OrgTypeEnum.Unspecified.value, + "hidden": True, + "display_order": 0, + "onboarding_zulip_guide_url": None, + }, + "business": { + "name": "Business", + "id": OrgTypeEnum.Business.value, + "hidden": False, + "display_order": 1, + "onboarding_zulip_guide_url": "https://zulip.com/for/business/", + }, + "opensource": { + "name": "Open-source project", + "id": OrgTypeEnum.OpenSource.value, + "hidden": False, + "display_order": 2, + "onboarding_zulip_guide_url": "https://zulip.com/for/open-source/", + }, + "education_nonprofit": { + "name": "Education (non-profit)", + "id": OrgTypeEnum.EducationNonProfit.value, + "hidden": False, + "display_order": 3, + "onboarding_zulip_guide_url": "https://zulip.com/for/education/", + }, + "education": { + "name": "Education (for-profit)", + "id": OrgTypeEnum.Education.value, + "hidden": False, + "display_order": 4, + "onboarding_zulip_guide_url": "https://zulip.com/for/education/", + }, + "research": { + "name": "Research", + "id": OrgTypeEnum.Research.value, + "hidden": False, + "display_order": 5, + "onboarding_zulip_guide_url": "https://zulip.com/for/research/", + }, + "event": { + "name": "Event or conference", + "id": OrgTypeEnum.Event.value, + "hidden": False, + "display_order": 6, + "onboarding_zulip_guide_url": "https://zulip.com/for/events/", + }, + "nonprofit": { + "name": "Non-profit (registered)", + "id": OrgTypeEnum.NonProfit.value, + "hidden": False, + "display_order": 7, + "onboarding_zulip_guide_url": "https://zulip.com/for/communities/", + }, + "government": { + "name": "Government", + "id": OrgTypeEnum.Government.value, + "hidden": False, + "display_order": 8, + "onboarding_zulip_guide_url": None, + }, + "political_group": { + "name": "Political group", + "id": OrgTypeEnum.PoliticalGroup.value, + "hidden": False, + "display_order": 9, + "onboarding_zulip_guide_url": None, + }, + "community": { + "name": "Community", + "id": OrgTypeEnum.Community.value, + "hidden": False, + "display_order": 10, + "onboarding_zulip_guide_url": "https://zulip.com/for/communities/", + }, + "personal": { + "name": "Personal", + "id": OrgTypeEnum.Personal.value, + "hidden": False, + "display_order": 100, + "onboarding_zulip_guide_url": None, + }, + "other": { + "name": "Other", + "id": OrgTypeEnum.Other.value, + "hidden": False, + "display_order": 1000, + "onboarding_zulip_guide_url": None, + }, + } + + ORG_TYPE_IDS: List[int] = [t["id"] for t in ORG_TYPES.values()] + + org_type = models.PositiveSmallIntegerField( + default=ORG_TYPES["unspecified"]["id"], + choices=[(t["id"], t["name"]) for t in ORG_TYPES.values()], + ) + + UPGRADE_TEXT_STANDARD = gettext_lazy("Available on Zulip Cloud Standard. Upgrade to access.") + UPGRADE_TEXT_PLUS = gettext_lazy("Available on Zulip Cloud Plus. Upgrade to access.") + # plan_type controls various features around resource/feature + # limitations for a Zulip organization on multi-tenant installations + # like Zulip Cloud. + PLAN_TYPE_SELF_HOSTED = 1 + PLAN_TYPE_LIMITED = 2 + PLAN_TYPE_STANDARD = 3 + PLAN_TYPE_STANDARD_FREE = 4 + PLAN_TYPE_PLUS = 10 + + # Used for creating realms with different plan types. + ALL_PLAN_TYPES = { + PLAN_TYPE_SELF_HOSTED: "self-hosted-plan", + PLAN_TYPE_LIMITED: "limited-plan", + PLAN_TYPE_STANDARD: "standard-plan", + PLAN_TYPE_STANDARD_FREE: "standard-free-plan", + PLAN_TYPE_PLUS: "plus-plan", + } + plan_type = models.PositiveSmallIntegerField(default=PLAN_TYPE_SELF_HOSTED) + + # This value is also being used in web/src/settings_bots.bot_creation_policy_values. + # On updating it here, update it there as well. + BOT_CREATION_EVERYONE = 1 + BOT_CREATION_LIMIT_GENERIC_BOTS = 2 + BOT_CREATION_ADMINS_ONLY = 3 + bot_creation_policy = models.PositiveSmallIntegerField(default=BOT_CREATION_EVERYONE) + BOT_CREATION_POLICY_TYPES = [ + BOT_CREATION_EVERYONE, + BOT_CREATION_LIMIT_GENERIC_BOTS, + BOT_CREATION_ADMINS_ONLY, + ] + + # See upload_quota_bytes; don't interpret upload_quota_gb directly. + UPLOAD_QUOTA_LIMITED = 5 + UPLOAD_QUOTA_STANDARD = 50 + upload_quota_gb = models.IntegerField(null=True) + + VIDEO_CHAT_PROVIDERS = { + "disabled": { + "name": "None", + "id": 0, + }, + "jitsi_meet": { + "name": "Jitsi Meet", + "id": 1, + }, + # ID 2 was used for the now-deleted Google Hangouts. + # ID 3 reserved for optional Zoom, see below. + # ID 4 reserved for optional BigBlueButton, see below. + } + + if settings.VIDEO_ZOOM_CLIENT_ID is not None and settings.VIDEO_ZOOM_CLIENT_SECRET is not None: + VIDEO_CHAT_PROVIDERS["zoom"] = { + "name": "Zoom", + "id": 3, + } + + if settings.BIG_BLUE_BUTTON_SECRET is not None and settings.BIG_BLUE_BUTTON_URL is not None: + VIDEO_CHAT_PROVIDERS["big_blue_button"] = {"name": "BigBlueButton", "id": 4} + + video_chat_provider = models.PositiveSmallIntegerField( + default=VIDEO_CHAT_PROVIDERS["jitsi_meet"]["id"] + ) + + JITSI_SERVER_SPECIAL_VALUES_MAP = {"default": None} + jitsi_server_url = models.URLField(null=True, default=None) + + # Please access this via get_giphy_rating_options. + GIPHY_RATING_OPTIONS = { + "disabled": { + "name": gettext_lazy("GIPHY integration disabled"), + "id": 0, + }, + # Source: https://github.com/Giphy/giphy-js/blob/master/packages/fetch-api/README.md#shared-options + "y": { + "name": gettext_lazy("Allow GIFs rated Y (Very young audience)"), + "id": 1, + }, + "g": { + "name": gettext_lazy("Allow GIFs rated G (General audience)"), + "id": 2, + }, + "pg": { + "name": gettext_lazy("Allow GIFs rated PG (Parental guidance)"), + "id": 3, + }, + "pg-13": { + "name": gettext_lazy("Allow GIFs rated PG-13 (Parental guidance - under 13)"), + "id": 4, + }, + "r": { + "name": gettext_lazy("Allow GIFs rated R (Restricted)"), + "id": 5, + }, + } + + # maximum rating of the GIFs that will be retrieved from GIPHY + giphy_rating = models.PositiveSmallIntegerField(default=GIPHY_RATING_OPTIONS["g"]["id"]) + + default_code_block_language = models.TextField(default="") + + # Whether read receipts are enabled in the organization. If disabled, + # they will not be available regardless of users' personal settings. + enable_read_receipts = models.BooleanField(default=False) + + # Whether clients should display "(guest)" after names of guest users. + enable_guest_user_indicator = models.BooleanField(default=True) + + # Define the types of the various automatically managed properties + property_types: Dict[str, Union[type, Tuple[type, ...]]] = dict( + add_custom_emoji_policy=int, + allow_edit_history=bool, + allow_message_editing=bool, + avatar_changes_disabled=bool, + bot_creation_policy=int, + create_private_stream_policy=int, + create_public_stream_policy=int, + create_web_public_stream_policy=int, + default_code_block_language=str, + default_language=str, + delete_own_message_policy=int, + description=str, + digest_emails_enabled=bool, + digest_weekday=int, + disallow_disposable_email_addresses=bool, + edit_topic_policy=int, + email_changes_disabled=bool, + emails_restricted_to_domains=bool, + enable_guest_user_indicator=bool, + enable_read_receipts=bool, + enable_spectator_access=bool, + giphy_rating=int, + inline_image_preview=bool, + inline_url_embed_preview=bool, + invite_required=bool, + invite_to_realm_policy=int, + invite_to_stream_policy=int, + jitsi_server_url=(str, type(None)), + mandatory_topics=bool, + message_content_allowed_in_email_notifications=bool, + message_content_edit_limit_seconds=(int, type(None)), + message_content_delete_limit_seconds=(int, type(None)), + move_messages_between_streams_limit_seconds=(int, type(None)), + move_messages_within_stream_limit_seconds=(int, type(None)), + message_retention_days=(int, type(None)), + move_messages_between_streams_policy=int, + name=str, + name_changes_disabled=bool, + private_message_policy=int, + push_notifications_enabled=bool, + send_welcome_emails=bool, + user_group_edit_policy=int, + video_chat_provider=int, + waiting_period_threshold=int, + want_advertise_in_communities_directory=bool, + wildcard_mention_policy=int, + ) + + REALM_PERMISSION_GROUP_SETTINGS: Dict[str, GroupPermissionSetting] = dict( + create_multiuse_invite_group=GroupPermissionSetting( + require_system_group=True, + allow_internet_group=False, + allow_owners_group=False, + allow_nobody_group=True, + allow_everyone_group=False, + default_group_name=SystemGroups.ADMINISTRATORS, + id_field_name="create_multiuse_invite_group_id", + ), + can_access_all_users_group=GroupPermissionSetting( + require_system_group=True, + allow_internet_group=False, + allow_owners_group=False, + allow_nobody_group=False, + allow_everyone_group=True, + default_group_name=SystemGroups.EVERYONE, + id_field_name="can_access_all_users_group_id", + allowed_system_groups=[SystemGroups.EVERYONE, SystemGroups.MEMBERS], + ), + ) + + DIGEST_WEEKDAY_VALUES = [0, 1, 2, 3, 4, 5, 6] + + # Icon is the square mobile icon. + ICON_FROM_GRAVATAR = "G" + ICON_UPLOADED = "U" + ICON_SOURCES = ( + (ICON_FROM_GRAVATAR, "Hosted by Gravatar"), + (ICON_UPLOADED, "Uploaded by administrator"), + ) + icon_source = models.CharField( + default=ICON_FROM_GRAVATAR, + choices=ICON_SOURCES, + max_length=1, + ) + icon_version = models.PositiveSmallIntegerField(default=1) + + # Logo is the horizontal logo we show in top-left of web app navbar UI. + LOGO_DEFAULT = "D" + LOGO_UPLOADED = "U" + LOGO_SOURCES = ( + (LOGO_DEFAULT, "Default to Zulip"), + (LOGO_UPLOADED, "Uploaded by administrator"), + ) + logo_source = models.CharField( + default=LOGO_DEFAULT, + choices=LOGO_SOURCES, + max_length=1, + ) + logo_version = models.PositiveSmallIntegerField(default=1) + + night_logo_source = models.CharField( + default=LOGO_DEFAULT, + choices=LOGO_SOURCES, + max_length=1, + ) + night_logo_version = models.PositiveSmallIntegerField(default=1) + + @override + def __str__(self) -> str: + return f"{self.string_id} {self.id}" + + def get_giphy_rating_options(self) -> Dict[str, Dict[str, object]]: + """Wrapper function for GIPHY_RATING_OPTIONS that ensures evaluation + of the lazily evaluated `name` field without modifying the original.""" + return { + rating_type: {"name": str(rating["name"]), "id": rating["id"]} + for rating_type, rating in self.GIPHY_RATING_OPTIONS.items() + } + + def authentication_methods_dict(self) -> Dict[str, bool]: + """Returns the mapping from authentication flags to their status, + showing only those authentication flags that are supported on + the current server (i.e. if EmailAuthBackend is not configured + on the server, this will not return an entry for "Email").""" + # This mapping needs to be imported from here due to the cyclic + # dependency. + from zproject.backends import AUTH_BACKEND_NAME_MAP, all_implemented_backend_names + + ret: Dict[str, bool] = {} + supported_backends = [type(backend) for backend in supported_auth_backends()] + + for backend_name in all_implemented_backend_names(): + backend_class = AUTH_BACKEND_NAME_MAP[backend_name] + if backend_class in supported_backends: + ret[backend_name] = False + for realm_authentication_method in RealmAuthenticationMethod.objects.filter( + realm_id=self.id + ): + backend_class = AUTH_BACKEND_NAME_MAP[realm_authentication_method.name] + if backend_class in supported_backends: + ret[realm_authentication_method.name] = True + return ret + + def get_admin_users_and_bots( + self, include_realm_owners: bool = True + ) -> QuerySet["UserProfile"]: + """Use this in contexts where we want administrative users as well as + bots with administrator privileges, like send_event calls for + notifications to all administrator users. + """ + if include_realm_owners: + roles = [UserProfile.ROLE_REALM_ADMINISTRATOR, UserProfile.ROLE_REALM_OWNER] + else: + roles = [UserProfile.ROLE_REALM_ADMINISTRATOR] + + return UserProfile.objects.filter( + realm=self, + is_active=True, + role__in=roles, + ) + + def get_human_admin_users(self, include_realm_owners: bool = True) -> QuerySet["UserProfile"]: + """Use this in contexts where we want only human users with + administrative privileges, like sending an email to all of a + realm's administrators (bots don't have real email addresses). + """ + if include_realm_owners: + roles = [UserProfile.ROLE_REALM_ADMINISTRATOR, UserProfile.ROLE_REALM_OWNER] + else: + roles = [UserProfile.ROLE_REALM_ADMINISTRATOR] + + return UserProfile.objects.filter( + realm=self, + is_bot=False, + is_active=True, + role__in=roles, + ) + + def get_human_billing_admin_and_realm_owner_users(self) -> QuerySet["UserProfile"]: + return UserProfile.objects.filter( + Q(role=UserProfile.ROLE_REALM_OWNER) | Q(is_billing_admin=True), + realm=self, + is_bot=False, + is_active=True, + ) + + def get_active_users(self) -> QuerySet["UserProfile"]: + return UserProfile.objects.filter(realm=self, is_active=True) + + def get_first_human_user(self) -> Optional["UserProfile"]: + """A useful value for communications with newly created realms. + Has a few fundamental limitations: + + * Its value will be effectively random for realms imported from Slack or + other third-party tools. + * The user may be deactivated, etc., so it's not something that's useful + for features, permissions, etc. + """ + return UserProfile.objects.filter(realm=self, is_bot=False).order_by("id").first() + + def get_human_owner_users(self) -> QuerySet["UserProfile"]: + return UserProfile.objects.filter( + realm=self, is_bot=False, role=UserProfile.ROLE_REALM_OWNER, is_active=True + ) + + def get_bot_domain(self) -> str: + return get_fake_email_domain(self.host) + + def get_notifications_stream(self) -> Optional["Stream"]: + if self.notifications_stream is not None and not self.notifications_stream.deactivated: + return self.notifications_stream + return None + + def get_signup_notifications_stream(self) -> Optional["Stream"]: + if ( + self.signup_notifications_stream is not None + and not self.signup_notifications_stream.deactivated + ): + return self.signup_notifications_stream + return None + + @property + def max_invites(self) -> int: + if self._max_invites is None: + return settings.INVITES_DEFAULT_REALM_DAILY_MAX + return self._max_invites + + @max_invites.setter + def max_invites(self, value: Optional[int]) -> None: + self._max_invites = value + + def upload_quota_bytes(self) -> Optional[int]: + if self.upload_quota_gb is None: + return None + # We describe the quota to users in "GB" or "gigabytes", but actually apply + # it as gibibytes (GiB) to be a bit more generous in case of confusion. + return self.upload_quota_gb << 30 + + # `realm` instead of `self` here to make sure the parameters of the cache key + # function matches the original method. + @cache_with_key( + lambda realm: get_realm_used_upload_space_cache_key(realm.id), timeout=3600 * 24 * 7 + ) + def currently_used_upload_space_bytes(realm) -> int: # noqa: N805 + from zerver.models import Attachment + + used_space = Attachment.objects.filter(realm=realm).aggregate(Sum("size"))["size__sum"] + if used_space is None: + return 0 + return used_space + + def ensure_not_on_limited_plan(self) -> None: + if self.plan_type == Realm.PLAN_TYPE_LIMITED: + raise JsonableError(str(self.UPGRADE_TEXT_STANDARD)) + + def can_enable_restricted_user_access_for_guests(self) -> None: + if self.plan_type not in [Realm.PLAN_TYPE_PLUS, Realm.PLAN_TYPE_SELF_HOSTED]: + raise JsonableError(str(self.UPGRADE_TEXT_PLUS)) + + @property + def subdomain(self) -> str: + return self.string_id + + @property + def display_subdomain(self) -> str: + """Likely to be temporary function to avoid signup messages being sent + to an empty topic""" + if self.string_id == "": + return "." + return self.string_id + + @property + def uri(self) -> str: + return settings.EXTERNAL_URI_SCHEME + self.host + + @property + def host(self) -> str: + # Use mark sanitized to prevent false positives from Pysa thinking that + # the host is user controlled. + return mark_sanitized(self.host_for_subdomain(self.subdomain)) + + @staticmethod + def host_for_subdomain(subdomain: str) -> str: + if subdomain == Realm.SUBDOMAIN_FOR_ROOT_DOMAIN: + return settings.EXTERNAL_HOST + default_host = f"{subdomain}.{settings.EXTERNAL_HOST}" + return settings.REALM_HOSTS.get(subdomain, default_host) + + @property + def is_zephyr_mirror_realm(self) -> bool: + return self.string_id == "zephyr" + + @property + def webathena_enabled(self) -> bool: + return self.is_zephyr_mirror_realm + + @property + def presence_disabled(self) -> bool: + return self.is_zephyr_mirror_realm + + def web_public_streams_enabled(self) -> bool: + if not settings.WEB_PUBLIC_STREAMS_ENABLED: + # To help protect against accidentally web-public streams in + # self-hosted servers, we require the feature to be enabled at + # the server level before it is available to users. + return False + + if self.plan_type == Realm.PLAN_TYPE_LIMITED: + # In Zulip Cloud, we also require a paid or sponsored + # plan, to protect against the spam/abuse attacks that + # target every open Internet service that can host files. + return False + + if not self.enable_spectator_access: + return False + + return True + + def has_web_public_streams(self) -> bool: + if not self.web_public_streams_enabled(): + return False + + from zerver.lib.streams import get_web_public_streams_queryset + + return get_web_public_streams_queryset(self).exists() + + def allow_web_public_streams_access(self) -> bool: + """ + If any of the streams in the realm is web + public and `enable_spectator_access` and + settings.WEB_PUBLIC_STREAMS_ENABLED is True, + then the Realm is web-public. + """ + return self.has_web_public_streams() + + +post_save.connect(flush_realm, sender=Realm) + + +# We register realm cache flushing in a duplicate way to be run both +# pre_delete and post_delete on purpose: +# 1. pre_delete is needed because flush_realm wants to flush the UserProfile caches, +# and UserProfile objects are deleted via on_delete=CASCADE before the post_delete handler +# is called, which results in the `flush_realm` logic not having access to the details +# for the deleted users if called at that time. +# 2. post_delete is run as a precaution to reduce the risk of races where items might be +# added to the cache after the pre_delete handler but before the save. +# Note that it does not eliminate this risk, not least because it only flushes +# the realm cache, and not the user caches, for the reasons explained above. +def realm_pre_and_post_delete_handler(*, instance: Realm, **kwargs: object) -> None: + # This would be better as a functools.partial, but for some reason + # Django doesn't call it even when it's registered as a post_delete handler. + flush_realm(instance=instance, from_deletion=True) + + +pre_delete.connect(realm_pre_and_post_delete_handler, sender=Realm) +post_delete.connect(realm_pre_and_post_delete_handler, sender=Realm) + + +def get_realm(string_id: str) -> Realm: + return Realm.objects.get(string_id=string_id) + + +def get_realm_by_id(realm_id: int) -> Realm: + return Realm.objects.get(id=realm_id) + + +def name_changes_disabled(realm: Optional[Realm]) -> bool: + if realm is None: + return settings.NAME_CHANGES_DISABLED + return settings.NAME_CHANGES_DISABLED or realm.name_changes_disabled + + +def avatar_changes_disabled(realm: Realm) -> bool: + return settings.AVATAR_CHANGES_DISABLED or realm.avatar_changes_disabled + + +def get_org_type_display_name(org_type: int) -> str: + for realm_type_details in Realm.ORG_TYPES.values(): + if realm_type_details["id"] == org_type: + return realm_type_details["name"] + + return "" + + +class RealmDomain(models.Model): + """For an organization with emails_restricted_to_domains enabled, the list of + allowed domains""" + + realm = models.ForeignKey(Realm, on_delete=CASCADE) + # should always be stored lowercase + domain = models.CharField(max_length=80, db_index=True) + allow_subdomains = models.BooleanField(default=False) + + class Meta: + unique_together = ("realm", "domain") + + +class DomainNotAllowedForRealmError(Exception): + pass + + +class DisposableEmailError(Exception): + pass + + +class EmailContainsPlusError(Exception): + pass + + +class RealmDomainDict(TypedDict): + domain: str + allow_subdomains: bool + + +def get_realm_domains(realm: Realm) -> List[RealmDomainDict]: + return list(realm.realmdomain_set.values("domain", "allow_subdomains")) + + +class InvalidFakeEmailDomainError(Exception): + pass + + +def get_fake_email_domain(realm_host: str) -> str: + try: + # Check that realm.host can be used to form valid email addresses. + validate_email(Address(username="bot", domain=realm_host).addr_spec) + return realm_host + except ValidationError: + pass + + try: + # Check that the fake email domain can be used to form valid email addresses. + validate_email(Address(username="bot", domain=settings.FAKE_EMAIL_DOMAIN).addr_spec) + except ValidationError: + raise InvalidFakeEmailDomainError( + settings.FAKE_EMAIL_DOMAIN + " is not a valid domain. " + "Consider setting the FAKE_EMAIL_DOMAIN setting." + ) + + return settings.FAKE_EMAIL_DOMAIN diff --git a/zerver/models/users.py b/zerver/models/users.py index bfb74787d3..26d7b5f93e 100644 --- a/zerver/models/users.py +++ b/zerver/models/users.py @@ -1060,7 +1060,8 @@ def bot_owner_user_ids(user_profile: UserProfile) -> Set[int]: def get_source_profile(email: str, realm_id: int) -> Optional[UserProfile]: - from zerver.models import Realm, get_realm_by_id + from zerver.models import Realm + from zerver.models.realms import get_realm_by_id try: return get_user_by_delivery_email(email, get_realm_by_id(realm_id)) diff --git a/zerver/openapi/curl_param_value_generators.py b/zerver/openapi/curl_param_value_generators.py index 58e56d90c3..3b7efb859b 100644 --- a/zerver/openapi/curl_param_value_generators.py +++ b/zerver/openapi/curl_param_value_generators.py @@ -20,7 +20,8 @@ from zerver.lib.initial_password import initial_password from zerver.lib.test_classes import ZulipTestCase from zerver.lib.upload import upload_message_attachment from zerver.lib.users import get_api_key -from zerver.models import Client, Message, UserGroup, UserPresence, get_realm +from zerver.models import Client, Message, UserGroup, UserPresence +from zerver.models.realms import get_realm from zerver.models.users import get_user GENERATOR_FUNCTIONS: Dict[str, Callable[[], Dict[str, object]]] = {} diff --git a/zerver/openapi/python_examples.py b/zerver/openapi/python_examples.py index 2a1c6f4076..1cb811951f 100644 --- a/zerver/openapi/python_examples.py +++ b/zerver/openapi/python_examples.py @@ -22,7 +22,7 @@ from typing import Any, Callable, Dict, List, Set, TypeVar from typing_extensions import ParamSpec from zulip import Client -from zerver.models import get_realm +from zerver.models.realms import get_realm from zerver.models.users import get_user from zerver.openapi.openapi import validate_against_openapi_schema diff --git a/zerver/openapi/test_curl_examples.py b/zerver/openapi/test_curl_examples.py index f487cfc972..b009afb198 100644 --- a/zerver/openapi/test_curl_examples.py +++ b/zerver/openapi/test_curl_examples.py @@ -16,7 +16,7 @@ import markdown from django.conf import settings from zulip import Client -from zerver.models import get_realm +from zerver.models.realms import get_realm from zerver.openapi import markdown_extension from zerver.openapi.curl_param_value_generators import ( AUTHENTICATION_LINE, diff --git a/zerver/tests/test_audit_log.py b/zerver/tests/test_audit_log.py index 52a5217a75..6e601775e7 100644 --- a/zerver/tests/test_audit_log.py +++ b/zerver/tests/test_audit_log.py @@ -77,20 +77,18 @@ from zerver.models import ( Message, Realm, RealmAuditLog, - RealmDomainDict, RealmPlayground, Recipient, Subscription, UserGroup, UserProfile, get_all_custom_emoji_for_realm, - get_realm, - get_realm_domains, get_realm_playgrounds, get_stream, linkifiers_for_realm, ) from zerver.models.groups import SystemGroups +from zerver.models.realms import RealmDomainDict, get_realm, get_realm_domains class TestRealmAuditLog(ZulipTestCase): diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index f91ff9fcba..4d5bb2be96 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -116,9 +116,8 @@ from zerver.models import ( Stream, UserGroup, UserProfile, - clear_supported_auth_backends_cache, - get_realm, ) +from zerver.models.realms import clear_supported_auth_backends_cache, get_realm from zerver.models.users import PasswordTooWeakError, get_user_by_delivery_email from zerver.signals import JUST_CREATED_THRESHOLD from zerver.views.auth import log_into_subdomain, maybe_send_to_registration diff --git a/zerver/tests/test_bots.py b/zerver/tests/test_bots.py index 5567b76644..4aecbf1f07 100644 --- a/zerver/tests/test_bots.py +++ b/zerver/tests/test_bots.py @@ -24,9 +24,9 @@ from zerver.models import ( Subscription, UserProfile, get_bot_services, - get_realm, get_stream, ) +from zerver.models.realms import get_realm from zerver.models.users import get_user, is_cross_realm_bot_email diff --git a/zerver/tests/test_cache.py b/zerver/tests/test_cache.py index b7f75bf7ef..5f0730dff5 100644 --- a/zerver/tests/test_cache.py +++ b/zerver/tests/test_cache.py @@ -21,7 +21,8 @@ from zerver.lib.cache import ( validate_cache_key, ) from zerver.lib.test_classes import ZulipTestCase -from zerver.models import UserProfile, get_realm +from zerver.models import UserProfile +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot, get_user, get_user_profile_by_id diff --git a/zerver/tests/test_custom_profile_data.py b/zerver/tests/test_custom_profile_data.py index 39a8f88eea..e83d026015 100644 --- a/zerver/tests/test_custom_profile_data.py +++ b/zerver/tests/test_custom_profile_data.py @@ -20,8 +20,8 @@ from zerver.models import ( CustomProfileFieldValue, UserProfile, custom_profile_fields_for_realm, - get_realm, ) +from zerver.models.realms import get_realm class CustomProfileFieldTestCase(ZulipTestCase): diff --git a/zerver/tests/test_decorators.py b/zerver/tests/test_decorators.py index 4914d7121c..02dd5b62be 100644 --- a/zerver/tests/test_decorators.py +++ b/zerver/tests/test_decorators.py @@ -52,7 +52,8 @@ from zerver.lib.user_agent import parse_user_agent from zerver.lib.users import get_api_key from zerver.lib.utils import generate_api_key, has_api_key_format from zerver.middleware import LogRequests, parse_client -from zerver.models import Client, Realm, UserProfile, clear_client_cache, get_realm +from zerver.models import Client, Realm, UserProfile, clear_client_cache +from zerver.models.realms import get_realm from zerver.models.users import get_user if settings.ZILENCER_ENABLED: diff --git a/zerver/tests/test_digest.py b/zerver/tests/test_digest.py index 5c2eb3e498..a8da00bc6c 100644 --- a/zerver/tests/test_digest.py +++ b/zerver/tests/test_digest.py @@ -33,9 +33,9 @@ from zerver.models import ( Stream, UserActivityInterval, UserProfile, - get_realm, get_stream, ) +from zerver.models.realms import get_realm class TestDigestEmailMessages(ZulipTestCase): diff --git a/zerver/tests/test_docs.py b/zerver/tests/test_docs.py index ab8770d4b3..b34b268112 100644 --- a/zerver/tests/test_docs.py +++ b/zerver/tests/test_docs.py @@ -14,7 +14,8 @@ from zerver.context_processors import get_apps_page_url from zerver.lib.integrations import CATEGORIES, INTEGRATIONS, META_CATEGORY from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import HostRequestMock -from zerver.models import Realm, get_realm +from zerver.models import Realm +from zerver.models.realms import get_realm from zerver.views.documentation import add_api_url_context if TYPE_CHECKING: diff --git a/zerver/tests/test_email_change.py b/zerver/tests/test_email_change.py index 0d8f7b61c7..65009f30f7 100644 --- a/zerver/tests/test_email_change.py +++ b/zerver/tests/test_email_change.py @@ -18,7 +18,8 @@ from zerver.actions.realm_settings import do_deactivate_realm, do_set_realm_prop from zerver.actions.user_settings import do_change_user_setting, do_start_email_change_process from zerver.actions.users import do_deactivate_user from zerver.lib.test_classes import ZulipTestCase -from zerver.models import EmailChangeStatus, UserProfile, get_realm +from zerver.models import EmailChangeStatus, UserProfile +from zerver.models.realms import get_realm from zerver.models.users import get_user, get_user_by_delivery_email, get_user_profile_by_id diff --git a/zerver/tests/test_email_mirror.py b/zerver/tests/test_email_mirror.py index eea87920cd..425c5478a6 100644 --- a/zerver/tests/test_email_mirror.py +++ b/zerver/tests/test_email_mirror.py @@ -37,7 +37,8 @@ from zerver.lib.send_email import FromAddress from zerver.lib.streams import ensure_stream from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import mock_queue_publish, most_recent_message, most_recent_usermessage -from zerver.models import Attachment, Recipient, Stream, UserProfile, get_realm, get_stream +from zerver.models import Attachment, Recipient, Stream, UserProfile, get_stream +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot from zerver.worker.queue_processors import MirrorWorker diff --git a/zerver/tests/test_email_notifications.py b/zerver/tests/test_email_notifications.py index 3fa46e4540..d2b3b82d42 100644 --- a/zerver/tests/test_email_notifications.py +++ b/zerver/tests/test_email_notifications.py @@ -22,7 +22,8 @@ from zerver.lib.send_email import ( send_custom_server_email, ) from zerver.lib.test_classes import ZulipTestCase -from zerver.models import Realm, ScheduledEmail, UserProfile, get_realm +from zerver.models import Realm, ScheduledEmail, UserProfile +from zerver.models.realms import get_realm from zilencer.models import RemoteZulipServer diff --git a/zerver/tests/test_embedded_bot_system.py b/zerver/tests/test_embedded_bot_system.py index bd52d952df..bca7c86bc6 100644 --- a/zerver/tests/test_embedded_bot_system.py +++ b/zerver/tests/test_embedded_bot_system.py @@ -5,7 +5,8 @@ from typing_extensions import override from zerver.lib.bot_lib import EmbeddedBotQuitError from zerver.lib.test_classes import ZulipTestCase -from zerver.models import UserProfile, get_display_recipient, get_realm, get_service_profile +from zerver.models import UserProfile, get_display_recipient, get_service_profile +from zerver.models.realms import get_realm from zerver.models.users import get_user diff --git a/zerver/tests/test_event_system.py b/zerver/tests/test_event_system.py index 0b316d679a..0513a5105a 100644 --- a/zerver/tests/test_event_system.py +++ b/zerver/tests/test_event_system.py @@ -34,9 +34,9 @@ from zerver.models import ( UserPresence, UserProfile, get_client, - get_realm, get_stream, ) +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot from zerver.tornado.event_queue import ( allocate_client_descriptor, diff --git a/zerver/tests/test_example.py b/zerver/tests/test_example.py index 4be0e57a84..8ae9d79e86 100644 --- a/zerver/tests/test_example.py +++ b/zerver/tests/test_example.py @@ -11,7 +11,8 @@ from zerver.lib.streams import access_stream_for_send_message from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import most_recent_message from zerver.lib.users import is_administrator_role -from zerver.models import UserProfile, UserStatus, get_realm, get_stream +from zerver.models import UserProfile, UserStatus, get_stream +from zerver.models.realms import get_realm from zerver.models.users import get_user_by_delivery_email diff --git a/zerver/tests/test_gitter_importer.py b/zerver/tests/test_gitter_importer.py index 5a8502b9c3..75e950acbd 100644 --- a/zerver/tests/test_gitter_importer.py +++ b/zerver/tests/test_gitter_importer.py @@ -10,7 +10,8 @@ import time_machine from zerver.data_import.gitter import do_convert_data, get_usermentions from zerver.lib.import_realm import do_import_realm from zerver.lib.test_classes import ZulipTestCase -from zerver.models import Message, UserProfile, get_realm +from zerver.models import Message, UserProfile +from zerver.models.realms import get_realm from zproject.backends import ( AUTH_BACKEND_NAME_MAP, GitHubAuthBackend, diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 4a00c1b7de..29ff92b6b1 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -24,15 +24,8 @@ from zerver.lib.soft_deactivation import do_soft_deactivate_users from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import get_user_messages, queries_captured from zerver.lib.timestamp import datetime_to_timestamp -from zerver.models import ( - DefaultStream, - Draft, - Realm, - UserActivity, - UserProfile, - get_realm, - get_stream, -) +from zerver.models import DefaultStream, Draft, Realm, UserActivity, UserProfile, get_stream +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot, get_user from zerver.worker.queue_processors import UserActivityWorker diff --git a/zerver/tests/test_hotspots.py b/zerver/tests/test_hotspots.py index 8c12554d28..766b49b5d2 100644 --- a/zerver/tests/test_hotspots.py +++ b/zerver/tests/test_hotspots.py @@ -10,7 +10,8 @@ from zerver.lib.hotspots import ( get_next_onboarding_steps, ) from zerver.lib.test_classes import ZulipTestCase -from zerver.models import OnboardingStep, UserProfile, get_realm +from zerver.models import OnboardingStep, UserProfile +from zerver.models.realms import get_realm # Splitting this out, since I imagine this will eventually have most of the diff --git a/zerver/tests/test_import_export.py b/zerver/tests/test_import_export.py index 4d32e1b911..c0b6a00c3f 100644 --- a/zerver/tests/test_import_export.py +++ b/zerver/tests/test_import_export.py @@ -86,10 +86,10 @@ from zerver.models import ( get_active_streams, get_client, get_huddle_hash, - get_realm, get_stream, ) from zerver.models.groups import SystemGroups +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot, get_user_by_delivery_email diff --git a/zerver/tests/test_integrations_dev_panel.py b/zerver/tests/test_integrations_dev_panel.py index 5fd7d70858..0b2b17b829 100644 --- a/zerver/tests/test_integrations_dev_panel.py +++ b/zerver/tests/test_integrations_dev_panel.py @@ -4,7 +4,8 @@ import orjson from django.core.exceptions import ValidationError from zerver.lib.test_classes import ZulipTestCase -from zerver.models import Message, Stream, get_realm +from zerver.models import Message, Stream +from zerver.models.realms import get_realm from zerver.models.users import get_user diff --git a/zerver/tests/test_invite.py b/zerver/tests/test_invite.py index 77595c1b80..2e17f75d57 100644 --- a/zerver/tests/test_invite.py +++ b/zerver/tests/test_invite.py @@ -63,10 +63,10 @@ from zerver.models import ( UserGroup, UserMessage, UserProfile, - get_realm, get_stream, ) from zerver.models.groups import SystemGroups +from zerver.models.realms import get_realm from zerver.models.users import get_user_by_delivery_email from zerver.views.invite import INVITATION_LINK_VALIDITY_MINUTES, get_invitee_emails_set from zerver.views.registration import accounts_home diff --git a/zerver/tests/test_management_commands.py b/zerver/tests/test_management_commands.py index 2391ec7aed..975e8eb24c 100644 --- a/zerver/tests/test_management_commands.py +++ b/zerver/tests/test_management_commands.py @@ -20,7 +20,8 @@ from zerver.actions.reactions import do_add_reaction from zerver.lib.management import ZulipBaseCommand, check_config from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import most_recent_message, stdout_suppressed -from zerver.models import Message, Reaction, Realm, Recipient, UserProfile, get_realm, get_stream +from zerver.models import Message, Reaction, Realm, Recipient, UserProfile, get_stream +from zerver.models.realms import get_realm from zerver.models.users import get_user_profile_by_email diff --git a/zerver/tests/test_markdown.py b/zerver/tests/test_markdown.py index 514f328637..d82d6ff535 100644 --- a/zerver/tests/test_markdown.py +++ b/zerver/tests/test_markdown.py @@ -68,11 +68,11 @@ from zerver.models import ( UserMessage, UserProfile, get_client, - get_realm, get_stream, linkifiers_for_realm, ) from zerver.models.groups import SystemGroups +from zerver.models.realms import get_realm class SimulatedFencedBlockPreprocessor(FencedBlockPreprocessor): diff --git a/zerver/tests/test_mattermost_importer.py b/zerver/tests/test_mattermost_importer.py index 281a668f86..d7251e1385 100644 --- a/zerver/tests/test_mattermost_importer.py +++ b/zerver/tests/test_mattermost_importer.py @@ -28,7 +28,8 @@ from zerver.data_import.user_handler import UserHandler from zerver.lib.emoji import name_to_codepoint from zerver.lib.import_realm import do_import_realm from zerver.lib.test_classes import ZulipTestCase -from zerver.models import Message, Reaction, Recipient, UserProfile, get_realm +from zerver.models import Message, Reaction, Recipient, UserProfile +from zerver.models.realms import get_realm from zerver.models.users import get_user diff --git a/zerver/tests/test_message_dict.py b/zerver/tests/test_message_dict.py index 291af89b79..c3da52d7d8 100644 --- a/zerver/tests/test_message_dict.py +++ b/zerver/tests/test_message_dict.py @@ -20,9 +20,9 @@ from zerver.models import ( Stream, UserProfile, get_display_recipient, - get_realm, get_stream, ) +from zerver.models.realms import get_realm class MessageDictTest(ZulipTestCase): diff --git a/zerver/tests/test_message_edit.py b/zerver/tests/test_message_edit.py index a23f6d7a2e..16fa688c30 100644 --- a/zerver/tests/test_message_edit.py +++ b/zerver/tests/test_message_edit.py @@ -38,11 +38,11 @@ from zerver.models import ( UserMessage, UserProfile, UserTopic, - get_realm, get_stream, ) from zerver.models.constants import MAX_TOPIC_NAME_LENGTH from zerver.models.groups import SystemGroups +from zerver.models.realms import get_realm if TYPE_CHECKING: from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse diff --git a/zerver/tests/test_message_fetch.py b/zerver/tests/test_message_fetch.py index a4391638c0..801d23b7f4 100644 --- a/zerver/tests/test_message_fetch.py +++ b/zerver/tests/test_message_fetch.py @@ -58,9 +58,9 @@ from zerver.models import ( UserProfile, UserTopic, get_display_recipient, - get_realm, get_stream, ) +from zerver.models.realms import get_realm from zerver.views.message_fetch import get_messages_backend if TYPE_CHECKING: diff --git a/zerver/tests/test_message_flags.py b/zerver/tests/test_message_flags.py index abb5c04436..be1082be0a 100644 --- a/zerver/tests/test_message_flags.py +++ b/zerver/tests/test_message_flags.py @@ -33,9 +33,9 @@ from zerver.models import ( UserMessage, UserProfile, UserTopic, - get_realm, get_stream, ) +from zerver.models.realms import get_realm if TYPE_CHECKING: from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse diff --git a/zerver/tests/test_message_notification_emails.py b/zerver/tests/test_message_notification_emails.py index ac3fda6d6a..c734219bbf 100644 --- a/zerver/tests/test_message_notification_emails.py +++ b/zerver/tests/test_message_notification_emails.py @@ -33,9 +33,9 @@ from zerver.models import ( UserProfile, UserTopic, get_name_keyed_dict_for_active_realm_emoji, - get_realm, get_stream, ) +from zerver.models.realms import get_realm class TestMessageNotificationEmails(ZulipTestCase): diff --git a/zerver/tests/test_message_send.py b/zerver/tests/test_message_send.py index a523922005..b8b268c0b9 100644 --- a/zerver/tests/test_message_send.py +++ b/zerver/tests/test_message_send.py @@ -57,11 +57,11 @@ from zerver.models import ( UserMessage, UserProfile, get_or_create_huddle, - get_realm, get_stream, ) from zerver.models.constants import MAX_TOPIC_NAME_LENGTH from zerver.models.groups import SystemGroups +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot, get_user from zerver.views.message_send import InvalidMirrorInputError diff --git a/zerver/tests/test_message_topics.py b/zerver/tests/test_message_topics.py index ec65b21d28..a0b72b2d22 100644 --- a/zerver/tests/test_message_topics.py +++ b/zerver/tests/test_message_topics.py @@ -6,7 +6,8 @@ from zerver.actions.streams import do_change_stream_permission from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import timeout_mock from zerver.lib.timeout import TimeoutExpiredError -from zerver.models import Message, UserMessage, get_client, get_realm, get_stream +from zerver.models import Message, UserMessage, get_client, get_stream +from zerver.models.realms import get_realm class TopicHistoryTest(ZulipTestCase): diff --git a/zerver/tests/test_middleware.py b/zerver/tests/test_middleware.py index 6182ba3806..e701719b68 100644 --- a/zerver/tests/test_middleware.py +++ b/zerver/tests/test_middleware.py @@ -11,7 +11,7 @@ from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import HostRequestMock from zerver.lib.utils import assert_is_not_none from zerver.middleware import LogRequests, is_slow_query, write_log_line -from zerver.models import get_realm +from zerver.models.realms import get_realm from zilencer.models import RemoteZulipServer diff --git a/zerver/tests/test_mirror_users.py b/zerver/tests/test_mirror_users.py index 24464d1e1b..133521f646 100644 --- a/zerver/tests/test_mirror_users.py +++ b/zerver/tests/test_mirror_users.py @@ -8,7 +8,8 @@ from zerver.actions.message_send import create_mirror_user_if_needed from zerver.lib.create_user import create_user_profile from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import reset_email_visibility_to_everyone_in_zulip_realm -from zerver.models import UserProfile, get_client, get_realm +from zerver.models import UserProfile, get_client +from zerver.models.realms import get_realm from zerver.models.users import get_user from zerver.views.message_send import InvalidMirrorInputError, create_mirrored_message_users diff --git a/zerver/tests/test_new_users.py b/zerver/tests/test_new_users.py index cb2e392fc2..14e90cf88e 100644 --- a/zerver/tests/test_new_users.py +++ b/zerver/tests/test_new_users.py @@ -13,7 +13,8 @@ from zerver.actions.create_user import notify_new_user from zerver.actions.user_settings import do_change_user_setting from zerver.lib.initial_password import initial_password from zerver.lib.test_classes import ZulipTestCase -from zerver.models import Message, Realm, Recipient, Stream, UserProfile, get_realm +from zerver.models import Message, Realm, Recipient, Stream, UserProfile +from zerver.models.realms import get_realm from zerver.signals import JUST_CREATED_THRESHOLD, get_device_browser, get_device_os if sys.version_info < (3, 9): # nocoverage diff --git a/zerver/tests/test_outgoing_webhook_interfaces.py b/zerver/tests/test_outgoing_webhook_interfaces.py index f66510cb0f..57ff699fef 100644 --- a/zerver/tests/test_outgoing_webhook_interfaces.py +++ b/zerver/tests/test_outgoing_webhook_interfaces.py @@ -12,7 +12,8 @@ from zerver.lib.outgoing_webhook import get_service_interface_class, process_suc from zerver.lib.test_classes import ZulipTestCase from zerver.lib.timestamp import datetime_to_timestamp from zerver.lib.topic import TOPIC_NAME -from zerver.models import SLACK_INTERFACE, Message, NotificationTriggers, get_realm, get_stream +from zerver.models import SLACK_INTERFACE, Message, NotificationTriggers, get_stream +from zerver.models.realms import get_realm from zerver.models.users import get_user from zerver.openapi.openapi import validate_against_openapi_schema diff --git a/zerver/tests/test_outgoing_webhook_system.py b/zerver/tests/test_outgoing_webhook_system.py index cda83d5ff3..f6dad2df96 100644 --- a/zerver/tests/test_outgoing_webhook_system.py +++ b/zerver/tests/test_outgoing_webhook_system.py @@ -19,7 +19,8 @@ from zerver.lib.test_classes import ZulipTestCase from zerver.lib.topic import TOPIC_NAME from zerver.lib.url_encoding import near_message_url from zerver.lib.users import add_service -from zerver.models import Recipient, Service, UserProfile, get_realm, get_stream +from zerver.models import Recipient, Service, UserProfile, get_stream +from zerver.models.realms import get_realm class ResponseMock: diff --git a/zerver/tests/test_presence.py b/zerver/tests/test_presence.py index cfbee31d56..84a63328f2 100644 --- a/zerver/tests/test_presence.py +++ b/zerver/tests/test_presence.py @@ -18,8 +18,8 @@ from zerver.models import ( UserActivityInterval, UserPresence, UserProfile, - get_realm, ) +from zerver.models.realms import get_realm class TestClientModel(ZulipTestCase): diff --git a/zerver/tests/test_push_notifications.py b/zerver/tests/test_push_notifications.py index 0441d988f0..27a7ddbb3d 100644 --- a/zerver/tests/test_push_notifications.py +++ b/zerver/tests/test_push_notifications.py @@ -91,9 +91,9 @@ from zerver.models import ( UserProfile, UserTopic, get_client, - get_realm, get_stream, ) +from zerver.models.realms import get_realm from zilencer.models import RemoteZulipServerAuditLog from zilencer.views import DevicesToCleanUpDict diff --git a/zerver/tests/test_queue_worker.py b/zerver/tests/test_queue_worker.py index c0a90309d2..452fbca45c 100644 --- a/zerver/tests/test_queue_worker.py +++ b/zerver/tests/test_queue_worker.py @@ -32,9 +32,9 @@ from zerver.models import ( UserActivity, UserProfile, get_client, - get_realm, get_stream, ) +from zerver.models.realms import get_realm from zerver.tornado.event_queue import build_offline_notification from zerver.worker import queue_processors from zerver.worker.queue_processors import ( diff --git a/zerver/tests/test_reactions.py b/zerver/tests/test_reactions.py index b015896e04..fdd94adc21 100644 --- a/zerver/tests/test_reactions.py +++ b/zerver/tests/test_reactions.py @@ -12,7 +12,8 @@ from zerver.lib.exceptions import JsonableError from zerver.lib.message import extract_message_dict from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import zulip_reaction_info -from zerver.models import Message, Reaction, RealmEmoji, UserMessage, get_realm +from zerver.models import Message, Reaction, RealmEmoji, UserMessage +from zerver.models.realms import get_realm if TYPE_CHECKING: from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py index 837f5d4fa0..a2fdbf446e 100644 --- a/zerver/tests/test_realm.py +++ b/zerver/tests/test_realm.py @@ -53,10 +53,10 @@ from zerver.models import ( UserGroupMembership, UserMessage, UserProfile, - get_realm, get_stream, ) from zerver.models.groups import SystemGroups +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot, get_user_profile_by_id diff --git a/zerver/tests/test_realm_domains.py b/zerver/tests/test_realm_domains.py index 9faacd31ec..e78b62048a 100644 --- a/zerver/tests/test_realm_domains.py +++ b/zerver/tests/test_realm_domains.py @@ -10,7 +10,8 @@ from zerver.actions.users import do_change_user_role from zerver.lib.domains import validate_domain from zerver.lib.email_validation import email_allowed_for_realm from zerver.lib.test_classes import ZulipTestCase -from zerver.models import DomainNotAllowedForRealmError, RealmDomain, UserProfile, get_realm +from zerver.models import RealmDomain, UserProfile +from zerver.models.realms import DomainNotAllowedForRealmError, get_realm class RealmDomainTest(ZulipTestCase): diff --git a/zerver/tests/test_realm_emoji.py b/zerver/tests/test_realm_emoji.py index 28c5921729..603caa3723 100644 --- a/zerver/tests/test_realm_emoji.py +++ b/zerver/tests/test_realm_emoji.py @@ -9,7 +9,8 @@ from zerver.lib.exceptions import JsonableError from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import get_test_image_file from zerver.lib.upload.base import BadImageError -from zerver.models import Realm, RealmEmoji, UserProfile, get_realm +from zerver.models import Realm, RealmEmoji, UserProfile +from zerver.models.realms import get_realm class RealmEmojiTest(ZulipTestCase): diff --git a/zerver/tests/test_realm_playgrounds.py b/zerver/tests/test_realm_playgrounds.py index 97ac4fe43e..45e23b8de1 100644 --- a/zerver/tests/test_realm_playgrounds.py +++ b/zerver/tests/test_realm_playgrounds.py @@ -1,6 +1,7 @@ from zerver.actions.realm_playgrounds import check_add_realm_playground from zerver.lib.test_classes import ZulipTestCase -from zerver.models import RealmPlayground, get_realm +from zerver.models import RealmPlayground +from zerver.models.realms import get_realm class RealmPlaygroundTests(ZulipTestCase): diff --git a/zerver/tests/test_retention.py b/zerver/tests/test_retention.py index 340fee5689..55d8b7dbcc 100644 --- a/zerver/tests/test_retention.py +++ b/zerver/tests/test_retention.py @@ -38,9 +38,9 @@ from zerver.models import ( SubMessage, UserMessage, get_client, - get_realm, get_stream, ) +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot # Class with helper functions useful for testing archiving of reactions: diff --git a/zerver/tests/test_rocketchat_importer.py b/zerver/tests/test_rocketchat_importer.py index bc2b98bb15..f11533b5d2 100644 --- a/zerver/tests/test_rocketchat_importer.py +++ b/zerver/tests/test_rocketchat_importer.py @@ -28,7 +28,8 @@ from zerver.data_import.user_handler import UserHandler from zerver.lib.emoji import name_to_codepoint from zerver.lib.import_realm import do_import_realm from zerver.lib.test_classes import ZulipTestCase -from zerver.models import Message, Reaction, Recipient, UserProfile, get_realm +from zerver.models import Message, Reaction, Recipient, UserProfile +from zerver.models.realms import get_realm from zerver.models.users import get_user diff --git a/zerver/tests/test_scim.py b/zerver/tests/test_scim.py index 1bab9984d4..600c191de5 100644 --- a/zerver/tests/test_scim.py +++ b/zerver/tests/test_scim.py @@ -10,7 +10,8 @@ from typing_extensions import override from zerver.actions.user_settings import do_change_full_name from zerver.lib.scim import ZulipSCIMUser from zerver.lib.test_classes import ZulipTestCase -from zerver.models import UserProfile, get_realm +from zerver.models import UserProfile +from zerver.models.realms import get_realm if TYPE_CHECKING: from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse diff --git a/zerver/tests/test_service_bot_system.py b/zerver/tests/test_service_bot_system.py index 5eda173fbc..131b30d803 100644 --- a/zerver/tests/test_service_bot_system.py +++ b/zerver/tests/test_service_bot_system.py @@ -15,7 +15,8 @@ from zerver.lib.bot_storage import StateError from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import mock_queue_publish from zerver.lib.validator import check_string -from zerver.models import NotificationTriggers, Recipient, UserProfile, get_realm +from zerver.models import NotificationTriggers, Recipient, UserProfile +from zerver.models.realms import get_realm BOT_TYPE_TO_QUEUE_NAME = { UserProfile.OUTGOING_WEBHOOK_BOT: "outgoing_webhooks", diff --git a/zerver/tests/test_sessions.py b/zerver/tests/test_sessions.py index ccebe1980f..1d77a28a27 100644 --- a/zerver/tests/test_sessions.py +++ b/zerver/tests/test_sessions.py @@ -18,7 +18,8 @@ from zerver.lib.sessions import ( user_sessions, ) from zerver.lib.test_classes import ZulipTestCase -from zerver.models import Realm, UserProfile, get_realm +from zerver.models import Realm, UserProfile +from zerver.models.realms import get_realm class TestSessions(ZulipTestCase): diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index f41e1d7ab6..7210e55149 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -76,9 +76,9 @@ from zerver.models import ( Subscription, UserMessage, UserProfile, - get_realm, get_stream, ) +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot, get_user, get_user_by_delivery_email from zerver.views.auth import redirect_and_log_into_subdomain, start_two_factor_auth from zerver.views.development.registration import confirmation_key diff --git a/zerver/tests/test_slack_importer.py b/zerver/tests/test_slack_importer.py index b1c6d8473a..09d27bf805 100644 --- a/zerver/tests/test_slack_importer.py +++ b/zerver/tests/test_slack_importer.py @@ -48,7 +48,8 @@ from zerver.lib.import_realm import do_import_realm from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import read_test_image_file from zerver.lib.topic import EXPORT_TOPIC_NAME -from zerver.models import Message, Realm, RealmAuditLog, Recipient, UserProfile, get_realm +from zerver.models import Message, Realm, RealmAuditLog, Recipient, UserProfile +from zerver.models.realms import get_realm def remove_folder(path: str) -> None: diff --git a/zerver/tests/test_soft_deactivation.py b/zerver/tests/test_soft_deactivation.py index 71f484f7e8..d6c9309235 100644 --- a/zerver/tests/test_soft_deactivation.py +++ b/zerver/tests/test_soft_deactivation.py @@ -27,9 +27,9 @@ from zerver.models import ( UserActivity, UserMessage, UserProfile, - get_realm, get_stream, ) +from zerver.models.realms import get_realm logger_string = "zulip.soft_deactivation" diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py index 23bb63cedb..a9a232cffe 100644 --- a/zerver/tests/test_subs.py +++ b/zerver/tests/test_subs.py @@ -105,11 +105,11 @@ from zerver.models import ( UserMessage, UserProfile, get_default_stream_groups, - get_realm, get_stream, validate_attachment_request, validate_attachment_request_for_spectator_access, ) +from zerver.models.realms import get_realm from zerver.models.users import active_non_guest_user_ids, get_user, get_user_profile_by_id_in_realm from zerver.views.streams import compose_views diff --git a/zerver/tests/test_upload.py b/zerver/tests/test_upload.py index 115b81cd75..9efad08627 100644 --- a/zerver/tests/test_upload.py +++ b/zerver/tests/test_upload.py @@ -45,9 +45,9 @@ from zerver.models import ( Realm, RealmDomain, UserProfile, - get_realm, validate_attachment_request, ) +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot, get_user_by_delivery_email diff --git a/zerver/tests/test_upload_local.py b/zerver/tests/test_upload_local.py index 368436c7fe..850726a293 100644 --- a/zerver/tests/test_upload_local.py +++ b/zerver/tests/test_upload_local.py @@ -22,7 +22,8 @@ from zerver.lib.upload import ( ) from zerver.lib.upload.base import DEFAULT_EMOJI_SIZE, MEDIUM_AVATAR_SIZE, resize_avatar from zerver.lib.upload.local import write_local_file -from zerver.models import Attachment, RealmEmoji, get_realm +from zerver.models import Attachment, RealmEmoji +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot diff --git a/zerver/tests/test_upload_s3.py b/zerver/tests/test_upload_s3.py index 49716a0a6d..6e5c4c1993 100644 --- a/zerver/tests/test_upload_s3.py +++ b/zerver/tests/test_upload_s3.py @@ -36,7 +36,8 @@ from zerver.lib.upload.base import ( resize_avatar, ) from zerver.lib.upload.s3 import S3UploadBackend -from zerver.models import Attachment, RealmEmoji, UserProfile, get_realm +from zerver.models import Attachment, RealmEmoji, UserProfile +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot diff --git a/zerver/tests/test_user_groups.py b/zerver/tests/test_user_groups.py index dde4dced70..61b3a50770 100644 --- a/zerver/tests/test_user_groups.py +++ b/zerver/tests/test_user_groups.py @@ -32,15 +32,9 @@ from zerver.lib.user_groups import ( is_user_in_group, user_groups_in_realm_serialized, ) -from zerver.models import ( - GroupGroupMembership, - Realm, - UserGroup, - UserGroupMembership, - UserProfile, - get_realm, -) +from zerver.models import GroupGroupMembership, Realm, UserGroup, UserGroupMembership, UserProfile from zerver.models.groups import SystemGroups +from zerver.models.realms import get_realm class UserGroupTestCase(ZulipTestCase): diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py index 91ee3a0701..6a803ddb66 100644 --- a/zerver/tests/test_users.py +++ b/zerver/tests/test_users.py @@ -59,7 +59,6 @@ from zerver.lib.users import ( from zerver.lib.utils import assert_is_not_none from zerver.models import ( CustomProfileField, - InvalidFakeEmailDomainError, Message, OnboardingStep, PreregistrationUser, @@ -76,11 +75,10 @@ from zerver.models import ( check_valid_user_ids, filter_to_valid_prereg_users, get_client, - get_fake_email_domain, - get_realm, get_stream, ) from zerver.models.groups import SystemGroups +from zerver.models.realms import InvalidFakeEmailDomainError, get_fake_email_domain, get_realm from zerver.models.users import ( get_source_profile, get_system_bot, diff --git a/zerver/tests/test_webhooks_common.py b/zerver/tests/test_webhooks_common.py index 444bd0d0e4..f4d55e2a33 100644 --- a/zerver/tests/test_webhooks_common.py +++ b/zerver/tests/test_webhooks_common.py @@ -21,7 +21,8 @@ from zerver.lib.webhooks.common import ( standardize_headers, validate_extract_webhook_http_header, ) -from zerver.models import UserProfile, get_realm +from zerver.models import UserProfile +from zerver.models.realms import get_realm from zerver.models.users import get_user diff --git a/zerver/tests/test_zephyr.py b/zerver/tests/test_zephyr.py index 0721ce61ad..1ac021ce07 100644 --- a/zerver/tests/test_zephyr.py +++ b/zerver/tests/test_zephyr.py @@ -6,7 +6,7 @@ import orjson from zerver.lib.test_classes import ZulipTestCase from zerver.lib.users import get_api_key -from zerver.models import get_realm +from zerver.models.realms import get_realm from zerver.models.users import get_user if TYPE_CHECKING: diff --git a/zerver/transaction_tests/test_user_groups.py b/zerver/transaction_tests/test_user_groups.py index 42d407e3ba..0a5b639e38 100644 --- a/zerver/transaction_tests/test_user_groups.py +++ b/zerver/transaction_tests/test_user_groups.py @@ -12,7 +12,8 @@ from zerver.lib.exceptions import JsonableError from zerver.lib.test_classes import ZulipTransactionTestCase from zerver.lib.test_helpers import HostRequestMock from zerver.lib.user_groups import access_user_group_by_id -from zerver.models import Realm, UserGroup, UserProfile, get_realm +from zerver.models import Realm, UserGroup, UserProfile +from zerver.models.realms import get_realm from zerver.views.user_groups import update_subgroups_of_user_group BARRIER: Optional[threading.Barrier] = None diff --git a/zerver/views/auth.py b/zerver/views/auth.py index 6f57ba1ed5..f434371c82 100644 --- a/zerver/views/auth.py +++ b/zerver/views/auth.py @@ -75,8 +75,8 @@ from zerver.models import ( Realm, UserProfile, filter_to_valid_prereg_users, - get_realm, ) +from zerver.models.realms import get_realm from zerver.models.users import remote_user_to_email from zerver.signals import email_on_new_login from zerver.views.errors import config_error diff --git a/zerver/views/development/dev_login.py b/zerver/views/development/dev_login.py index be06d18c0e..f26296c47c 100644 --- a/zerver/views/development/dev_login.py +++ b/zerver/views/development/dev_login.py @@ -20,7 +20,8 @@ from zerver.lib.response import json_success from zerver.lib.subdomains import get_subdomain from zerver.lib.users import get_api_key from zerver.lib.validator import validate_login_email -from zerver.models import Realm, UserProfile, get_realm +from zerver.models import Realm, UserProfile +from zerver.models.realms import get_realm from zerver.views.auth import get_safe_redirect_to from zerver.views.errors import config_error from zproject.backends import dev_auth_enabled diff --git a/zerver/views/development/email_log.py b/zerver/views/development/email_log.py index e359cecee2..1081634321 100644 --- a/zerver/views/development/email_log.py +++ b/zerver/views/development/email_log.py @@ -16,7 +16,8 @@ from zerver.actions.users import change_user_is_active from zerver.lib.email_notifications import enqueue_welcome_emails, send_account_registered_email from zerver.lib.request import REQ, has_request_variables from zerver.lib.response import json_success -from zerver.models import Realm, get_realm, get_realm_stream +from zerver.models import Realm, get_realm_stream +from zerver.models.realms import get_realm from zerver.models.users import get_user_by_delivery_email from zerver.views.invite import INVITATION_LINK_VALIDITY_MINUTES from zproject.email_backends import get_forward_address, set_forward_address diff --git a/zerver/views/development/integrations.py b/zerver/views/development/integrations.py index 6ba0f006bd..0edab9d494 100644 --- a/zerver/views/development/integrations.py +++ b/zerver/views/development/integrations.py @@ -14,7 +14,8 @@ from zerver.lib.request import REQ, has_request_variables from zerver.lib.response import json_success from zerver.lib.validator import check_bool from zerver.lib.webhooks.common import get_fixture_http_headers, standardize_headers -from zerver.models import UserProfile, get_realm +from zerver.models import UserProfile +from zerver.models.realms import get_realm if TYPE_CHECKING: from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse diff --git a/zerver/views/realm_domains.py b/zerver/views/realm_domains.py index c27de2cdbc..65ac5a0fe8 100644 --- a/zerver/views/realm_domains.py +++ b/zerver/views/realm_domains.py @@ -13,7 +13,8 @@ from zerver.lib.exceptions import JsonableError from zerver.lib.request import REQ, has_request_variables from zerver.lib.response import json_success from zerver.lib.validator import check_bool -from zerver.models import RealmDomain, UserProfile, get_realm_domains +from zerver.models import RealmDomain, UserProfile +from zerver.models.realms import get_realm_domains def list_realm_domains(request: HttpRequest, user_profile: UserProfile) -> HttpResponse: diff --git a/zerver/views/registration.py b/zerver/views/registration.py index 6f25640037..6c4d3884d4 100644 --- a/zerver/views/registration.py +++ b/zerver/views/registration.py @@ -73,9 +73,6 @@ from zerver.lib.validator import ( ) from zerver.lib.zephyr import compute_mit_user_fullname from zerver.models import ( - DisposableEmailError, - DomainNotAllowedForRealmError, - EmailContainsPlusError, MultiuseInvite, PreregistrationRealm, PreregistrationUser, @@ -84,11 +81,16 @@ from zerver.models import ( Stream, UserProfile, get_default_stream_groups, +) +from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH +from zerver.models.realms import ( + DisposableEmailError, + DomainNotAllowedForRealmError, + EmailContainsPlusError, get_org_type_display_name, get_realm, name_changes_disabled, ) -from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH from zerver.models.users import get_source_profile, get_user_by_delivery_email from zerver.views.auth import ( create_preregistration_realm, diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py index c073f35cbc..410e309e2e 100644 --- a/zerver/views/user_settings.py +++ b/zerver/views/user_settings.py @@ -52,12 +52,8 @@ from zerver.lib.validator import ( check_string_in, check_timezone, ) -from zerver.models import ( - EmailChangeStatus, - UserProfile, - avatar_changes_disabled, - name_changes_disabled, -) +from zerver.models import EmailChangeStatus, UserProfile +from zerver.models.realms import avatar_changes_disabled, name_changes_disabled from zerver.views.auth import redirect_to_deactivation_notice from zproject.backends import check_password_strength, email_belongs_to_ldap diff --git a/zerver/views/users.py b/zerver/views/users.py index 5f9647b281..ca1317bde2 100644 --- a/zerver/views/users.py +++ b/zerver/views/users.py @@ -86,14 +86,12 @@ from zerver.lib.validator import ( check_union, check_url, ) -from zerver.models import ( +from zerver.models import Service, Stream, UserProfile +from zerver.models.realms import ( DisposableEmailError, DomainNotAllowedForRealmError, EmailContainsPlusError, InvalidFakeEmailDomainError, - Service, - Stream, - UserProfile, ) from zerver.models.users import ( get_user_by_delivery_email, diff --git a/zerver/views/video_calls.py b/zerver/views/video_calls.py index 1c53c7f77e..09f2400379 100644 --- a/zerver/views/video_calls.py +++ b/zerver/views/video_calls.py @@ -32,7 +32,8 @@ from zerver.lib.response import json_success from zerver.lib.subdomains import get_subdomain from zerver.lib.url_encoding import append_url_query_string from zerver.lib.validator import check_bool, check_dict, check_string -from zerver.models import UserProfile, get_realm +from zerver.models import UserProfile +from zerver.models.realms import get_realm class VideoCallSession(OutgoingSession): diff --git a/zerver/webhooks/helloworld/tests.py b/zerver/webhooks/helloworld/tests.py index 3ee3010f56..92cdee952e 100644 --- a/zerver/webhooks/helloworld/tests.py +++ b/zerver/webhooks/helloworld/tests.py @@ -1,7 +1,7 @@ from django.conf import settings from zerver.lib.test_classes import WebhookTestCase -from zerver.models import get_realm +from zerver.models.realms import get_realm from zerver.models.users import get_system_bot diff --git a/zerver/webhooks/teamcity/tests.py b/zerver/webhooks/teamcity/tests.py index 50ac0d8bf7..39a11b4d80 100644 --- a/zerver/webhooks/teamcity/tests.py +++ b/zerver/webhooks/teamcity/tests.py @@ -2,7 +2,8 @@ import orjson from zerver.lib.send_email import FromAddress from zerver.lib.test_classes import WebhookTestCase -from zerver.models import Recipient, get_realm +from zerver.models import Recipient +from zerver.models.realms import get_realm from zerver.models.users import get_user_by_delivery_email from zerver.webhooks.teamcity.view import MISCONFIGURED_PAYLOAD_TYPE_ERROR_MESSAGE diff --git a/zilencer/management/commands/add_mock_conversation.py b/zilencer/management/commands/add_mock_conversation.py index c4f50876cf..b424faaafb 100644 --- a/zilencer/management/commands/add_mock_conversation.py +++ b/zilencer/management/commands/add_mock_conversation.py @@ -11,7 +11,8 @@ from zerver.actions.user_settings import do_change_avatar_fields from zerver.lib.emoji import get_emoji_data from zerver.lib.streams import ensure_stream from zerver.lib.upload import upload_avatar_image -from zerver.models import Message, UserProfile, get_realm +from zerver.models import Message, UserProfile +from zerver.models.realms import get_realm class Command(BaseCommand): diff --git a/zilencer/management/commands/populate_billing_realms.py b/zilencer/management/commands/populate_billing_realms.py index 752e826b75..941cd1682e 100644 --- a/zilencer/management/commands/populate_billing_realms.py +++ b/zilencer/management/commands/populate_billing_realms.py @@ -26,7 +26,8 @@ from zerver.actions.streams import bulk_add_subscriptions from zerver.apps import flush_cache from zerver.lib.remote_server import get_realms_info_for_push_bouncer from zerver.lib.streams import create_stream_if_needed -from zerver.models import Realm, UserProfile, get_realm +from zerver.models import Realm, UserProfile +from zerver.models.realms import get_realm from zilencer.models import ( RemoteRealm, RemoteRealmBillingUser, diff --git a/zilencer/management/commands/populate_db.py b/zilencer/management/commands/populate_db.py index eca2eefeb6..3cde92da31 100644 --- a/zilencer/management/commands/populate_db.py +++ b/zilencer/management/commands/populate_db.py @@ -67,9 +67,9 @@ from zerver.models import ( flush_alert_word, get_client, get_or_create_huddle, - get_realm, get_stream, ) +from zerver.models.realms import get_realm from zerver.models.users import get_user, get_user_by_delivery_email, get_user_profile_by_id from zilencer.models import RemoteRealm, RemoteZulipServer from zilencer.views import update_remote_realm_data_for_server diff --git a/zilencer/management/commands/sync_api_key.py b/zilencer/management/commands/sync_api_key.py index 84458b2f47..dbd1a042a2 100644 --- a/zilencer/management/commands/sync_api_key.py +++ b/zilencer/management/commands/sync_api_key.py @@ -5,7 +5,8 @@ from typing import Any from django.core.management.base import BaseCommand from typing_extensions import override -from zerver.models import UserProfile, get_realm +from zerver.models import UserProfile +from zerver.models.realms import get_realm from zerver.models.users import get_user_by_delivery_email diff --git a/zproject/backends.py b/zproject/backends.py index 3a2fdd2fb5..ecec01c762 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -103,9 +103,6 @@ from zerver.lib.url_encoding import append_url_query_string from zerver.lib.users import check_full_name, validate_user_custom_profile_field from zerver.models import ( CustomProfileField, - DisposableEmailError, - DomainNotAllowedForRealmError, - EmailContainsPlusError, PreregistrationRealm, PreregistrationUser, Realm, @@ -113,6 +110,11 @@ from zerver.models import ( UserGroupMembership, UserProfile, custom_profile_fields_for_realm, +) +from zerver.models.realms import ( + DisposableEmailError, + DomainNotAllowedForRealmError, + EmailContainsPlusError, get_realm, supported_auth_backends, )