diff --git a/templates/zerver/realm_creation_form.html b/templates/zerver/realm_creation_form.html index 84115a7d33..8df7469db6 100644 --- a/templates/zerver/realm_creation_form.html +++ b/templates/zerver/realm_creation_form.html @@ -85,4 +85,26 @@ {% endif %} + + {% if is_realm_import_enabled %} +
+
+ +
+ {% trans %} + You can also import from + Mattermost or + Rocket.Chat. + {% endtrans %} +
+
+ +
+ {% endif %} diff --git a/zerver/context_processors.py b/zerver/context_processors.py index 4377a02a8b..af71678675 100644 --- a/zerver/context_processors.py +++ b/zerver/context_processors.py @@ -22,7 +22,7 @@ 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 +from zerver.models import PreregistrationRealm, Realm, UserProfile from zerver.models.realms import get_realm from zproject.backends import ( AUTH_BACKEND_NAME_MAP, @@ -295,5 +295,6 @@ def get_realm_create_form_context() -> dict[str, Any]: "root_domain_available": is_root_domain_available(), "sorted_realm_types": sorted(Realm.ORG_TYPES.values(), key=lambda d: d["display_order"]), "is_realm_import_enabled": is_realm_import_enabled(), + "import_from_choices": PreregistrationRealm.IMPORT_FROM_CHOICES, } return context diff --git a/zerver/forms.py b/zerver/forms.py index acf20d787c..c5b331ba21 100644 --- a/zerver/forms.py +++ b/zerver/forms.py @@ -38,7 +38,7 @@ from zerver.lib.name_restrictions import is_reserved_subdomain from zerver.lib.rate_limiter import RateLimitedObject, rate_limit_request_by_ip from zerver.lib.subdomains import get_subdomain, is_root_domain_available from zerver.lib.users import check_full_name -from zerver.models import Realm, UserProfile +from zerver.models import PreregistrationRealm, Realm, UserProfile from zerver.models.realm_audit_logs import RealmAuditLog from zerver.models.realms import ( DisposableEmailError, @@ -333,11 +333,19 @@ class HomepageForm(forms.Form): class RealmCreationForm(RealmDetailsForm): # This form determines whether users can create a new realm. email = forms.EmailField(validators=[email_not_system_bot, email_is_not_disposable]) + import_from = forms.ChoiceField( + choices=PreregistrationRealm.IMPORT_FROM_CHOICES, + required=False, + ) def __init__(self, *args: Any, **kwargs: Any) -> None: kwargs["realm_creation"] = True super().__init__(*args, **kwargs) + def clean_import_from(self) -> str: + # Convert "" to "none". + return self.cleaned_data["import_from"] or "none" + class AltchaWidget(forms.TextInput): @override diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index a1bf1c19b0..792e7ed290 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -898,6 +898,7 @@ Output: realm_default_language: str = "en", realm_in_root_domain: str | None = None, captcha: str | None = None, + import_from: str = "none", ) -> "TestHttpResponse": payload = { "email": email, @@ -905,6 +906,7 @@ Output: "realm_type": realm_type, "realm_default_language": realm_default_language, "realm_subdomain": realm_subdomain, + "import_from": import_from, } if captcha is not None: payload["captcha"] = captcha diff --git a/zerver/models/prereg_users.py b/zerver/models/prereg_users.py index 93515fde08..cf754ba1aa 100644 --- a/zerver/models/prereg_users.py +++ b/zerver/models/prereg_users.py @@ -50,6 +50,10 @@ class PreregistrationRealm(models.Model): UserProfile, null=True, related_name="+", on_delete=models.SET_NULL ) + IMPORT_FROM_CHOICES = [ + ("none", "Don't import"), + ("slack", "Import from Slack"), + ] data_import_metadata = models.JSONField(default=dict, encoder=DjangoJSONEncoder) diff --git a/zerver/tests/test_management_commands.py b/zerver/tests/test_management_commands.py index afc4bcf0d3..fbea9ff2a1 100644 --- a/zerver/tests/test_management_commands.py +++ b/zerver/tests/test_management_commands.py @@ -306,6 +306,7 @@ class TestGenerateRealmCreationLink(ZulipTestCase): "realm_type": Realm.ORG_TYPES["business"]["id"], "realm_default_language": "en", "realm_subdomain": "custom-test", + "import_from": "none", }, ) self.assertEqual(result.status_code, 302) @@ -334,6 +335,7 @@ class TestGenerateRealmCreationLink(ZulipTestCase): "realm_type": Realm.ORG_TYPES["business"]["id"], "realm_default_language": "en", "realm_subdomain": string_id, + "import_from": "none", }, ) self.assertEqual(result.status_code, 302) diff --git a/zerver/tests/test_tusd.py b/zerver/tests/test_tusd.py index df6706369b..0ceb9cf99e 100644 --- a/zerver/tests/test_tusd.py +++ b/zerver/tests/test_tusd.py @@ -311,12 +311,10 @@ class TusdPreCreateTest(ZulipTestCase): email, realm_subdomain="ete-slack-import", realm_name="Slack import end to end", - # TODO: Uncomment after adding support to form. - # import_from="slack", + import_from="slack", ) prereg_realm = PreregistrationRealm.objects.get(email=email) - # TODO: Uncomment after adding support to form. - # self.assertEqual(prereg_realm.data_import_metadata["import_from"], "slack") + self.assertEqual(prereg_realm.data_import_metadata["import_from"], "slack") prereg_realm.data_import_metadata["import_from"] = "slack" prereg_realm.save() @@ -392,12 +390,10 @@ class TusdPreCreateTest(ZulipTestCase): email, realm_subdomain="ete-slack-import", realm_name="Slack import end to end", - # TODO: Uncomment after adding support to form. - # import_from="slack", + import_from="slack", ) prereg_realm = PreregistrationRealm.objects.get(email=email) - # TODO: Uncomment after adding support to form. - # self.assertEqual(prereg_realm.data_import_metadata["import_from"], "slack") + self.assertEqual(prereg_realm.data_import_metadata["import_from"], "slack") prereg_realm.data_import_metadata["import_from"] = "slack" prereg_realm.save() @@ -428,12 +424,10 @@ class TusdPreCreateTest(ZulipTestCase): email, realm_subdomain="ete-slack-import", realm_name="Slack import end to end", - # TODO: Uncomment after adding support to form. - # import_from="slack", + import_from="slack", ) prereg_realm = PreregistrationRealm.objects.get(email=email) - # TODO: Uncomment after adding support to form. - # self.assertEqual(prereg_realm.data_import_metadata["import_from"], "slack") + self.assertEqual(prereg_realm.data_import_metadata["import_from"], "slack") prereg_realm.data_import_metadata["import_from"] = "slack" prereg_realm.save() diff --git a/zerver/views/auth.py b/zerver/views/auth.py index 04ec558c31..eaed843c3e 100644 --- a/zerver/views/auth.py +++ b/zerver/views/auth.py @@ -145,6 +145,7 @@ def create_preregistration_realm( string_id: str, org_type: int, default_language: str, + import_from: str | None = None, ) -> PreregistrationRealm: return PreregistrationRealm.objects.create( email=email, @@ -152,6 +153,7 @@ def create_preregistration_realm( string_id=string_id, org_type=org_type, default_language=default_language, + data_import_metadata={"import_from": import_from}, ) diff --git a/zerver/views/registration.py b/zerver/views/registration.py index f5702d5450..cdab3cf51e 100644 --- a/zerver/views/registration.py +++ b/zerver/views/registration.py @@ -829,9 +829,15 @@ def prepare_realm_activation_url( string_id: str, org_type: int, default_language: str, + import_form: str, ) -> str: prereg_realm = create_preregistration_realm( - email, realm_name, string_id, org_type, default_language + email, + realm_name, + string_id, + org_type, + default_language, + import_form, ) activation_url = create_confirmation_link( prereg_realm, Confirmation.REALM_CREATION, no_associated_realm_object=True @@ -928,6 +934,7 @@ def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpR realm_type = form.cleaned_data["realm_type"] realm_default_language = form.cleaned_data["realm_default_language"] realm_subdomain = form.cleaned_data["realm_subdomain"] + import_from = form.cleaned_data["import_from"] activation_url = prepare_realm_activation_url( email, request.session, @@ -935,6 +942,7 @@ def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpR realm_subdomain, realm_type, realm_default_language, + import_from, ) if key_record is not None and key_record.presume_email_valid: # The user has a token created from the server command line; diff --git a/zproject/default_settings.py b/zproject/default_settings.py index a099b8f431..65d5c5a6eb 100644 --- a/zproject/default_settings.py +++ b/zproject/default_settings.py @@ -698,12 +698,12 @@ CUSTOM_AUTHENTICATION_WRAPPER_FUNCTION: Callable[..., Any] | None = None # notification. RESOLVE_TOPIC_UNDO_GRACE_PERIOD_SECONDS = 60 -# Maximum allowed size of uploaded file for realm import, in megabytes. +# Maximum allowed size of uploaded file for realm import on the web. # 0 disables import; None means no limit. # # Note that this is a limit for the size of the uploaded export # itself, not any additional files that may be imported as well. -MAX_WEB_DATA_IMPORT_SIZE_MB: int | None = 1024 +MAX_WEB_DATA_IMPORT_SIZE_MB: int | None = 0 # Minimum and maximum permitted number of days before full data # deletion when deactivating an organization. A nonzero minimum helps diff --git a/zproject/dev_settings.py b/zproject/dev_settings.py index 680867a7c2..f38e2ab043 100644 --- a/zproject/dev_settings.py +++ b/zproject/dev_settings.py @@ -226,3 +226,4 @@ TOPIC_SUMMARIZATION_MODEL = "groq/llama-3.3-70b-versatile" OUTPUT_COST_PER_GIGATOKEN = 590 INPUT_COST_PER_GIGATOKEN = 790 MAX_PER_USER_MONTHLY_AI_COST = 1 +MAX_WEB_DATA_IMPORT_SIZE_MB = 1024