From 0723476a0cb1b6026642b20e5f21797207808d9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C4=93teris=20Caune?=
Date: Sat, 2 Sep 2017 15:44:28 +0300
Subject: [PATCH] All plans now have team access, but different team size
limits.
---
hc/accounts/admin.py | 20 +++++++++----
.../migrations/0010_profile_team_limit.py | 20 +++++++++++++
hc/accounts/models.py | 5 ++++
hc/accounts/tests/test_profile.py | 18 +++++-------
hc/accounts/views.py | 5 +---
hc/payments/tests/test_cancel_plan.py | 2 +-
hc/payments/tests/test_create_plan.py | 3 +-
hc/payments/views.py | 3 ++
hc/test.py | 1 -
templates/accounts/profile.html | 16 +++++-----
templates/payments/pricing.html | 29 ++++++++++---------
11 files changed, 78 insertions(+), 44 deletions(-)
create mode 100644 hc/accounts/migrations/0010_profile_team_limit.py
diff --git a/hc/accounts/admin.py b/hc/accounts/admin.py
index f88b26ad..17f5a58d 100644
--- a/hc/accounts/admin.py
+++ b/hc/accounts/admin.py
@@ -1,6 +1,7 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
+from django.db.models import Count
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.safestring import mark_safe
@@ -25,7 +26,7 @@ class ProfileFieldset(Fieldset):
class TeamFieldset(Fieldset):
name = "Team"
- fields = ("team_name", "team_access_allowed", "check_limit",
+ fields = ("team_name", "team_limit", "check_limit",
"ping_log_limit", "sms_limit", "sms_sent", "last_sms_date",
"bill_to")
@@ -41,17 +42,23 @@ class ProfileAdmin(admin.ModelAdmin):
readonly_fields = ("user", "email")
raw_id_fields = ("current_team", )
list_select_related = ("user", )
- list_display = ("id", "users", "checks", "team_access_allowed",
+ list_display = ("id", "users", "checks", "invited",
"reports_allowed", "ping_log_limit", "sms")
search_fields = ["id", "user__email"]
- list_filter = ("team_access_allowed", "reports_allowed",
+ list_filter = ("team_access_allowed", "team_limit", "reports_allowed",
"check_limit", "next_report_date")
fieldsets = (ProfileFieldset.tuple(), TeamFieldset.tuple())
+ def get_queryset(self, request):
+ qs = super(ProfileAdmin, self).get_queryset(request)
+ qs = qs.annotate(Count("member", distinct=True))
+ qs = qs.annotate(Count("user__check", distinct=True))
+ return qs
+
@mark_safe
def users(self, obj):
- if obj.member_set.count() == 0:
+ if obj.member__count == 0:
return obj.user.email
else:
return render_to_string("admin/profile_list_team.html", {
@@ -60,7 +67,7 @@ class ProfileAdmin(admin.ModelAdmin):
@mark_safe
def checks(self, obj):
- num_checks = Check.objects.filter(user=obj.user).count()
+ num_checks = obj.user__check__count
pct = 100 * num_checks / max(obj.check_limit, 1)
pct = min(100, int(pct))
@@ -69,6 +76,9 @@ class ProfileAdmin(admin.ModelAdmin):
%d of %d
""" % (pct, num_checks, obj.check_limit)
+ def invited(self, obj):
+ return "%d of %d" % (obj.member__count, obj.team_limit)
+
def sms(self, obj):
return "%d of %d" % (obj.sms_sent, obj.sms_limit)
diff --git a/hc/accounts/migrations/0010_profile_team_limit.py b/hc/accounts/migrations/0010_profile_team_limit.py
new file mode 100644
index 00000000..299fd031
--- /dev/null
+++ b/hc/accounts/migrations/0010_profile_team_limit.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-09-02 11:52
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('accounts', '0009_auto_20170714_1734'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='profile',
+ name='team_limit',
+ field=models.IntegerField(default=2),
+ ),
+ ]
diff --git a/hc/accounts/models.py b/hc/accounts/models.py
index 2a711153..b74fbe76 100644
--- a/hc/accounts/models.py
+++ b/hc/accounts/models.py
@@ -28,6 +28,7 @@ class ProfileManager(models.Manager):
# If not using payments, set high limits
profile.check_limit = 500
profile.sms_limit = 500
+ profile.team_limit = 500
profile.save()
return profile
@@ -49,6 +50,7 @@ class Profile(models.Model):
last_sms_date = models.DateTimeField(null=True, blank=True)
sms_limit = models.IntegerField(default=0)
sms_sent = models.IntegerField(default=0)
+ team_limit = models.IntegerField(default=2)
objects = ProfileManager()
@@ -121,6 +123,9 @@ class Profile(models.Model):
emails.report(self.user.email, ctx)
+ def can_invite(self):
+ return self.member_set.count() < self.team_limit
+
def invite(self, user):
member = Member(team=self, user=user)
member.save()
diff --git a/hc/accounts/tests/test_profile.py b/hc/accounts/tests/test_profile.py
index ecd3463c..ceadde46 100644
--- a/hc/accounts/tests/test_profile.py
+++ b/hc/accounts/tests/test_profile.py
@@ -75,15 +75,18 @@ class ProfileTestCase(BaseTestCase):
# And an email should have been sent
subj = ('You have been invited to join'
- ' alice@example.org on {0}'.format(getattr(settings, "SITE_NAME")))
+ ' alice@example.org on %s' % settings.SITE_NAME)
self.assertEqual(mail.outbox[0].subject, subj)
- def test_add_team_member_checks_team_access_allowed_flag(self):
- self.client.login(username="charlie@example.org", password="password")
+ def test_it_checks_team_size(self):
+ self.profile.team_limit = 0
+ self.profile.save()
+
+ self.client.login(username="alice@example.org", password="password")
form = {"invite_team_member": "1", "email": "frank@example.org"}
r = self.client.post("/accounts/profile/", form)
- assert r.status_code == 403
+ self.assertEqual(r.status_code, 403)
def test_it_removes_team_member(self):
self.client.login(username="alice@example.org", password="password")
@@ -107,13 +110,6 @@ class ProfileTestCase(BaseTestCase):
self.alice.profile.refresh_from_db()
self.assertEqual(self.alice.profile.team_name, "Alpha Team")
- def test_set_team_name_checks_team_access_allowed_flag(self):
- self.client.login(username="charlie@example.org", password="password")
-
- form = {"set_team_name": "1", "team_name": "Charlies Team"}
- r = self.client.post("/accounts/profile/", form)
- assert r.status_code == 403
-
def test_it_switches_to_own_team(self):
self.client.login(username="bob@example.org", password="password")
diff --git a/hc/accounts/views.py b/hc/accounts/views.py
index 2b483695..23753452 100644
--- a/hc/accounts/views.py
+++ b/hc/accounts/views.py
@@ -189,7 +189,7 @@ def profile(request):
elif "show_api_key" in request.POST:
ctx["show_api_key"] = True
elif "invite_team_member" in request.POST:
- if not profile.team_access_allowed:
+ if not profile.can_invite():
return HttpResponseForbidden()
form = InviteTeamMemberForm(request.POST)
@@ -220,9 +220,6 @@ def profile(request):
ctx["team_member_removed"] = email
ctx["team_status"] = "info"
elif "set_team_name" in request.POST:
- if not profile.team_access_allowed:
- return HttpResponseForbidden()
-
form = TeamNameForm(request.POST)
if form.is_valid():
profile.team_name = form.cleaned_data["team_name"]
diff --git a/hc/payments/tests/test_cancel_plan.py b/hc/payments/tests/test_cancel_plan.py
index 7ba91b5f..5bff942b 100644
--- a/hc/payments/tests/test_cancel_plan.py
+++ b/hc/payments/tests/test_cancel_plan.py
@@ -34,5 +34,5 @@ class CancelPlanTestCase(BaseTestCase):
profile = Profile.objects.get(user=self.alice)
self.assertEqual(profile.ping_log_limit, 100)
self.assertEqual(profile.check_limit, 20)
+ self.assertEqual(profile.team_limit, 2)
self.assertEqual(profile.sms_limit, 0)
- self.assertFalse(profile.team_access_allowed)
diff --git a/hc/payments/tests/test_create_plan.py b/hc/payments/tests/test_create_plan.py
index b0ecd581..517112f2 100644
--- a/hc/payments/tests/test_create_plan.py
+++ b/hc/payments/tests/test_create_plan.py
@@ -28,7 +28,6 @@ class CreatePlanTestCase(BaseTestCase):
def test_it_works(self, mock):
self._setup_mock(mock)
- self.profile.team_access_allowed = False
self.profile.sms_limit = 0
self.profile.sms_sent = 1
self.profile.save()
@@ -47,9 +46,9 @@ class CreatePlanTestCase(BaseTestCase):
self.profile.refresh_from_db()
self.assertEqual(self.profile.ping_log_limit, 1000)
self.assertEqual(self.profile.check_limit, 500)
+ self.assertEqual(self.profile.team_limit, 9)
self.assertEqual(self.profile.sms_limit, 50)
self.assertEqual(self.profile.sms_sent, 0)
- self.assertTrue(self.profile.team_access_allowed)
# braintree.Subscription.cancel should have not been called
assert not mock.Subscription.cancel.called
diff --git a/hc/payments/views.py b/hc/payments/views.py
index e9455921..8c936fc2 100644
--- a/hc/payments/views.py
+++ b/hc/payments/views.py
@@ -110,6 +110,7 @@ def create_plan(request):
if plan_id == "P5":
profile.ping_log_limit = 1000
profile.check_limit = 500
+ profile.team_limit = 9
profile.sms_limit = 50
profile.sms_sent = 0
profile.team_access_allowed = True
@@ -117,6 +118,7 @@ def create_plan(request):
elif plan_id == "P50":
profile.ping_log_limit = 1000
profile.check_limit = 500
+ profile.team_limit = 500
profile.sms_limit = 500
profile.sms_sent = 0
profile.team_access_allowed = True
@@ -169,6 +171,7 @@ def cancel_plan(request):
profile = request.user.profile
profile.ping_log_limit = 100
profile.check_limit = 20
+ profile.team_limit = 2
profile.sms_limit = 0
profile.team_access_allowed = False
profile.save()
diff --git a/hc/test.py b/hc/test.py
index ffe021cb..d92b567a 100644
--- a/hc/test.py
+++ b/hc/test.py
@@ -15,7 +15,6 @@ class BaseTestCase(TestCase):
self.alice.save()
self.profile = Profile(user=self.alice, api_key="abc")
- self.profile.team_access_allowed = True
self.profile.sms_limit = 50
self.profile.save()
diff --git a/templates/accounts/profile.html b/templates/accounts/profile.html
index 7078d4e8..dac09ab2 100644
--- a/templates/accounts/profile.html
+++ b/templates/accounts/profile.html
@@ -134,23 +134,25 @@
Share access to your checks and configured integrations
without having to share a login.
- {% if not profile.team_access_allowed %}
-
- To enable team access, please upgrade to
- one of the paid plans.
-
- {% endif %}
{% endif %}
- {% if profile.team_access_allowed %}
+ {% if not profile.can_invite %}
+
+ {% endif %}
+
Set Team Name
+ {% if profile.can_invite %}
20 Checks
100 log entries per check
Personal or Commercial use
- Single User
+ 3 Team Members
@@ -105,7 +105,7 @@
Unlimited Checks
1000 log entries per check
Personal or Commercial use
- Team Access
+ 10 Team Members
50 SMS alerts per month
Email Support
@@ -147,7 +147,7 @@
Unlimited Checks
1000 log entries per check
Personal or Commercial use
- Team Access
+ Unlimited Team Members
500 SMS alerts per month
Priority Email Support
@@ -213,6 +213,19 @@
Premium Features
+
What's "3 / 10 / Unlimited Team Members"?
+
+ Invite your colleagues
+ to your account so they can access your checks,
+ logs, and configured integrations. Inviting team members
+ is more convenient and more secure
+ than sharing a single login and password.
+
+
+ Each plan has a specific team size limit. When you reach
+ the limit, you cannot invite more team members.
+
+
What is the "log entries per check" number?
For each of your checks, healthchecks.io keeps a
@@ -236,16 +249,6 @@
log entries will only cover 8 hours.
-
What's Team Access?
-
- With Team Access enabled, you can "invite" your colleagues
- to your account. They will be able to access your checks,
- logs, and configured integrations.
-
-
- Team Access is more convenient and more secure than
- sharing a single login and password.
-