def test_change_plan_type(self) -> None: realm = get_realm('zulip') self.assertEqual(realm.plan_type, Realm.SELF_HOSTED) self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, None) self.assertEqual(realm.upload_quota_gb, None) do_change_plan_type(realm, Realm.STANDARD) realm = get_realm('zulip') self.assertEqual(realm.plan_type, Realm.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) do_change_plan_type(realm, Realm.LIMITED) realm = get_realm('zulip') self.assertEqual(realm.plan_type, Realm.LIMITED) self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, Realm.MESSAGE_VISIBILITY_LIMITED) self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_LIMITED) do_change_plan_type(realm, Realm.STANDARD_FREE) realm = get_realm('zulip') self.assertEqual(realm.plan_type, Realm.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) do_change_plan_type(realm, Realm.LIMITED) do_change_plan_type(realm, Realm.SELF_HOSTED) self.assertEqual(realm.plan_type, Realm.SELF_HOSTED) self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, None) self.assertEqual(realm.upload_quota_gb, None)
def test_message_retention_days(self) -> None: self.login('iago') realm = get_realm('zulip') req = dict(message_retention_days=ujson.dumps(0)) result = self.client_patch('/json/realm', req) self.assert_json_error(result, "Bad value for 'message_retention_days': 0") req = dict(message_retention_days=ujson.dumps(-10)) result = self.client_patch('/json/realm', req) self.assert_json_error(result, "Bad value for 'message_retention_days': -10") req = dict( message_retention_days=ujson.dumps(Realm.RETAIN_MESSAGE_FOREVER)) result = self.client_patch('/json/realm', req) self.assert_json_success(result) req = dict(message_retention_days=ujson.dumps(10)) result = self.client_patch('/json/realm', req) self.assert_json_success(result) do_change_plan_type(realm, Realm.LIMITED) req = dict(message_retention_days=ujson.dumps(10)) result = self.client_patch('/json/realm', req) self.assert_json_error(result, "Feature unavailable on your current plan.")
def process_downgrade(user: UserProfile) -> None: stripe_customer = stripe_get_customer( Customer.objects.filter(realm=user.realm).first().stripe_customer_id) subscription_balance = preview_invoice_total_for_downgrade(stripe_customer) # If subscription_balance > 0, they owe us money. This is likely due to # people they added in the last day, so we can just forgive it. # Stripe automatically forgives it when we delete the subscription, so nothing we need to do there. if subscription_balance < 0: stripe_customer.account_balance = stripe_customer.account_balance + subscription_balance stripe_subscription = extract_current_subscription(stripe_customer) # Wish these two could be transaction.atomic stripe_subscription = stripe_subscription.delete() stripe.Customer.save(stripe_customer) with transaction.atomic(): user.realm.has_seat_based_plan = False user.realm.save(update_fields=['has_seat_based_plan']) RealmAuditLog.objects.create( realm=user.realm, acting_user=user, event_type=RealmAuditLog.STRIPE_PLAN_CHANGED, event_time=timestamp_to_datetime(stripe_subscription.canceled_at), extra_data=ujson.dumps({'plan': None, 'quantity': stripe_subscription.quantity})) # Doing this last, since it results in user-visible confirmation (via # product changes) that the downgrade succeeded. # Keeping it out of the transaction.atomic block because it will # eventually have a lot of stuff going on. do_change_plan_type(user, Realm.LIMITED)
def approve_sponsorship(realm: Realm, *, acting_user: Optional[UserProfile]) -> None: from zerver.lib.actions import do_change_plan_type, internal_send_private_message do_change_plan_type(realm, Realm.PLAN_TYPE_STANDARD_FREE, acting_user=acting_user) customer = get_customer_by_realm(realm) if customer is not None and customer.sponsorship_pending: customer.sponsorship_pending = False customer.save(update_fields=["sponsorship_pending"]) RealmAuditLog.objects.create( realm=realm, acting_user=acting_user, event_type=RealmAuditLog.REALM_SPONSORSHIP_APPROVED, event_time=timezone_now(), ) notification_bot = get_system_bot(settings.NOTIFICATION_BOT, realm.id) for user in realm.get_human_billing_admin_and_realm_owner_users(): with override_language(user.default_language): # Using variable to make life easier for translators if these details change. plan_name = "Zulip Cloud Standard" emoji = ":tada:" message = _( f"Your organization's request for sponsored hosting has been approved! {emoji}.\n" f"You have been upgraded to {plan_name}, free of charge.") internal_send_private_message(notification_bot, user, message)
def process_downgrade(user: UserProfile) -> None: stripe_customer = stripe_get_customer( Customer.objects.filter(realm=user.realm).first().stripe_customer_id) subscription_balance = preview_invoice_total_for_downgrade(stripe_customer) # If subscription_balance > 0, they owe us money. This is likely due to # people they added in the last day, so we can just forgive it. # Stripe automatically forgives it when we delete the subscription, so nothing we need to do there. if subscription_balance < 0: stripe_customer.account_balance = stripe_customer.account_balance + subscription_balance stripe_subscription = extract_current_subscription(stripe_customer) # Wish these two could be transaction.atomic stripe_subscription = stripe_subscription.delete() stripe.Customer.save(stripe_customer) with transaction.atomic(): user.realm.has_seat_based_plan = False user.realm.save(update_fields=['has_seat_based_plan']) RealmAuditLog.objects.create( realm=user.realm, acting_user=user, event_type=RealmAuditLog.STRIPE_PLAN_CHANGED, event_time=timestamp_to_datetime(stripe_subscription.canceled_at), extra_data=ujson.dumps({ 'plan': None, 'quantity': stripe_subscription.quantity })) # Doing this last, since it results in user-visible confirmation (via # product changes) that the downgrade succeeded. # Keeping it out of the transaction.atomic block because it will # eventually have a lot of stuff going on. do_change_plan_type(user, Realm.LIMITED)
def test_message_retention_days(self) -> None: self.login('iago') realm = get_realm('zulip') self.assertEqual(realm.plan_type, Realm.SELF_HOSTED) req = dict(message_retention_days=ujson.dumps(0)) result = self.client_patch('/json/realm', req) self.assert_json_error(result, "Bad value for 'message_retention_days': 0") req = dict(message_retention_days=ujson.dumps(-10)) result = self.client_patch('/json/realm', req) self.assert_json_error(result, "Bad value for 'message_retention_days': -10") req = dict( message_retention_days=ujson.dumps(Realm.RETAIN_MESSAGE_FOREVER)) result = self.client_patch('/json/realm', req) self.assert_json_success(result) req = dict(message_retention_days=ujson.dumps(10)) result = self.client_patch('/json/realm', req) self.assert_json_success(result) do_change_plan_type(realm, Realm.LIMITED) req = dict(message_retention_days=ujson.dumps(10)) result = self.client_patch('/json/realm', req) self.assert_json_error( result, "Available on Zulip Standard. Upgrade to access.") do_change_plan_type(realm, Realm.STANDARD) req = dict(message_retention_days=ujson.dumps(10)) result = self.client_patch('/json/realm', req) self.assert_json_success(result)
def process_downgrade(plan: CustomerPlan) -> None: from zerver.lib.actions import do_change_plan_type do_change_plan_type(plan.customer.realm, Realm.PLAN_TYPE_LIMITED, acting_user=None) plan.status = CustomerPlan.ENDED plan.save(update_fields=["status"])
def support(request: HttpRequest) -> HttpResponse: context = {} # type: Dict[str, Any] if settings.BILLING_ENABLED and request.method == "POST": realm_id = request.POST.get("realm_id", None) realm = Realm.objects.get(id=realm_id) new_plan_type = request.POST.get("plan_type", None) if new_plan_type is not None: new_plan_type = int(new_plan_type) current_plan_type = realm.plan_type do_change_plan_type(realm, new_plan_type) msg = "Plan type of {} changed to {} from {} ".format(realm.name, get_plan_name(new_plan_type), get_plan_name(current_plan_type)) context["plan_type_msg"] = msg new_discount = request.POST.get("discount", None) if new_discount is not None: new_discount = Decimal(new_discount) current_discount = get_discount_for_realm(realm) attach_discount_to_realm(realm, new_discount) msg = "Discount of {} changed to {} from {} ".format(realm.name, new_discount, current_discount) context["discount_msg"] = msg query = request.GET.get("q", None) if query: key_words = get_invitee_emails_set(query) users = UserProfile.objects.filter(email__in=key_words) if users: for user in users: user.realm.realm_icon_url = realm_icon_url(user.realm) user.realm.admins = UserProfile.objects.filter(realm=user.realm, is_realm_admin=True) user.realm.default_discount = get_discount_for_realm(user.realm) context["users"] = users realms = set(Realm.objects.filter(string_id__in=key_words)) for key_word in key_words: try: URLValidator()(key_word) parse_result = urllib.parse.urlparse(key_word) hostname = parse_result.hostname if parse_result.port: hostname = "{}:{}".format(hostname, parse_result.port) subdomain = get_subdomain_from_hostname(hostname) realm = get_realm(subdomain) if realm is not None: realms.add(realm) except ValidationError: pass if realms: for realm in realms: realm.realm_icon_url = realm_icon_url(realm) realm.admins = UserProfile.objects.filter(realm=realm, is_realm_admin=True) realm.default_discount = get_discount_for_realm(realm) context["realms"] = realms return render(request, 'analytics/support.html', context=context)
def test_change_plan_type(self) -> None: realm = get_realm("zulip") iago = self.example_user("iago") self.assertEqual(realm.plan_type, Realm.SELF_HOSTED) self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, None) self.assertEqual(realm.upload_quota_gb, None) do_change_plan_type(realm, Realm.STANDARD, acting_user=iago) realm = get_realm("zulip") realm_audit_log = RealmAuditLog.objects.filter( event_type=RealmAuditLog.REALM_PLAN_TYPE_CHANGED).last() assert realm_audit_log is not None expected_extra_data = { "old_value": Realm.SELF_HOSTED, "new_value": Realm.STANDARD } self.assertEqual(realm_audit_log.extra_data, str(expected_extra_data)) self.assertEqual(realm_audit_log.acting_user, iago) self.assertEqual(realm.plan_type, Realm.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) do_change_plan_type(realm, Realm.LIMITED, acting_user=iago) realm = get_realm("zulip") self.assertEqual(realm.plan_type, Realm.LIMITED) self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, Realm.MESSAGE_VISIBILITY_LIMITED) self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_LIMITED) do_change_plan_type(realm, Realm.STANDARD_FREE, acting_user=iago) realm = get_realm("zulip") self.assertEqual(realm.plan_type, Realm.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) do_change_plan_type(realm, Realm.LIMITED, acting_user=iago) do_change_plan_type(realm, Realm.SELF_HOSTED, acting_user=iago) self.assertEqual(realm.plan_type, Realm.SELF_HOSTED) self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, None) self.assertEqual(realm.upload_quota_gb, None)
def process_initial_upgrade(user: UserProfile, plan: Plan, seat_count: int, stripe_token: str) -> None: customer = Customer.objects.filter(realm=user.realm).first() if customer is None: stripe_customer = do_create_customer_with_payment_source(user, stripe_token) else: stripe_customer = do_replace_payment_source(user, stripe_token) do_subscribe_customer_to_plan( stripe_customer=stripe_customer, stripe_plan_id=plan.stripe_plan_id, seat_count=seat_count, # TODO: billing address details are passed to us in the request; # use that to calculate taxes. tax_percent=0) do_change_plan_type(user, Realm.PREMIUM)
def test_promote_sponsoring_zulip_in_realm(self) -> None: realm = get_realm("zulip") do_change_plan_type(realm, Realm.PLAN_TYPE_STANDARD_FREE, acting_user=None) promote_zulip = promote_sponsoring_zulip_in_realm(realm) self.assertTrue(promote_zulip) with self.settings(PROMOTE_SPONSORING_ZULIP=False): promote_zulip = promote_sponsoring_zulip_in_realm(realm) self.assertFalse(promote_zulip) do_change_plan_type(realm, Realm.PLAN_TYPE_STANDARD_FREE, acting_user=None) promote_zulip = promote_sponsoring_zulip_in_realm(realm) self.assertTrue(promote_zulip) do_change_plan_type(realm, Realm.PLAN_TYPE_LIMITED, acting_user=None) promote_zulip = promote_sponsoring_zulip_in_realm(realm) self.assertFalse(promote_zulip) do_change_plan_type(realm, Realm.PLAN_TYPE_STANDARD, acting_user=None) promote_zulip = promote_sponsoring_zulip_in_realm(realm) self.assertFalse(promote_zulip)
def test_message_retention_days(self) -> None: self.login("iago") realm = get_realm("zulip") self.assertEqual(realm.plan_type, Realm.SELF_HOSTED) req = dict(message_retention_days=orjson.dumps(10).decode()) result = self.client_patch("/json/realm", req) self.assert_json_error(result, "Must be an organization owner") self.login("desdemona") req = dict(message_retention_days=orjson.dumps(0).decode()) result = self.client_patch("/json/realm", req) self.assert_json_error(result, "Bad value for 'message_retention_days': 0") req = dict(message_retention_days=orjson.dumps(-10).decode()) result = self.client_patch("/json/realm", req) self.assert_json_error(result, "Bad value for 'message_retention_days': -10") req = dict(message_retention_days=orjson.dumps("invalid").decode()) result = self.client_patch("/json/realm", req) self.assert_json_error( result, "Bad value for 'message_retention_days': invalid") req = dict(message_retention_days=orjson.dumps(-1).decode()) result = self.client_patch("/json/realm", req) self.assert_json_error(result, "Bad value for 'message_retention_days': -1") req = dict(message_retention_days=orjson.dumps("unlimited").decode()) result = self.client_patch("/json/realm", req) self.assert_json_success(result) req = dict(message_retention_days=orjson.dumps(10).decode()) result = self.client_patch("/json/realm", req) self.assert_json_success(result) do_change_plan_type(realm, Realm.LIMITED, acting_user=None) req = dict(message_retention_days=orjson.dumps(10).decode()) result = self.client_patch("/json/realm", req) self.assert_json_error( result, "Available on Zulip Standard. Upgrade to access.") do_change_plan_type(realm, Realm.STANDARD, acting_user=None) req = dict(message_retention_days=orjson.dumps(10).decode()) result = self.client_patch("/json/realm", req) self.assert_json_success(result)
def test_message_retention_days(self) -> None: self.login('iago') realm = get_realm('zulip') self.assertEqual(realm.plan_type, Realm.SELF_HOSTED) req = dict(message_retention_days=orjson.dumps(10).decode()) result = self.client_patch('/json/realm', req) self.assert_json_error(result, "Must be an organization owner") self.login('desdemona') req = dict(message_retention_days=orjson.dumps(0).decode()) result = self.client_patch('/json/realm', req) self.assert_json_error(result, "Bad value for 'message_retention_days': 0") req = dict(message_retention_days=orjson.dumps(-10).decode()) result = self.client_patch('/json/realm', req) self.assert_json_error(result, "Bad value for 'message_retention_days': -10") req = dict(message_retention_days=orjson.dumps('invalid').decode()) result = self.client_patch('/json/realm', req) self.assert_json_error( result, "Bad value for 'message_retention_days': invalid") req = dict(message_retention_days=orjson.dumps(-1).decode()) result = self.client_patch('/json/realm', req) self.assert_json_error(result, "Bad value for 'message_retention_days': -1") req = dict(message_retention_days=orjson.dumps('forever').decode()) result = self.client_patch('/json/realm', req) self.assert_json_success(result) req = dict(message_retention_days=orjson.dumps(10).decode()) result = self.client_patch('/json/realm', req) self.assert_json_success(result) do_change_plan_type(realm, Realm.LIMITED) req = dict(message_retention_days=orjson.dumps(10).decode()) result = self.client_patch('/json/realm', req) self.assert_json_error( result, "Available on Zulip Standard. Upgrade to access.") do_change_plan_type(realm, Realm.STANDARD) req = dict(message_retention_days=orjson.dumps(10).decode()) result = self.client_patch('/json/realm', req) self.assert_json_success(result)
def process_initial_upgrade(user: UserProfile, plan: Plan, seat_count: int, stripe_token: str) -> None: customer = Customer.objects.filter(realm=user.realm).first() if customer is None: stripe_customer = do_create_customer(user, stripe_token=stripe_token) else: stripe_customer = do_replace_payment_source(user, stripe_token) do_subscribe_customer_to_plan( user=user, stripe_customer=stripe_customer, stripe_plan_id=plan.stripe_plan_id, seat_count=seat_count, # TODO: billing address details are passed to us in the request; # use that to calculate taxes. tax_percent=0) do_change_plan_type(user, Realm.STANDARD)
def approve_sponsorship(realm: Realm) -> None: from zerver.lib.actions import do_change_plan_type, internal_send_private_message do_change_plan_type(realm, Realm.STANDARD_FREE) customer = get_customer_by_realm(realm) if customer is not None and customer.sponsorship_pending: customer.sponsorship_pending = False customer.save(update_fields=["sponsorship_pending"]) notification_bot = get_system_bot(settings.NOTIFICATION_BOT) for billing_admin in realm.get_human_billing_admin_users(): with override_language(billing_admin.default_language): # Using variable to make life easier for translators if these details change. plan_name = "Zulip Cloud Standard" emoji = ":tada:" message = _( f"Your organization's request for sponsored hosting has been approved! {emoji}.\n" f"You have been upgraded to {plan_name}, free of charge.") internal_send_private_message(billing_admin.realm, notification_bot, billing_admin, message)
def process_initial_upgrade(user: UserProfile, plan: Plan, seat_count: int, stripe_token: Optional[str]) -> None: customer = Customer.objects.filter(realm=user.realm).first() if customer is None: stripe_customer = do_create_customer(user, stripe_token=stripe_token) # elif instead of if since we want to avoid doing two round trips to # stripe if we can elif stripe_token is not None: stripe_customer = do_replace_payment_source(user, stripe_token) do_subscribe_customer_to_plan( user=user, stripe_customer=stripe_customer, stripe_plan_id=plan.stripe_plan_id, seat_count=seat_count, # TODO: billing address details are passed to us in the request; # use that to calculate taxes. tax_percent=0, charge_automatically=(stripe_token is not None)) do_change_plan_type(user, Realm.STANDARD)
def test_change_plan_type(self) -> None: user = self.example_user('iago') realm = get_realm('zulip') self.assertEqual(realm.plan_type, Realm.SELF_HOSTED) self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, None) do_change_plan_type(user, Realm.STANDARD) realm = get_realm('zulip') self.assertEqual(realm.max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, None) do_change_plan_type(user, Realm.LIMITED) realm = get_realm('zulip') self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, Realm.MESSAGE_VISIBILITY_LIMITED) do_change_plan_type(user, Realm.STANDARD_FREE) realm = get_realm('zulip') self.assertEqual(realm.max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, None)
def test_change_plan_type(self) -> None: user = self.example_user('iago') self.assertEqual(get_realm('zulip').max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) do_change_plan_type(user, Realm.STANDARD) self.assertEqual(get_realm('zulip').max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX) do_change_plan_type(user, Realm.LIMITED) self.assertEqual(get_realm('zulip').max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) do_change_plan_type(user, Realm.STANDARD_FREE) self.assertEqual(get_realm('zulip').max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX)
def test_show_plans(self) -> None: realm = get_realm("zulip") # Don't show plans to guest users self.login("polonius") do_change_plan_type(realm, Realm.LIMITED, acting_user=None) self.assertNotInHomePage("Plans") # Show plans link to all other users if plan_type is LIMITED self.login("hamlet") self.assertInHomePage("Plans") # Show plans link to no one, including admins, if SELF_HOSTED or STANDARD do_change_plan_type(realm, Realm.SELF_HOSTED, acting_user=None) self.assertNotInHomePage("Plans") do_change_plan_type(realm, Realm.STANDARD, acting_user=None) self.assertNotInHomePage("Plans")
def test_show_support_zulip(self) -> None: realm = get_realm("zulip") self.login("hamlet") self.assertInHomePage("Support Zulip") do_change_plan_type(realm, Realm.STANDARD_FREE, acting_user=None) self.assertInHomePage("Support Zulip") with self.settings(PROMOTE_SPONSORING_ZULIP=False): self.assertNotInHomePage("Support Zulip") do_change_plan_type(realm, Realm.LIMITED, acting_user=None) self.assertNotInHomePage("Support Zulip") do_change_plan_type(realm, Realm.STANDARD, acting_user=None) self.assertNotInHomePage("Support Zulip")
def test_change_plan_type(self) -> None: realm = get_realm('zulip') self.assertEqual(realm.plan_type, Realm.SELF_HOSTED) self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, None) do_change_plan_type(realm, Realm.STANDARD) realm = get_realm('zulip') self.assertEqual(realm.max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, None) do_change_plan_type(realm, Realm.LIMITED) realm = get_realm('zulip') self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, Realm.MESSAGE_VISIBILITY_LIMITED) do_change_plan_type(realm, Realm.STANDARD_FREE) realm = get_realm('zulip') self.assertEqual(realm.max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, None)
def test_show_plans(self) -> None: realm = get_realm("zulip") # Don't show plans to guest users self.login("polonius") do_change_plan_type(realm, Realm.LIMITED, acting_user=None) result_html = self._get_home_page().content.decode("utf-8") self.assertNotIn("Plans", result_html) # Show plans link to all other users if plan_type is LIMITED self.login("hamlet") result_html = self._get_home_page().content.decode("utf-8") self.assertIn("Plans", result_html) # Show plans link to no one, including admins, if SELF_HOSTED or STANDARD do_change_plan_type(realm, Realm.SELF_HOSTED, acting_user=None) result_html = self._get_home_page().content.decode("utf-8") self.assertNotIn("Plans", result_html) do_change_plan_type(realm, Realm.STANDARD, acting_user=None) result_html = self._get_home_page().content.decode("utf-8") self.assertNotIn("Plans", result_html)
def process_initial_upgrade(user: UserProfile, licenses: int, automanage_licenses: bool, billing_schedule: int, stripe_token: Optional[str]) -> None: realm = user.realm customer = update_or_create_stripe_customer(user, stripe_token=stripe_token) if CustomerPlan.objects.filter(customer=customer, status=CustomerPlan.ACTIVE).exists(): # Unlikely race condition from two people upgrading (clicking "Make payment") # at exactly the same time. Doesn't fully resolve the race condition, but having # a check here reduces the likelihood. billing_logger.warning( "Customer {} trying to upgrade, but has an active subscription".format(customer)) raise BillingError('subscribing with existing subscription', BillingError.TRY_RELOADING) billing_cycle_anchor, next_invoice_date, period_end, price_per_license = compute_plan_parameters( automanage_licenses, billing_schedule, customer.default_discount) # The main design constraint in this function is that if you upgrade with a credit card, and the # charge fails, everything should be rolled back as if nothing had happened. This is because we # expect frequent card failures on initial signup. # Hence, if we're going to charge a card, do it at the beginning, even if we later may have to # adjust the number of licenses. charge_automatically = stripe_token is not None if charge_automatically: stripe_charge = stripe.Charge.create( amount=price_per_license * licenses, currency='usd', customer=customer.stripe_customer_id, description="Upgrade to Zulip Standard, ${} x {}".format(price_per_license/100, licenses), receipt_email=user.email, statement_descriptor='Zulip Standard') # Not setting a period start and end, but maybe we should? Unclear what will make things # most similar to the renewal case from an accounting perspective. stripe.InvoiceItem.create( amount=price_per_license * licenses * -1, currency='usd', customer=customer.stripe_customer_id, description="Payment (Card ending in {})".format(cast(stripe.Card, stripe_charge.source).last4), discountable=False) # TODO: The correctness of this relies on user creation, deactivation, etc being # in a transaction.atomic() with the relevant RealmAuditLog entries with transaction.atomic(): # billed_licenses can greater than licenses if users are added between the start of # this function (process_initial_upgrade) and now billed_licenses = max(get_seat_count(realm), licenses) plan_params = { 'automanage_licenses': automanage_licenses, 'charge_automatically': charge_automatically, 'price_per_license': price_per_license, 'discount': customer.default_discount, 'billing_cycle_anchor': billing_cycle_anchor, 'billing_schedule': billing_schedule, 'tier': CustomerPlan.STANDARD} plan = CustomerPlan.objects.create( customer=customer, next_invoice_date=next_invoice_date, **plan_params) ledger_entry = LicenseLedger.objects.create( plan=plan, is_renewal=True, event_time=billing_cycle_anchor, licenses=billed_licenses, licenses_at_next_renewal=billed_licenses) plan.invoiced_through = ledger_entry plan.save(update_fields=['invoiced_through']) RealmAuditLog.objects.create( realm=realm, acting_user=user, event_time=billing_cycle_anchor, event_type=RealmAuditLog.CUSTOMER_PLAN_CREATED, extra_data=ujson.dumps(plan_params)) stripe.InvoiceItem.create( currency='usd', customer=customer.stripe_customer_id, description='Zulip Standard', discountable=False, period = {'start': datetime_to_timestamp(billing_cycle_anchor), 'end': datetime_to_timestamp(period_end)}, quantity=billed_licenses, unit_amount=price_per_license) if charge_automatically: billing_method = 'charge_automatically' days_until_due = None else: billing_method = 'send_invoice' days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE stripe_invoice = stripe.Invoice.create( auto_advance=True, billing=billing_method, customer=customer.stripe_customer_id, days_until_due=days_until_due, statement_descriptor='Zulip Standard') stripe.Invoice.finalize_invoice(stripe_invoice) from zerver.lib.actions import do_change_plan_type do_change_plan_type(realm, Realm.STANDARD)
def support( request: HttpRequest, realm_id: Optional[int] = REQ(default=None, converter=to_non_negative_int), plan_type: Optional[int] = REQ(default=None, converter=to_non_negative_int), discount: Optional[Decimal] = REQ(default=None, converter=to_decimal), new_subdomain: Optional[str] = REQ(default=None), status: Optional[str] = REQ( default=None, str_validator=check_string_in(VALID_STATUS_VALUES)), billing_method: Optional[str] = REQ( default=None, str_validator=check_string_in(VALID_BILLING_METHODS)), sponsorship_pending: Optional[bool] = REQ(default=None, json_validator=check_bool), approve_sponsorship: Optional[bool] = REQ(default=None, json_validator=check_bool), downgrade_method: Optional[str] = REQ( default=None, str_validator=check_string_in(VALID_DOWNGRADE_METHODS)), scrub_realm: Optional[bool] = REQ(default=None, json_validator=check_bool), query: Optional[str] = REQ("q", default=None), org_type: Optional[int] = REQ(default=None, converter=to_non_negative_int), ) -> HttpResponse: context: Dict[str, Any] = {} if "success_message" in request.session: context["success_message"] = request.session["success_message"] del request.session["success_message"] if settings.BILLING_ENABLED and request.method == "POST": # We check that request.POST only has two keys in it: The # realm_id and a field to change. keys = set(request.POST.keys()) if "csrfmiddlewaretoken" in keys: keys.remove("csrfmiddlewaretoken") if len(keys) != 2: raise JsonableError(_("Invalid parameters")) realm = Realm.objects.get(id=realm_id) acting_user = request.user assert isinstance(acting_user, UserProfile) if plan_type is not None: current_plan_type = realm.plan_type do_change_plan_type(realm, plan_type, acting_user=acting_user) msg = f"Plan type of {realm.string_id} changed from {get_plan_name(current_plan_type)} to {get_plan_name(plan_type)} " context["success_message"] = msg elif org_type is not None: current_realm_type = realm.org_type do_change_realm_org_type(realm, org_type, acting_user=acting_user) msg = f"Org type of {realm.string_id} changed from {get_org_type_display_name(current_realm_type)} to {get_org_type_display_name(org_type)} " context["success_message"] = msg elif discount is not None: current_discount = get_discount_for_realm(realm) or 0 attach_discount_to_realm(realm, discount, acting_user=acting_user) context[ "success_message"] = f"Discount of {realm.string_id} changed to {discount}% from {current_discount}%." elif new_subdomain is not None: old_subdomain = realm.string_id try: check_subdomain_available(new_subdomain) except ValidationError as error: context["error_message"] = error.message else: do_change_realm_subdomain(realm, new_subdomain, acting_user=acting_user) request.session[ "success_message"] = f"Subdomain changed from {old_subdomain} to {new_subdomain}" return HttpResponseRedirect( reverse("support") + "?" + urlencode({"q": new_subdomain})) elif status is not None: if status == "active": do_send_realm_reactivation_email(realm, acting_user=acting_user) context[ "success_message"] = f"Realm reactivation email sent to admins of {realm.string_id}." elif status == "deactivated": do_deactivate_realm(realm, acting_user=acting_user) context["success_message"] = f"{realm.string_id} deactivated." elif billing_method is not None: if billing_method == "send_invoice": update_billing_method_of_current_plan( realm, charge_automatically=False, acting_user=acting_user) context[ "success_message"] = f"Billing method of {realm.string_id} updated to pay by invoice." elif billing_method == "charge_automatically": update_billing_method_of_current_plan( realm, charge_automatically=True, acting_user=acting_user) context[ "success_message"] = f"Billing method of {realm.string_id} updated to charge automatically." elif sponsorship_pending is not None: if sponsorship_pending: update_sponsorship_status(realm, True, acting_user=acting_user) context[ "success_message"] = f"{realm.string_id} marked as pending sponsorship." else: update_sponsorship_status(realm, False, acting_user=acting_user) context[ "success_message"] = f"{realm.string_id} is no longer pending sponsorship." elif approve_sponsorship: do_approve_sponsorship(realm, acting_user=acting_user) context[ "success_message"] = f"Sponsorship approved for {realm.string_id}" elif downgrade_method is not None: if downgrade_method == "downgrade_at_billing_cycle_end": downgrade_at_the_end_of_billing_cycle(realm) context[ "success_message"] = f"{realm.string_id} marked for downgrade at the end of billing cycle" elif downgrade_method == "downgrade_now_without_additional_licenses": downgrade_now_without_creating_additional_invoices(realm) context[ "success_message"] = f"{realm.string_id} downgraded without creating additional invoices" elif downgrade_method == "downgrade_now_void_open_invoices": downgrade_now_without_creating_additional_invoices(realm) voided_invoices_count = void_all_open_invoices(realm) context[ "success_message"] = f"{realm.string_id} downgraded and voided {voided_invoices_count} open invoices" elif scrub_realm: do_scrub_realm(realm, acting_user=acting_user) context["success_message"] = f"{realm.string_id} scrubbed." if query: key_words = get_invitee_emails_set(query) users = set(UserProfile.objects.filter(delivery_email__in=key_words)) realms = set(Realm.objects.filter(string_id__in=key_words)) for key_word in key_words: try: URLValidator()(key_word) parse_result = urllib.parse.urlparse(key_word) hostname = parse_result.hostname assert hostname is not None if parse_result.port: hostname = f"{hostname}:{parse_result.port}" subdomain = get_subdomain_from_hostname(hostname) try: realms.add(get_realm(subdomain)) except Realm.DoesNotExist: pass except ValidationError: users.update( UserProfile.objects.filter(full_name__iexact=key_word)) for realm in realms: realm.customer = get_customer_by_realm(realm) current_plan = get_current_plan_by_realm(realm) if current_plan is not None: new_plan, last_ledger_entry = make_end_of_cycle_updates_if_needed( current_plan, timezone_now()) if last_ledger_entry is not None: if new_plan is not None: realm.current_plan = new_plan else: realm.current_plan = current_plan realm.current_plan.licenses = last_ledger_entry.licenses realm.current_plan.licenses_used = get_latest_seat_count( realm) # full_names can have , in them users.update(UserProfile.objects.filter(full_name__iexact=query)) context["users"] = users context["realms"] = realms confirmations: List[Dict[str, Any]] = [] preregistration_users = PreregistrationUser.objects.filter( email__in=key_words) confirmations += get_confirmations( [ Confirmation.USER_REGISTRATION, Confirmation.INVITATION, Confirmation.REALM_CREATION ], preregistration_users, hostname=request.get_host(), ) multiuse_invites = MultiuseInvite.objects.filter(realm__in=realms) confirmations += get_confirmations([Confirmation.MULTIUSE_INVITE], multiuse_invites) confirmations += get_confirmations([Confirmation.REALM_REACTIVATION], [realm.id for realm in realms]) context["confirmations"] = confirmations def get_realm_owner_emails_as_string(realm: Realm) -> str: return ", ".join(realm.get_human_owner_users().order_by( "delivery_email").values_list("delivery_email", flat=True)) def get_realm_admin_emails_as_string(realm: Realm) -> str: return ", ".join( realm.get_human_admin_users(include_realm_owners=False).order_by( "delivery_email").values_list("delivery_email", flat=True)) context[ "get_realm_owner_emails_as_string"] = get_realm_owner_emails_as_string context[ "get_realm_admin_emails_as_string"] = get_realm_admin_emails_as_string context["get_discount_for_realm"] = get_discount_for_realm context["get_org_type_display_name"] = get_org_type_display_name context["realm_icon_url"] = realm_icon_url context["Confirmation"] = Confirmation context["sorted_realm_types"] = sorted(Realm.ORG_TYPES.values(), key=lambda d: d["display_order"]) return render(request, "analytics/support.html", context=context)
def process_initial_upgrade( user: UserProfile, licenses: int, automanage_licenses: bool, billing_schedule: int, charge_automatically: bool, free_trial: bool, ) -> None: realm = user.realm customer = update_or_create_stripe_customer(user) assert customer.stripe_customer_id is not None # for mypy ensure_realm_does_not_have_active_plan(customer.realm) ( billing_cycle_anchor, next_invoice_date, period_end, price_per_license, ) = compute_plan_parameters( CustomerPlan.STANDARD, automanage_licenses, billing_schedule, customer.default_discount, free_trial, ) # TODO: The correctness of this relies on user creation, deactivation, etc being # in a transaction.atomic() with the relevant RealmAuditLog entries with transaction.atomic(): # billed_licenses can greater than licenses if users are added between the start of # this function (process_initial_upgrade) and now billed_licenses = max(get_latest_seat_count(realm), licenses) plan_params = { "automanage_licenses": automanage_licenses, "charge_automatically": charge_automatically, "price_per_license": price_per_license, "discount": customer.default_discount, "billing_cycle_anchor": billing_cycle_anchor, "billing_schedule": billing_schedule, "tier": CustomerPlan.STANDARD, } if free_trial: plan_params["status"] = CustomerPlan.FREE_TRIAL plan = CustomerPlan.objects.create(customer=customer, next_invoice_date=next_invoice_date, **plan_params) ledger_entry = LicenseLedger.objects.create( plan=plan, is_renewal=True, event_time=billing_cycle_anchor, licenses=billed_licenses, licenses_at_next_renewal=billed_licenses, ) plan.invoiced_through = ledger_entry plan.save(update_fields=["invoiced_through"]) RealmAuditLog.objects.create( realm=realm, acting_user=user, event_time=billing_cycle_anchor, event_type=RealmAuditLog.CUSTOMER_PLAN_CREATED, extra_data=orjson.dumps(plan_params, default=decimal_to_float).decode(), ) if not free_trial: stripe.InvoiceItem.create( currency="usd", customer=customer.stripe_customer_id, description="Zulip Standard", discountable=False, period={ "start": datetime_to_timestamp(billing_cycle_anchor), "end": datetime_to_timestamp(period_end), }, quantity=billed_licenses, unit_amount=price_per_license, ) if charge_automatically: collection_method = "charge_automatically" days_until_due = None else: collection_method = "send_invoice" days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE stripe_invoice = stripe.Invoice.create( auto_advance=True, collection_method=collection_method, customer=customer.stripe_customer_id, days_until_due=days_until_due, statement_descriptor="Zulip Standard", ) stripe.Invoice.finalize_invoice(stripe_invoice) from zerver.lib.actions import do_change_plan_type do_change_plan_type(realm, Realm.PLAN_TYPE_STANDARD, acting_user=user)
def test_get_billing_info(self) -> None: user = self.example_user("desdemona") user.role = UserProfile.ROLE_REALM_OWNER user.save(update_fields=["role"]) # realm owner, but no CustomerPlan and realm plan_type SELF_HOSTED -> neither billing link or plans with self.settings(CORPORATE_ENABLED=True): billing_info = get_billing_info(user) self.assertFalse(billing_info.show_billing) self.assertFalse(billing_info.show_plans) # realm owner, with inactive CustomerPlan and realm plan_type SELF_HOSTED -> show only billing link customer = Customer.objects.create(realm=get_realm("zulip"), stripe_customer_id="cus_id") CustomerPlan.objects.create( customer=customer, billing_cycle_anchor=timezone_now(), billing_schedule=CustomerPlan.ANNUAL, next_invoice_date=timezone_now(), tier=CustomerPlan.STANDARD, status=CustomerPlan.ENDED, ) with self.settings(CORPORATE_ENABLED=True): billing_info = get_billing_info(user) self.assertTrue(billing_info.show_billing) self.assertFalse(billing_info.show_plans) # realm owner, with inactive CustomerPlan and realm plan_type LIMITED -> show billing link and plans do_change_plan_type(user.realm, Realm.LIMITED, acting_user=None) with self.settings(CORPORATE_ENABLED=True): billing_info = get_billing_info(user) self.assertTrue(billing_info.show_billing) self.assertTrue(billing_info.show_plans) # Always false without CORPORATE_ENABLED with self.settings(CORPORATE_ENABLED=False): billing_info = get_billing_info(user) self.assertFalse(billing_info.show_billing) self.assertFalse(billing_info.show_plans) # Always false without a UserProfile with self.settings(CORPORATE_ENABLED=True): billing_info = get_billing_info(None) self.assertFalse(billing_info.show_billing) self.assertFalse(billing_info.show_plans) # realm admin, with CustomerPlan and realm plan_type LIMITED -> show only billing plans user.role = UserProfile.ROLE_REALM_ADMINISTRATOR user.save(update_fields=["role"]) with self.settings(CORPORATE_ENABLED=True): billing_info = get_billing_info(user) self.assertFalse(billing_info.show_billing) self.assertTrue(billing_info.show_plans) # billing admin, with CustomerPlan and realm plan_type STANDARD -> show only billing link user.role = UserProfile.ROLE_MEMBER user.is_billing_admin = True do_change_plan_type(user.realm, Realm.STANDARD, acting_user=None) user.save(update_fields=["role", "is_billing_admin"]) with self.settings(CORPORATE_ENABLED=True): billing_info = get_billing_info(user) self.assertTrue(billing_info.show_billing) self.assertFalse(billing_info.show_plans) # member, with CustomerPlan and realm plan_type STANDARD -> neither billing link or plans user.is_billing_admin = False user.save(update_fields=["is_billing_admin"]) with self.settings(CORPORATE_ENABLED=True): billing_info = get_billing_info(user) self.assertFalse(billing_info.show_billing) self.assertFalse(billing_info.show_plans) # guest, with CustomerPlan and realm plan_type SELF_HOSTED -> neither billing link or plans user.role = UserProfile.ROLE_GUEST user.save(update_fields=["role"]) do_change_plan_type(user.realm, Realm.SELF_HOSTED, acting_user=None) with self.settings(CORPORATE_ENABLED=True): billing_info = get_billing_info(user) self.assertFalse(billing_info.show_billing) self.assertFalse(billing_info.show_plans) # billing admin, but no CustomerPlan and realm plan_type SELF_HOSTED -> neither billing link or plans user.role = UserProfile.ROLE_MEMBER user.is_billing_admin = True user.save(update_fields=["role", "is_billing_admin"]) CustomerPlan.objects.all().delete() with self.settings(CORPORATE_ENABLED=True): billing_info = get_billing_info(user) self.assertFalse(billing_info.show_billing) self.assertFalse(billing_info.show_plans) # billing admin, with sponsorship pending and relam plan_type SELF_HOSTED -> show only billing link customer.sponsorship_pending = True customer.save(update_fields=["sponsorship_pending"]) with self.settings(CORPORATE_ENABLED=True): billing_info = get_billing_info(user) self.assertTrue(billing_info.show_billing) self.assertFalse(billing_info.show_plans) # billing admin, no customer object and relam plan_type SELF_HOSTED -> neither billing link or plans customer.delete() with self.settings(CORPORATE_ENABLED=True): billing_info = get_billing_info(user) self.assertFalse(billing_info.show_billing) self.assertFalse(billing_info.show_plans)
def process_initial_upgrade(user: UserProfile, licenses: int, automanage_licenses: bool, billing_schedule: int, stripe_token: Optional[str]) -> None: realm = user.realm customer = update_or_create_stripe_customer(user, stripe_token=stripe_token) charge_automatically = stripe_token is not None free_trial = settings.FREE_TRIAL_DAYS not in (None, 0) if get_current_plan_by_customer(customer) is not None: # Unlikely race condition from two people upgrading (clicking "Make payment") # at exactly the same time. Doesn't fully resolve the race condition, but having # a check here reduces the likelihood. billing_logger.warning( "Customer %s trying to upgrade, but has an active subscription", customer, ) raise BillingError('subscribing with existing subscription', BillingError.TRY_RELOADING) billing_cycle_anchor, next_invoice_date, period_end, price_per_license = compute_plan_parameters( automanage_licenses, billing_schedule, customer.default_discount, free_trial) # The main design constraint in this function is that if you upgrade with a credit card, and the # charge fails, everything should be rolled back as if nothing had happened. This is because we # expect frequent card failures on initial signup. # Hence, if we're going to charge a card, do it at the beginning, even if we later may have to # adjust the number of licenses. if charge_automatically: if not free_trial: stripe_charge = stripe.Charge.create( amount=price_per_license * licenses, currency='usd', customer=customer.stripe_customer_id, description="Upgrade to Zulip Standard, ${} x {}".format( price_per_license / 100, licenses), receipt_email=user.delivery_email, statement_descriptor='Zulip Standard') # Not setting a period start and end, but maybe we should? Unclear what will make things # most similar to the renewal case from an accounting perspective. description = "Payment (Card ending in {})".format( cast(stripe.Card, stripe_charge.source).last4) stripe.InvoiceItem.create(amount=price_per_license * licenses * -1, currency='usd', customer=customer.stripe_customer_id, description=description, discountable=False) # TODO: The correctness of this relies on user creation, deactivation, etc being # in a transaction.atomic() with the relevant RealmAuditLog entries with transaction.atomic(): # billed_licenses can greater than licenses if users are added between the start of # this function (process_initial_upgrade) and now billed_licenses = max(get_latest_seat_count(realm), licenses) plan_params = { 'automanage_licenses': automanage_licenses, 'charge_automatically': charge_automatically, 'price_per_license': price_per_license, 'discount': customer.default_discount, 'billing_cycle_anchor': billing_cycle_anchor, 'billing_schedule': billing_schedule, 'tier': CustomerPlan.STANDARD } if free_trial: plan_params['status'] = CustomerPlan.FREE_TRIAL plan = CustomerPlan.objects.create(customer=customer, next_invoice_date=next_invoice_date, **plan_params) ledger_entry = LicenseLedger.objects.create( plan=plan, is_renewal=True, event_time=billing_cycle_anchor, licenses=billed_licenses, licenses_at_next_renewal=billed_licenses) plan.invoiced_through = ledger_entry plan.save(update_fields=['invoiced_through']) RealmAuditLog.objects.create( realm=realm, acting_user=user, event_time=billing_cycle_anchor, event_type=RealmAuditLog.CUSTOMER_PLAN_CREATED, extra_data=ujson.dumps(plan_params)) if not free_trial: stripe.InvoiceItem.create( currency='usd', customer=customer.stripe_customer_id, description='Zulip Standard', discountable=False, period={ 'start': datetime_to_timestamp(billing_cycle_anchor), 'end': datetime_to_timestamp(period_end) }, quantity=billed_licenses, unit_amount=price_per_license) if charge_automatically: billing_method = 'charge_automatically' days_until_due = None else: billing_method = 'send_invoice' days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE stripe_invoice = stripe.Invoice.create( auto_advance=True, billing=billing_method, customer=customer.stripe_customer_id, days_until_due=days_until_due, statement_descriptor='Zulip Standard') stripe.Invoice.finalize_invoice(stripe_invoice) from zerver.lib.actions import do_change_plan_type do_change_plan_type(realm, Realm.STANDARD)
def process_downgrade(plan: CustomerPlan) -> None: from zerver.lib.actions import do_change_plan_type do_change_plan_type(plan.customer.realm, Realm.LIMITED) plan.status = CustomerPlan.ENDED plan.save(update_fields=['status'])
def do_import_realm(import_dir: Path, subdomain: str) -> Realm: logging.info("Importing realm dump %s" % (import_dir,)) if not os.path.exists(import_dir): raise Exception("Missing import directory!") realm_data_filename = os.path.join(import_dir, "realm.json") if not os.path.exists(realm_data_filename): raise Exception("Missing realm.json file!") logging.info("Importing realm data from %s" % (realm_data_filename,)) with open(realm_data_filename) as f: data = ujson.load(f) sort_by_date = data.get('sort_by_date', False) bulk_import_client(data, Client, 'zerver_client') # We don't import the Stream model yet, since it depends on Realm, # which isn't imported yet. But we need the Stream model IDs for # notifications_stream. update_model_ids(Stream, data, 'stream') re_map_foreign_keys(data, 'zerver_realm', 'notifications_stream', related_table="stream") re_map_foreign_keys(data, 'zerver_realm', 'signup_notifications_stream', related_table="stream") fix_datetime_fields(data, 'zerver_realm') # Fix realm subdomain information data['zerver_realm'][0]['string_id'] = subdomain data['zerver_realm'][0]['name'] = subdomain fix_realm_authentication_bitfield(data, 'zerver_realm', 'authentication_methods') update_model_ids(Realm, data, 'realm') realm = Realm(**data['zerver_realm'][0]) if realm.notifications_stream_id is not None: notifications_stream_id = int(realm.notifications_stream_id) # type: Optional[int] else: notifications_stream_id = None realm.notifications_stream_id = None if realm.signup_notifications_stream_id is not None: signup_notifications_stream_id = int(realm.signup_notifications_stream_id) # type: Optional[int] else: signup_notifications_stream_id = None realm.signup_notifications_stream_id = None realm.save() # Email tokens will automatically be randomly generated when the # Stream objects are created by Django. fix_datetime_fields(data, 'zerver_stream') re_map_foreign_keys(data, 'zerver_stream', 'realm', related_table="realm") bulk_import_model(data, Stream) realm.notifications_stream_id = notifications_stream_id realm.signup_notifications_stream_id = signup_notifications_stream_id realm.save() # Remap the user IDs for notification_bot and friends to their # appropriate IDs on this server for item in data['zerver_userprofile_crossrealm']: logging.info("Adding to ID map: %s %s" % (item['id'], get_system_bot(item['email']).id)) new_user_id = get_system_bot(item['email']).id update_id_map(table='user_profile', old_id=item['id'], new_id=new_user_id) new_recipient_id = Recipient.objects.get(type=Recipient.PERSONAL, type_id=new_user_id).id update_id_map(table='recipient', old_id=item['recipient_id'], new_id=new_recipient_id) # Merge in zerver_userprofile_mirrordummy data['zerver_userprofile'] = data['zerver_userprofile'] + data['zerver_userprofile_mirrordummy'] del data['zerver_userprofile_mirrordummy'] data['zerver_userprofile'].sort(key=lambda r: r['id']) # To remap foreign key for UserProfile.last_active_message_id update_message_foreign_keys(import_dir=import_dir, sort_by_date=sort_by_date) fix_datetime_fields(data, 'zerver_userprofile') update_model_ids(UserProfile, data, 'user_profile') re_map_foreign_keys(data, 'zerver_userprofile', 'realm', related_table="realm") re_map_foreign_keys(data, 'zerver_userprofile', 'bot_owner', related_table="user_profile") re_map_foreign_keys(data, 'zerver_userprofile', 'default_sending_stream', related_table="stream") re_map_foreign_keys(data, 'zerver_userprofile', 'default_events_register_stream', related_table="stream") re_map_foreign_keys(data, 'zerver_userprofile', 'last_active_message_id', related_table="message", id_field=True) for user_profile_dict in data['zerver_userprofile']: user_profile_dict['password'] = None user_profile_dict['api_key'] = generate_api_key() # Since Zulip doesn't use these permissions, drop them del user_profile_dict['user_permissions'] del user_profile_dict['groups'] user_profiles = [UserProfile(**item) for item in data['zerver_userprofile']] for user_profile in user_profiles: user_profile.set_unusable_password() UserProfile.objects.bulk_create(user_profiles) re_map_foreign_keys(data, 'zerver_defaultstream', 'stream', related_table="stream") re_map_foreign_keys(data, 'zerver_realmemoji', 'author', related_table="user_profile") for (table, model, related_table) in realm_tables: re_map_foreign_keys(data, table, 'realm', related_table="realm") update_model_ids(model, data, related_table) bulk_import_model(data, model) if 'zerver_huddle' in data: update_model_ids(Huddle, data, 'huddle') # We don't import Huddle yet, since we don't have the data to # compute huddle hashes until we've imported some of the # tables below. # TODO: double-check this. re_map_foreign_keys(data, 'zerver_recipient', 'type_id', related_table="stream", recipient_field=True, id_field=True) re_map_foreign_keys(data, 'zerver_recipient', 'type_id', related_table="user_profile", recipient_field=True, id_field=True) re_map_foreign_keys(data, 'zerver_recipient', 'type_id', related_table="huddle", recipient_field=True, id_field=True) update_model_ids(Recipient, data, 'recipient') bulk_import_model(data, Recipient) re_map_foreign_keys(data, 'zerver_subscription', 'user_profile', related_table="user_profile") get_huddles_from_subscription(data, 'zerver_subscription') re_map_foreign_keys(data, 'zerver_subscription', 'recipient', related_table="recipient") update_model_ids(Subscription, data, 'subscription') bulk_import_model(data, Subscription) if 'zerver_realmauditlog' in data: fix_datetime_fields(data, 'zerver_realmauditlog') re_map_foreign_keys(data, 'zerver_realmauditlog', 'realm', related_table="realm") re_map_foreign_keys(data, 'zerver_realmauditlog', 'modified_user', related_table='user_profile') re_map_foreign_keys(data, 'zerver_realmauditlog', 'acting_user', related_table='user_profile') re_map_foreign_keys(data, 'zerver_realmauditlog', 'modified_stream', related_table="stream") update_model_ids(RealmAuditLog, data, related_table="realmauditlog") bulk_import_model(data, RealmAuditLog) else: logging.info('about to call create_subscription_events') create_subscription_events( data=data, realm_id=realm.id, ) logging.info('done with create_subscription_events') if 'zerver_huddle' in data: process_huddle_hash(data, 'zerver_huddle') bulk_import_model(data, Huddle) if 'zerver_userhotspot' in data: fix_datetime_fields(data, 'zerver_userhotspot') re_map_foreign_keys(data, 'zerver_userhotspot', 'user', related_table='user_profile') update_model_ids(UserHotspot, data, 'userhotspot') bulk_import_model(data, UserHotspot) if 'zerver_mutedtopic' in data: re_map_foreign_keys(data, 'zerver_mutedtopic', 'user_profile', related_table='user_profile') re_map_foreign_keys(data, 'zerver_mutedtopic', 'stream', related_table='stream') re_map_foreign_keys(data, 'zerver_mutedtopic', 'recipient', related_table='recipient') update_model_ids(MutedTopic, data, 'mutedtopic') bulk_import_model(data, MutedTopic) if 'zerver_service' in data: re_map_foreign_keys(data, 'zerver_service', 'user_profile', related_table='user_profile') fix_service_tokens(data, 'zerver_service') update_model_ids(Service, data, 'service') bulk_import_model(data, Service) if 'zerver_usergroup' in data: re_map_foreign_keys(data, 'zerver_usergroup', 'realm', related_table='realm') re_map_foreign_keys_many_to_many(data, 'zerver_usergroup', 'members', related_table='user_profile') update_model_ids(UserGroup, data, 'usergroup') bulk_import_model(data, UserGroup) re_map_foreign_keys(data, 'zerver_usergroupmembership', 'user_group', related_table='usergroup') re_map_foreign_keys(data, 'zerver_usergroupmembership', 'user_profile', related_table='user_profile') update_model_ids(UserGroupMembership, data, 'usergroupmembership') bulk_import_model(data, UserGroupMembership) if 'zerver_botstoragedata' in data: re_map_foreign_keys(data, 'zerver_botstoragedata', 'bot_profile', related_table='user_profile') update_model_ids(BotStorageData, data, 'botstoragedata') bulk_import_model(data, BotStorageData) if 'zerver_botconfigdata' in data: re_map_foreign_keys(data, 'zerver_botconfigdata', 'bot_profile', related_table='user_profile') update_model_ids(BotConfigData, data, 'botconfigdata') bulk_import_model(data, BotConfigData) fix_datetime_fields(data, 'zerver_userpresence') re_map_foreign_keys(data, 'zerver_userpresence', 'user_profile', related_table="user_profile") re_map_foreign_keys(data, 'zerver_userpresence', 'client', related_table='client') update_model_ids(UserPresence, data, 'user_presence') bulk_import_model(data, UserPresence) fix_datetime_fields(data, 'zerver_useractivity') re_map_foreign_keys(data, 'zerver_useractivity', 'user_profile', related_table="user_profile") re_map_foreign_keys(data, 'zerver_useractivity', 'client', related_table='client') update_model_ids(UserActivity, data, 'useractivity') bulk_import_model(data, UserActivity) fix_datetime_fields(data, 'zerver_useractivityinterval') re_map_foreign_keys(data, 'zerver_useractivityinterval', 'user_profile', related_table="user_profile") update_model_ids(UserActivityInterval, data, 'useractivityinterval') bulk_import_model(data, UserActivityInterval) re_map_foreign_keys(data, 'zerver_customprofilefield', 'realm', related_table="realm") update_model_ids(CustomProfileField, data, related_table="customprofilefield") bulk_import_model(data, CustomProfileField) re_map_foreign_keys(data, 'zerver_customprofilefieldvalue', 'user_profile', related_table="user_profile") re_map_foreign_keys(data, 'zerver_customprofilefieldvalue', 'field', related_table="customprofilefield") fix_customprofilefield(data) update_model_ids(CustomProfileFieldValue, data, related_table="customprofilefieldvalue") bulk_import_model(data, CustomProfileFieldValue) # Import uploaded files and avatars import_uploads(os.path.join(import_dir, "avatars"), processing_avatars=True) import_uploads(os.path.join(import_dir, "uploads")) # We need to have this check as the emoji files are only present in the data # importer from slack # For Zulip export, this doesn't exist if os.path.exists(os.path.join(import_dir, "emoji")): import_uploads(os.path.join(import_dir, "emoji"), processing_emojis=True) sender_map = { user['id']: user for user in data['zerver_userprofile'] } # Import zerver_message and zerver_usermessage import_message_data(realm=realm, sender_map=sender_map, import_dir=import_dir) re_map_foreign_keys(data, 'zerver_reaction', 'message', related_table="message") re_map_foreign_keys(data, 'zerver_reaction', 'user_profile', related_table="user_profile") re_map_foreign_keys(data, 'zerver_reaction', 'emoji_code', related_table="realmemoji", id_field=True, reaction_field=True) update_model_ids(Reaction, data, 'reaction') bulk_import_model(data, Reaction) # Do attachments AFTER message data is loaded. # TODO: de-dup how we read these json files. fn = os.path.join(import_dir, "attachment.json") if not os.path.exists(fn): raise Exception("Missing attachment.json file!") logging.info("Importing attachment data from %s" % (fn,)) with open(fn) as f: data = ujson.load(f) import_attachments(data) if settings.BILLING_ENABLED: do_change_plan_type(realm, Realm.LIMITED) return realm