From 8c134570371c2af66a26fe5dcd63f0761584ffe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Mon, 3 Aug 2020 17:52:09 +0300 Subject: [PATCH] Use separate counters for SMS and phone calls. --- hc/accounts/admin.py | 3 ++ .../migrations/0031_auto_20200803_1413.py | 28 +++++++++++++++++ hc/accounts/models.py | 30 +++++++++++++++++++ hc/accounts/tests/test_signup.py | 18 ++++++++++- hc/api/migrations/0074_auto_20200803_1411.py | 18 +++++++++++ hc/api/tests/test_notify.py | 20 +++++++++++-- hc/api/transports.py | 2 +- hc/payments/tests/test_update_subscription.py | 19 ++++++++++++ hc/payments/views.py | 7 +++++ templates/front/channels.html | 5 +++- templates/payments/pricing.html | 28 ++++++++++++----- 11 files changed, 166 insertions(+), 12 deletions(-) create mode 100644 hc/accounts/migrations/0031_auto_20200803_1413.py create mode 100644 hc/api/migrations/0074_auto_20200803_1411.py diff --git a/hc/accounts/admin.py b/hc/accounts/admin.py index 604ae999..2e7d58a6 100644 --- a/hc/accounts/admin.py +++ b/hc/accounts/admin.py @@ -62,6 +62,9 @@ class TeamFieldset(Fieldset): "sms_limit", "sms_sent", "last_sms_date", + "call_limit", + "calls_sent", + "last_call_date", ) diff --git a/hc/accounts/migrations/0031_auto_20200803_1413.py b/hc/accounts/migrations/0031_auto_20200803_1413.py new file mode 100644 index 00000000..2df40fef --- /dev/null +++ b/hc/accounts/migrations/0031_auto_20200803_1413.py @@ -0,0 +1,28 @@ +# Generated by Django 3.0.8 on 2020-08-03 14:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0030_member_transfer_request_date'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='call_limit', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='profile', + name='calls_sent', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='profile', + name='last_call_date', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/hc/accounts/models.py b/hc/accounts/models.py index cd7f5219..397e46f7 100644 --- a/hc/accounts/models.py +++ b/hc/accounts/models.py @@ -37,6 +37,7 @@ class ProfileManager(models.Manager): # If not using payments, set high limits profile.check_limit = 500 profile.sms_limit = 500 + profile.call_limit = 500 profile.team_limit = 500 profile.save() @@ -52,9 +53,15 @@ class Profile(models.Model): ping_log_limit = models.IntegerField(default=100) check_limit = models.IntegerField(default=20) token = models.CharField(max_length=128, blank=True) + last_sms_date = models.DateTimeField(null=True, blank=True) sms_limit = models.IntegerField(default=5) sms_sent = models.IntegerField(default=0) + + last_call_date = models.DateTimeField(null=True, blank=True) + call_limit = models.IntegerField(default=0) + calls_sent = models.IntegerField(default=0) + team_limit = models.IntegerField(default=2) sort = models.CharField(max_length=20, default="created") deletion_notice_date = models.DateTimeField(null=True, blank=True) @@ -229,6 +236,29 @@ class Profile(models.Model): self.save() return True + def calls_sent_this_month(self): + # IF last_call_date was never set, we have not made any phone calls yet. + if not self.last_call_date: + return 0 + + # If last sent date is not from this month, we've made 0 calls this month. + if month(timezone.now()) > month(self.last_call_date): + return 0 + + return self.calls_sent + + def authorize_call(self): + """ If monthly limit not exceeded, increase counter and return True """ + + sent_this_month = self.calls_sent_this_month() + if sent_this_month >= self.call_limit: + return False + + self.calls_sent = sent_this_month + 1 + self.last_call_date = timezone.now() + self.save() + return True + def num_checks_used(self): from hc.api.models import Check diff --git a/hc/accounts/tests/test_signup.py b/hc/accounts/tests/test_signup.py index a6d7ad28..07aacc7d 100644 --- a/hc/accounts/tests/test_signup.py +++ b/hc/accounts/tests/test_signup.py @@ -2,7 +2,7 @@ from django.contrib.auth.models import User from django.core import mail from django.test import TestCase from django.test.utils import override_settings -from hc.accounts.models import Project +from hc.accounts.models import Profile, Project from hc.api.models import Channel, Check from django.conf import settings @@ -18,6 +18,11 @@ class SignupTestCase(TestCase): # An user should have been created user = User.objects.get() + # A profile should have been created + profile = Profile.objects.get() + self.assertEqual(profile.sms_limit, 5) + self.assertEqual(profile.call_limit, 0) + # And email sent self.assertEqual(len(mail.outbox), 1) subject = "Log in to %s" % settings.SITE_NAME @@ -37,6 +42,17 @@ class SignupTestCase(TestCase): channel = Channel.objects.get() self.assertEqual(channel.project, project) + @override_settings(USE_PAYMENTS=False) + def test_it_sets_high_limits(self): + form = {"identity": "alice@example.org"} + + self.client.post("/accounts/signup/", form) + + # A profile should have been created + profile = Profile.objects.get() + self.assertEqual(profile.sms_limit, 500) + self.assertEqual(profile.call_limit, 500) + @override_settings(REGISTRATION_OPEN=False) def test_it_obeys_registration_open(self): form = {"identity": "dan@example.org"} diff --git a/hc/api/migrations/0074_auto_20200803_1411.py b/hc/api/migrations/0074_auto_20200803_1411.py new file mode 100644 index 00000000..c011a0c5 --- /dev/null +++ b/hc/api/migrations/0074_auto_20200803_1411.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.8 on 2020-08-03 14:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0073_auto_20200721_1000'), + ] + + operations = [ + migrations.AlterField( + model_name='channel', + name='kind', + field=models.CharField(choices=[('email', 'Email'), ('webhook', 'Webhook'), ('hipchat', 'HipChat'), ('slack', 'Slack'), ('pd', 'PagerDuty'), ('pagertree', 'PagerTree'), ('pagerteam', 'Pager Team'), ('po', 'Pushover'), ('pushbullet', 'Pushbullet'), ('opsgenie', 'OpsGenie'), ('victorops', 'VictorOps'), ('discord', 'Discord'), ('telegram', 'Telegram'), ('sms', 'SMS'), ('zendesk', 'Zendesk'), ('trello', 'Trello'), ('matrix', 'Matrix'), ('whatsapp', 'WhatsApp'), ('apprise', 'Apprise'), ('mattermost', 'Mattermost'), ('msteams', 'Microsoft Teams'), ('shell', 'Shell Command'), ('zulip', 'Zulip'), ('spike', 'Spike'), ('call', 'Phone Call')], max_length=20), + ), + ] diff --git a/hc/api/tests/test_notify.py b/hc/api/tests/test_notify.py index a64da31f..2b0c82d7 100644 --- a/hc/api/tests/test_notify.py +++ b/hc/api/tests/test_notify.py @@ -756,6 +756,9 @@ class NotifyTestCase(BaseTestCase): @patch("hc.api.transports.requests.request") def test_call(self, mock_post): + self.profile.call_limit = 1 + self.profile.save() + value = {"label": "foo", "value": "+1234567890"} self._setup_data("call", json.dumps(value)) self.check.last_ping = now() - td(hours=2) @@ -772,8 +775,8 @@ class NotifyTestCase(BaseTestCase): @patch("hc.api.transports.requests.request") def test_call_limit(self, mock_post): # At limit already: - self.profile.last_sms_date = now() - self.profile.sms_sent = 50 + self.profile.last_call_date = now() + self.profile.calls_sent = 50 self.profile.save() definition = {"value": "+1234567890"} @@ -792,6 +795,19 @@ class NotifyTestCase(BaseTestCase): self.assertEqual(email.to[0], "alice@example.org") self.assertEqual(email.subject, "Monthly Phone Call Limit Reached") + @patch("hc.api.transports.requests.request") + def test_call_limit_reset(self, mock_post): + # At limit, but also into a new month + self.profile.calls_sent = 50 + self.profile.last_call_date = now() - td(days=100) + self.profile.save() + + self._setup_data("sms", "+1234567890") + mock_post.return_value.status_code = 200 + + self.channel.notify(self.check) + self.assertTrue(mock_post.called) + @patch("apprise.Apprise") @override_settings(APPRISE_ENABLED=True) def test_apprise_enabled(self, mock_apprise): diff --git a/hc/api/transports.py b/hc/api/transports.py index 3ed517e5..f9967b36 100644 --- a/hc/api/transports.py +++ b/hc/api/transports.py @@ -486,7 +486,7 @@ class Call(HttpTransport): def notify(self, check): profile = Profile.objects.for_user(self.channel.project.owner) - if not profile.authorize_sms(): + if not profile.authorize_call(): profile.send_sms_limit_notice("phone call") return "Monthly phone call limit exceeded" diff --git a/hc/payments/tests/test_update_subscription.py b/hc/payments/tests/test_update_subscription.py index 40b32cfb..2412b5da 100644 --- a/hc/payments/tests/test_update_subscription.py +++ b/hc/payments/tests/test_update_subscription.py @@ -22,6 +22,8 @@ class UpdateSubscriptionTestCase(BaseTestCase): self.profile.sms_limit = 0 self.profile.sms_sent = 1 + self.profile.call_limit = 0 + self.profile.calls_sent = 1 self.profile.save() r = self.run_update() @@ -41,6 +43,8 @@ class UpdateSubscriptionTestCase(BaseTestCase): self.assertEqual(self.profile.team_limit, 9) self.assertEqual(self.profile.sms_limit, 50) self.assertEqual(self.profile.sms_sent, 0) + self.assertEqual(self.profile.call_limit, 20) + self.assertEqual(self.profile.calls_sent, 0) # braintree.Subscription.cancel should have not been called # because there was no previous subscription @@ -54,6 +58,8 @@ class UpdateSubscriptionTestCase(BaseTestCase): self.profile.sms_limit = 0 self.profile.sms_sent = 1 + self.profile.call_limit = 0 + self.profile.calls_sent = 1 self.profile.save() r = self.run_update("S5") @@ -72,6 +78,8 @@ class UpdateSubscriptionTestCase(BaseTestCase): self.assertEqual(self.profile.team_limit, 2) self.assertEqual(self.profile.sms_limit, 5) self.assertEqual(self.profile.sms_sent, 0) + self.assertEqual(self.profile.call_limit, 5) + self.assertEqual(self.profile.calls_sent, 0) # braintree.Subscription.cancel should have not been called assert not mock.Subscription.cancel.called @@ -82,6 +90,8 @@ class UpdateSubscriptionTestCase(BaseTestCase): self.profile.sms_limit = 0 self.profile.sms_sent = 1 + self.profile.call_limit = 0 + self.profile.calls_sent = 1 self.profile.save() r = self.run_update("Y192") @@ -100,6 +110,8 @@ class UpdateSubscriptionTestCase(BaseTestCase): self.assertEqual(self.profile.team_limit, 9) self.assertEqual(self.profile.sms_limit, 50) self.assertEqual(self.profile.sms_sent, 0) + self.assertEqual(self.profile.call_limit, 20) + self.assertEqual(self.profile.calls_sent, 0) # braintree.Subscription.cancel should have not been called assert not mock.Subscription.cancel.called @@ -110,6 +122,8 @@ class UpdateSubscriptionTestCase(BaseTestCase): self.profile.sms_limit = 0 self.profile.sms_sent = 1 + self.profile.call_limit = 0 + self.profile.calls_sent = 1 self.profile.save() r = self.run_update("P80") @@ -128,6 +142,8 @@ class UpdateSubscriptionTestCase(BaseTestCase): self.assertEqual(self.profile.team_limit, 500) self.assertEqual(self.profile.sms_limit, 500) self.assertEqual(self.profile.sms_sent, 0) + self.assertEqual(self.profile.call_limit, 100) + self.assertEqual(self.profile.calls_sent, 0) # braintree.Subscription.cancel should have not been called assert not mock.Subscription.cancel.called @@ -144,6 +160,8 @@ class UpdateSubscriptionTestCase(BaseTestCase): self.profile.sms_limit = 1 self.profile.sms_sent = 1 + self.profile.call_limit = 1 + self.profile.calls_sent = 1 self.profile.save() r = self.run_update("") @@ -162,6 +180,7 @@ class UpdateSubscriptionTestCase(BaseTestCase): self.assertEqual(self.profile.check_limit, 20) self.assertEqual(self.profile.team_limit, 2) self.assertEqual(self.profile.sms_limit, 5) + self.assertEqual(self.profile.call_limit, 0) self.assertTrue(mock.Subscription.cancel.called) diff --git a/hc/payments/views.py b/hc/payments/views.py index 7ceb2545..367612a4 100644 --- a/hc/payments/views.py +++ b/hc/payments/views.py @@ -117,6 +117,7 @@ def update(request): profile.check_limit = 20 profile.team_limit = 2 profile.sms_limit = 5 + profile.call_limit = 0 profile.save() if plan_id == "": @@ -135,6 +136,8 @@ def update(request): profile.ping_log_limit = 1000 profile.sms_limit = 5 profile.sms_sent = 0 + profile.call_limit = 5 + profile.calls_sent = 0 profile.save() elif plan_id in ("P20", "Y192"): profile.check_limit = 100 @@ -142,6 +145,8 @@ def update(request): profile.ping_log_limit = 1000 profile.sms_limit = 50 profile.sms_sent = 0 + profile.call_limit = 20 + profile.calls_sent = 0 profile.save() elif plan_id in ("P80", "Y768"): profile.check_limit = 1000 @@ -149,6 +154,8 @@ def update(request): profile.ping_log_limit = 1000 profile.sms_limit = 500 profile.sms_sent = 0 + profile.call_limit = 100 + profile.calls_sent = 0 profile.save() request.session["set_plan_status"] = "success" diff --git a/templates/front/channels.html b/templates/front/channels.html index 5954bedc..bad04b44 100644 --- a/templates/front/channels.html +++ b/templates/front/channels.html @@ -124,9 +124,12 @@ {% else %} Never {% endif %} - {% if ch.kind == "sms" or ch.kind == "whatsapp" or ch.kind == "call" %} + {% if ch.kind == "sms" or ch.kind == "whatsapp" %}

