models: Add plan_type to Realm.

This commit is contained in:
Vishnu Ks
2018-08-10 01:08:22 +05:30
committed by Tim Abbott
parent 6031ccff5c
commit 9bb338be11
8 changed files with 71 additions and 2 deletions

View File

@@ -3018,6 +3018,15 @@ def do_change_icon_source(realm: Realm, icon_source: str, log: bool=True) -> Non
icon_url=realm_icon_url(realm))), icon_url=realm_icon_url(realm))),
active_user_ids(realm.id)) active_user_ids(realm.id))
def do_change_plan_type(user: UserProfile, plan_type: int) -> None:
realm = user.realm
old_value = realm.plan_type
realm.plan_type = plan_type
realm.save(update_fields=['plan_type'])
RealmAuditLog.objects.create(event_type=RealmAuditLog.REALM_PLAN_TYPE_CHANGED,
realm=realm, acting_user=user, event_time=timezone_now(),
extra_data={'old_value': old_value, 'new_value': plan_type})
def do_change_default_sending_stream(user_profile: UserProfile, stream: Optional[Stream], def do_change_default_sending_stream(user_profile: UserProfile, stream: Optional[Stream],
log: bool=True) -> None: log: bool=True) -> None:
user_profile.default_sending_stream = stream user_profile.default_sending_stream = stream
@@ -3194,6 +3203,8 @@ def do_create_realm(string_id: str, name: str,
kwargs = {} # type: Dict[str, Any] kwargs = {} # type: Dict[str, Any]
if emails_restricted_to_domains is not None: if emails_restricted_to_domains is not None:
kwargs['emails_restricted_to_domains'] = emails_restricted_to_domains kwargs['emails_restricted_to_domains'] = emails_restricted_to_domains
if settings.BILLING_ENABLED:
kwargs['plan_type'] = Realm.LIMITED
realm = Realm(string_id=string_id, name=name, **kwargs) realm = Realm(string_id=string_id, name=name, **kwargs)
realm.save() realm.save()

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.14 on 2018-08-10 21:36
from __future__ import unicode_literals
from django.db import migrations, models
import zerver.models
class Migration(migrations.Migration):
dependencies = [
('zerver', '0184_rename_custom_field_types'),
]
operations = [
migrations.AddField(
model_name='realm',
name='plan_type',
# Realm.SELF_HOSTED
field=models.PositiveSmallIntegerField(default=1),
),
]

View File

@@ -223,6 +223,15 @@ class Realm(models.Model):
COMMUNITY = 2 COMMUNITY = 2
org_type = models.PositiveSmallIntegerField(default=CORPORATE) # type: int org_type = models.PositiveSmallIntegerField(default=CORPORATE) # type: int
# plan_type controls various features around resource/feature
# limitations for a Zulip organization on multi-tenant servers
# like zulipchat.com.
SELF_HOSTED = 1
LIMITED = 2
PREMIUM = 3
PREMIUM_FREE = 4
plan_type = models.PositiveSmallIntegerField(default=SELF_HOSTED) # type: int
# This value is also being used in static/js/settings_bots.bot_creation_policy_values. # This value is also being used in static/js/settings_bots.bot_creation_policy_values.
# On updating it here, update it there as well. # On updating it here, update it there as well.
BOT_CREATION_EVERYONE = 1 BOT_CREATION_EVERYONE = 1
@@ -2189,6 +2198,7 @@ class RealmAuditLog(models.Model):
REALM_DEACTIVATED = 'realm_deactivated' REALM_DEACTIVATED = 'realm_deactivated'
REALM_REACTIVATED = 'realm_reactivated' REALM_REACTIVATED = 'realm_reactivated'
REALM_PLAN_TYPE_CHANGED = 'realm_plan_type_changed'
SUBSCRIPTION_CREATED = 'subscription_created' SUBSCRIPTION_CREATED = 'subscription_created'
SUBSCRIPTION_ACTIVATED = 'subscription_activated' SUBSCRIPTION_ACTIVATED = 'subscription_activated'

View File

@@ -11,6 +11,7 @@ from zerver.lib.actions import (
do_set_realm_property, do_set_realm_property,
do_deactivate_realm, do_deactivate_realm,
do_deactivate_stream, do_deactivate_stream,
do_create_realm,
) )
from zerver.lib.send_email import send_future_email from zerver.lib.send_email import send_future_email
@@ -334,6 +335,12 @@ class RealmTest(ZulipTestCase):
self.assert_json_success(result) self.assert_json_success(result)
self.assertEqual(get_realm('zulip').video_chat_provider, "Jitsi") self.assertEqual(get_realm('zulip').video_chat_provider, "Jitsi")
def test_initial_plan_type(self) -> None:
with self.settings(BILLING_ENABLED=True):
self.assertEqual(Realm.LIMITED, do_create_realm('hosted', 'hosted').plan_type)
with self.settings(BILLING_ENABLED=False):
self.assertEqual(Realm.SELF_HOSTED, do_create_realm('onpremise', 'onpremise').plan_type)
class RealmAPITest(ZulipTestCase): class RealmAPITest(ZulipTestCase):
def setUp(self) -> None: def setUp(self) -> None:

