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 %} +
+ Team size limit reached. + To invite more members to your team, please + upgrade your account! +
+ {% 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. -