Used {{ profile.sms_sent_this_month }} of {{ profile.sms_limit }} sends this month.

{% endif %} + {% if ch.kind == "call" %} +

Used {{ profile.calls_sent_this_month }} of {{ profile.call_limit }} phone calls this month.

+ {% endif %} {% if ch.kind == "webhook" %} diff --git a/templates/payments/pricing.html b/templates/payments/pricing.html index 692405d5..f8973bb5 100644 --- a/templates/payments/pricing.html +++ b/templates/payments/pricing.html @@ -87,9 +87,10 @@
  • API access
  • - 5 SMS, WhatsApp and call credits + 5 SMS & WhatsApp credits
  •  
  • +
  •  
  • {% if not request.user.is_authenticated %} @@ -120,7 +121,10 @@
  • API access
  • - 5 SMS, WhatsApp and call credits + 5 SMS & WhatsApp credits +
  • +
  • + 5 phone call credits
  • Email support
  • @@ -156,7 +160,10 @@
  • API access
  • - 50 SMS, WhatsApp and call credits + 50 SMS & WhatsApp credits +
  • +
  • + 20 phone call credits
  • Email support
  • @@ -192,7 +199,10 @@
  • API access
  • - 500 SMS, WhatsApp and call credits + 500 SMS & WhatsApp credits +
  • +
  • + 100 phone call credits
  • Priority email support
  • @@ -303,10 +313,14 @@ -

    The limit is applied to the combined number of sent SMS, WhatsApp and phone - call notifications.

    + {% if not request.user.is_authenticated %}