View File

@@ -16,6 +16,7 @@ from zerver.lib.exceptions import JsonableError
from zerver.lib.logging_util import log_to_file from zerver.lib.logging_util import log_to_file
from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime
from zerver.lib.utils import generate_random_token from zerver.lib.utils import generate_random_token
from zerver.lib.actions import do_change_plan_type
from zerver.models import Realm, UserProfile, RealmAuditLog from zerver.models import Realm, UserProfile, RealmAuditLog
from zilencer.models import Customer, Plan, BillingProcessor from zilencer.models import Customer, Plan, BillingProcessor
from zproject.settings import get_secret from zproject.settings import get_secret
@@ -228,6 +229,7 @@ def process_initial_upgrade(user: UserProfile, plan: Plan, seat_count: int, stri
# TODO: billing address details are passed to us in the request; # TODO: billing address details are passed to us in the request;
# use that to calculate taxes. # use that to calculate taxes.
tax_percent=0) tax_percent=0)
do_change_plan_type(user, Realm.PREMIUM)
## Process RealmAuditLog ## Process RealmAuditLog

View File

@@ -50,6 +50,12 @@ def mock_customer_with_cancel_at_period_end_subscription(*args: Any, **kwargs: A
def mock_upcoming_invoice(*args: Any, **kwargs: Any) -> stripe.Invoice: def mock_upcoming_invoice(*args: Any, **kwargs: Any) -> stripe.Invoice:
return stripe.util.convert_to_stripe_object(fixture_data["upcoming_invoice"]) return stripe.util.convert_to_stripe_object(fixture_data["upcoming_invoice"])
# A Kandra is a fictional character that can become anything. Used as a
# wildcard when testing for equality.
class Kandra(object):
def __eq__(self, other: Any) -> bool:
return True
class StripeTest(ZulipTestCase): class StripeTest(ZulipTestCase):
def setUp(self) -> None: def setUp(self) -> None:
self.token = 'token' self.token = 'token'
@@ -103,6 +109,7 @@ class StripeTest(ZulipTestCase):
response = self.client_get("/upgrade/") response = self.client_get("/upgrade/")
self.assert_in_success_response(['We can also bill by invoice'], response) self.assert_in_success_response(['We can also bill by invoice'], response)
self.assertFalse(user.realm.has_seat_based_plan) self.assertFalse(user.realm.has_seat_based_plan)
self.assertNotEqual(user.realm.plan_type, Realm.PREMIUM)
# Click "Make payment" in Stripe Checkout # Click "Make payment" in Stripe Checkout
self.client_post("/upgrade/", { self.client_post("/upgrade/", {
@@ -135,10 +142,12 @@ class StripeTest(ZulipTestCase):
(RealmAuditLog.STRIPE_CUSTOMER_CREATED, timestamp_to_datetime(self.customer_created)), (RealmAuditLog.STRIPE_CUSTOMER_CREATED, timestamp_to_datetime(self.customer_created)),
(RealmAuditLog.STRIPE_CARD_ADDED, timestamp_to_datetime(self.customer_created)), (RealmAuditLog.STRIPE_CARD_ADDED, timestamp_to_datetime(self.customer_created)),
(RealmAuditLog.STRIPE_PLAN_CHANGED, timestamp_to_datetime(self.subscription_created)), (RealmAuditLog.STRIPE_PLAN_CHANGED, timestamp_to_datetime(self.subscription_created)),
(RealmAuditLog.REALM_PLAN_TYPE_CHANGED, Kandra()),
]) ])
# Check that we correctly updated Realm # Check that we correctly updated Realm
realm = get_realm("zulip") realm = get_realm("zulip")
self.assertTrue(realm.has_seat_based_plan) self.assertTrue(realm.has_seat_based_plan)
self.assertEqual(realm.plan_type, Realm.PREMIUM)
# Check that we can no longer access /upgrade # Check that we can no longer access /upgrade
response = self.client_get("/upgrade/") response = self.client_get("/upgrade/")
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
@@ -202,12 +211,13 @@ class StripeTest(ZulipTestCase):
# correctly handled the requires_billing_update field # correctly handled the requires_billing_update field
audit_log_entries = list(RealmAuditLog.objects.order_by('-id') audit_log_entries = list(RealmAuditLog.objects.order_by('-id')
.values_list('event_type', 'event_time', .values_list('event_type', 'event_time',
'requires_billing_update')[:4])[::-1] 'requires_billing_update')[:5])[::-1]
self.assertEqual(audit_log_entries, [ self.assertEqual(audit_log_entries, [
(RealmAuditLog.STRIPE_CUSTOMER_CREATED, timestamp_to_datetime(self.customer_created), False), (RealmAuditLog.STRIPE_CUSTOMER_CREATED, timestamp_to_datetime(self.customer_created), False),
(RealmAuditLog.STRIPE_CARD_ADDED, timestamp_to_datetime(self.customer_created), False), (RealmAuditLog.STRIPE_CARD_ADDED, timestamp_to_datetime(self.customer_created), False),
(RealmAuditLog.STRIPE_PLAN_CHANGED, timestamp_to_datetime(self.subscription_created), False), (RealmAuditLog.STRIPE_PLAN_CHANGED, timestamp_to_datetime(self.subscription_created), False),
(RealmAuditLog.STRIPE_PLAN_QUANTITY_RESET, timestamp_to_datetime(self.subscription_created), True), (RealmAuditLog.STRIPE_PLAN_QUANTITY_RESET, timestamp_to_datetime(self.subscription_created), True),
(RealmAuditLog.REALM_PLAN_TYPE_CHANGED, Kandra(), False),
]) ])
self.assertEqual(ujson.loads(RealmAuditLog.objects.filter( self.assertEqual(ujson.loads(RealmAuditLog.objects.filter(
event_type=RealmAuditLog.STRIPE_PLAN_QUANTITY_RESET).values_list('extra_data', flat=True).first()), event_type=RealmAuditLog.STRIPE_PLAN_QUANTITY_RESET).values_list('extra_data', flat=True).first()),
@@ -261,7 +271,8 @@ class StripeTest(ZulipTestCase):
self.assertEqual(audit_log_entries, [RealmAuditLog.STRIPE_CUSTOMER_CREATED, self.assertEqual(audit_log_entries, [RealmAuditLog.STRIPE_CUSTOMER_CREATED,
RealmAuditLog.STRIPE_CARD_ADDED, RealmAuditLog.STRIPE_CARD_ADDED,
RealmAuditLog.STRIPE_CARD_ADDED, RealmAuditLog.STRIPE_CARD_ADDED,
RealmAuditLog.STRIPE_PLAN_CHANGED]) RealmAuditLog.STRIPE_PLAN_CHANGED,
RealmAuditLog.REALM_PLAN_TYPE_CHANGED])
# Check that we correctly updated Realm # Check that we correctly updated Realm
realm = get_realm("zulip") realm = get_realm("zulip")
self.assertTrue(realm.has_seat_based_plan) self.assertTrue(realm.has_seat_based_plan)

View File

@@ -123,3 +123,5 @@ if FAKE_LDAP_MODE:
THUMBOR_URL = 'http://127.0.0.1:9995' THUMBOR_URL = 'http://127.0.0.1:9995'
SEARCH_PILLS_ENABLED = os.getenv('SEARCH_PILLS_ENABLED', False) SEARCH_PILLS_ENABLED = os.getenv('SEARCH_PILLS_ENABLED', False)
BILLING_ENABLED = True

View File

@@ -437,6 +437,10 @@ DEFAULT_SETTINGS.update({
# DEFAULT_SETTINGS, since it likely isn't usefully user-configurable. # DEFAULT_SETTINGS, since it likely isn't usefully user-configurable.
'OFFLINE_THRESHOLD_SECS': 5 * 60, 'OFFLINE_THRESHOLD_SECS': 5 * 60,
# Enables billing pages and plan-based feature gates. If False, all features
# are available to all realms.
'BILLING_ENABLED': False,
# Controls whether we run the worker that syncs billing-related updates # Controls whether we run the worker that syncs billing-related updates
# into Stripe. Should be True on at most one machine. # into Stripe. Should be True on at most one machine.
'BILLING_PROCESSOR_ENABLED': False, 'BILLING_PROCESSOR_ENABLED': False,