diff --git a/templates/corporate/for/education.html b/templates/corporate/for/education.html
index 9ff67c0a1b..6c2c0255a3 100644
--- a/templates/corporate/for/education.html
+++ b/templates/corporate/for/education.html
@@ -304,7 +304,7 @@
- Unlimited search history
- - File storage up to 10 GB per user
+ - File storage up to 5 GB per user
- Message retention policies
- Brand Zulip with your logo
- Priority commercial support
diff --git a/templates/corporate/pricing_model.html b/templates/corporate/pricing_model.html
index fa1469cd61..68be58a894 100644
--- a/templates/corporate/pricing_model.html
+++ b/templates/corporate/pricing_model.html
@@ -67,7 +67,7 @@
Standard
- Unlimited search history
- - File storage up to 10 GB per user
+ - File storage up to 5 GB per user
- Message retention policies
- Brand Zulip with your logo
- Priority commercial support
diff --git a/zerver/models/realms.py b/zerver/models/realms.py
index 366909ed0b..8689cb153e 100644
--- a/zerver/models/realms.py
+++ b/zerver/models/realms.py
@@ -511,7 +511,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
]
UPLOAD_QUOTA_LIMITED = 5
- UPLOAD_QUOTA_STANDARD = 50
+ UPLOAD_QUOTA_STANDARD_FREE = 50
custom_upload_quota_gb = models.IntegerField(null=True)
VIDEO_CHAT_PROVIDERS = {
@@ -836,17 +836,24 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
if self.custom_upload_quota_gb is not None:
return self.custom_upload_quota_gb
- plan_type = self.plan_type
- if plan_type == Realm.PLAN_TYPE_PLUS:
- return Realm.UPLOAD_QUOTA_STANDARD
- elif plan_type == Realm.PLAN_TYPE_STANDARD:
- return Realm.UPLOAD_QUOTA_STANDARD
- elif plan_type == Realm.PLAN_TYPE_SELF_HOSTED:
+ if not settings.CORPORATE_ENABLED:
return None
- elif plan_type == Realm.PLAN_TYPE_STANDARD_FREE:
- return Realm.UPLOAD_QUOTA_STANDARD
- elif plan_type == Realm.PLAN_TYPE_LIMITED:
+
+ plan_type = self.plan_type
+ if plan_type == Realm.PLAN_TYPE_SELF_HOSTED: # nocoverage
+ return None
+ if plan_type == Realm.PLAN_TYPE_LIMITED:
return Realm.UPLOAD_QUOTA_LIMITED
+ elif plan_type == Realm.PLAN_TYPE_STANDARD_FREE:
+ return Realm.UPLOAD_QUOTA_STANDARD_FREE
+ elif plan_type in [Realm.PLAN_TYPE_STANDARD, Realm.PLAN_TYPE_PLUS]:
+ from corporate.lib.stripe import get_seat_count
+
+ # Paying customers with few users should get a reasonable minimum quota.
+ return max(
+ get_seat_count(self) * settings.UPLOAD_QUOTA_PER_USER_GB,
+ Realm.UPLOAD_QUOTA_STANDARD_FREE,
+ )
else:
raise AssertionError("Invalid plan type")
diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py
index 1806610ffe..8f3e31b594 100644
--- a/zerver/tests/test_realm.py
+++ b/zerver/tests/test_realm.py
@@ -5,7 +5,7 @@ import re
import string
from datetime import datetime, timedelta
from typing import Any, Dict, List, Union
-from unittest import mock
+from unittest import mock, skipUnless
import orjson
from django.conf import settings
@@ -15,6 +15,7 @@ from typing_extensions import override
from confirmation.models import Confirmation, create_confirmation_link
from zerver.actions.create_realm import do_change_realm_subdomain, do_create_realm
+from zerver.actions.create_user import do_create_user
from zerver.actions.message_send import (
internal_send_huddle_message,
internal_send_private_message,
@@ -60,6 +61,9 @@ from zerver.models.realms import get_realm
from zerver.models.streams import get_stream
from zerver.models.users import get_system_bot, get_user_profile_by_id
+if settings.ZILENCER_ENABLED:
+ from corporate.lib.stripe import get_seat_count
+
class RealmTest(ZulipTestCase):
def assert_user_profile_cache_gets_new_name(
@@ -909,8 +913,22 @@ class RealmTest(ZulipTestCase):
self.assertEqual(realm_audit_log.acting_user, iago)
self.assertEqual(realm.org_type, Realm.ORG_TYPES["government"]["id"])
+ @skipUnless(settings.ZILENCER_ENABLED, "requires zilencer")
def test_change_realm_plan_type(self) -> None:
realm = get_realm("zulip")
+
+ # Create additional user, so that the realm has a lot of seats for the purposes
+ # of upload quota calculation.
+ for count in range(10):
+ do_create_user(
+ f"email{count}@example.com",
+ f"password {count}",
+ realm,
+ "name",
+ role=UserProfile.ROLE_MEMBER,
+ acting_user=None,
+ )
+
iago = self.example_user("iago")
self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_SELF_HOSTED)
self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX)
@@ -938,7 +956,9 @@ class RealmTest(ZulipTestCase):
self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_STANDARD)
self.assertEqual(realm.max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX)
self.assertEqual(realm.message_visibility_limit, None)
- self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_STANDARD)
+ self.assertEqual(
+ realm.upload_quota_gb, get_seat_count(realm) * settings.UPLOAD_QUOTA_PER_USER_GB
+ )
everyone_system_group = UserGroup.objects.get(name=SystemGroups.EVERYONE, realm=realm)
self.assertEqual(realm.can_access_all_users_group_id, everyone_system_group.id)
@@ -956,7 +976,7 @@ class RealmTest(ZulipTestCase):
self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_STANDARD_FREE)
self.assertEqual(realm.max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX)
self.assertEqual(realm.message_visibility_limit, None)
- self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_STANDARD)
+ self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_STANDARD_FREE)
do_change_realm_plan_type(realm, Realm.PLAN_TYPE_LIMITED, acting_user=iago)
do_change_realm_plan_type(realm, Realm.PLAN_TYPE_PLUS, acting_user=iago)
@@ -964,7 +984,9 @@ class RealmTest(ZulipTestCase):
self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_PLUS)
self.assertEqual(realm.max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX)
self.assertEqual(realm.message_visibility_limit, None)
- self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_STANDARD)
+ self.assertEqual(
+ realm.upload_quota_gb, get_seat_count(realm) * settings.UPLOAD_QUOTA_PER_USER_GB
+ )
do_change_realm_permission_group_setting(
realm, "can_access_all_users_group", members_system_group, acting_user=None
@@ -974,7 +996,9 @@ class RealmTest(ZulipTestCase):
self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_STANDARD)
self.assertEqual(realm.max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX)
self.assertEqual(realm.message_visibility_limit, None)
- self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_STANDARD)
+ self.assertEqual(
+ realm.upload_quota_gb, get_seat_count(realm) * settings.UPLOAD_QUOTA_PER_USER_GB
+ )
self.assertEqual(realm.can_access_all_users_group_id, everyone_system_group.id)
# Test that custom_upload_quota_gb overrides the default upload_quota_gb
diff --git a/zproject/default_settings.py b/zproject/default_settings.py
index c00c1d7186..edc02eff8f 100644
--- a/zproject/default_settings.py
+++ b/zproject/default_settings.py
@@ -167,6 +167,9 @@ LOCAL_UPLOADS_DIR: Optional[str] = None
LOCAL_AVATARS_DIR: Optional[str] = None
LOCAL_FILES_DIR: Optional[str] = None
MAX_FILE_UPLOAD_SIZE = 25
+# How many GB an organization on a paid plan can upload per user,
+# on zulipchat.com.
+UPLOAD_QUOTA_PER_USER_GB = 5
# Jitsi Meet video call integration; set to None to disable integration.
JITSI_SERVER_URL: Optional[str] = "https://meet.jit.si"