From 1c69cf7f8914730520f40d68fdef05c1ece62fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Sat, 12 Jan 2019 16:40:21 +0200 Subject: [PATCH] Project model. cc: #183 --- CHANGELOG.md | 2 + hc/accounts/admin.py | 11 +++++- hc/accounts/middleware.py | 4 ++ .../migrations/0017_auto_20190112_1426.py | 38 +++++++++++++++++++ .../migrations/0018_auto_20190112_1426.py | 37 ++++++++++++++++++ hc/accounts/models.py | 18 +++++++-- hc/accounts/tests/test_close_account.py | 1 + hc/accounts/tests/test_profile.py | 25 +++++++++--- hc/accounts/tests/test_signup.py | 14 ++++++- hc/accounts/tests/test_switch_team.py | 6 +++ hc/accounts/views.py | 35 +++++++++++++++-- hc/api/decorators.py | 2 + hc/api/migrations/0054_auto_20190112_1427.py | 25 ++++++++++++ hc/api/migrations/0055_auto_20190112_1427.py | 23 +++++++++++ hc/api/models.py | 3 ++ hc/api/tests/test_create_check.py | 1 + hc/api/views.py | 2 +- hc/front/tests/test_add_check.py | 14 ++++++- hc/front/tests/test_add_discord.py | 1 + hc/front/tests/test_add_email.py | 1 + hc/front/tests/test_add_hipchat.py | 1 + hc/front/tests/test_add_opsgenie.py | 1 + hc/front/tests/test_add_pagertree.py | 1 + hc/front/tests/test_add_pd.py | 1 + hc/front/tests/test_add_pushbullet.py | 1 + hc/front/tests/test_add_pushover.py | 6 +-- hc/front/tests/test_add_slack.py | 1 + hc/front/tests/test_add_slack_btn.py | 1 + hc/front/tests/test_add_sms.py | 1 + hc/front/tests/test_add_telegram.py | 3 +- hc/front/tests/test_add_victorops.py | 1 + hc/front/tests/test_add_webhook.py | 1 + hc/front/views.py | 23 +++++++---- hc/test.py | 10 ++++- 34 files changed, 283 insertions(+), 32 deletions(-) create mode 100644 hc/accounts/migrations/0017_auto_20190112_1426.py create mode 100644 hc/accounts/migrations/0018_auto_20190112_1426.py create mode 100644 hc/api/migrations/0054_auto_20190112_1427.py create mode 100644 hc/api/migrations/0055_auto_20190112_1427.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a51803b3..d5fc1aab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ All notable changes to this project will be documented in this file. - Database schema: add Ping.kind field - Database schema: remove Ping.start and Ping.fail fields - Add "Email Settings..." dialog and "Subject Must Contain" setting +- Database schema: add the Project model + ## 1.4.0 - 2018-12-25 diff --git a/hc/accounts/admin.py b/hc/accounts/admin.py index 3693fa09..deeef6d5 100644 --- a/hc/accounts/admin.py +++ b/hc/accounts/admin.py @@ -5,7 +5,7 @@ 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 -from hc.accounts.models import Profile +from hc.accounts.models import Profile, Project class Fieldset: @@ -85,6 +85,15 @@ class ProfileAdmin(admin.ModelAdmin): return obj.user.email +@admin.register(Project) +class ProjectAdmin(admin.ModelAdmin): + list_select_related = ("owner", ) + list_display = ("id", "name", "email") + + def email(self, obj): + return obj.owner.email + + class HcUserAdmin(UserAdmin): actions = ["send_report"] list_display = ('id', 'email', 'date_joined', 'last_login', 'engagement', diff --git a/hc/accounts/middleware.py b/hc/accounts/middleware.py index b40308b7..dd20e6de 100644 --- a/hc/accounts/middleware.py +++ b/hc/accounts/middleware.py @@ -16,4 +16,8 @@ class TeamAccessMiddleware(object): request.profile = Profile.objects.for_user(request.user) request.team = request.profile.team() + request.project = request.profile.current_project + if request.project is None: + request.project = request.team.user.project_set.first() + return self.get_response(request) diff --git a/hc/accounts/migrations/0017_auto_20190112_1426.py b/hc/accounts/migrations/0017_auto_20190112_1426.py new file mode 100644 index 00000000..4ef23c5d --- /dev/null +++ b/hc/accounts/migrations/0017_auto_20190112_1426.py @@ -0,0 +1,38 @@ +# Generated by Django 2.1.5 on 2019-01-12 14:26 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('accounts', '0016_remove_profile_bill_to'), + ] + + operations = [ + migrations.CreateModel( + name='Project', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('name', models.CharField(blank=True, max_length=200)), + ('api_key', models.CharField(blank=True, max_length=128)), + ('api_key_readonly', models.CharField(blank=True, max_length=128)), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddField( + model_name='member', + name='project', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.Project'), + ), + migrations.AddField( + model_name='profile', + name='current_project', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.Project'), + ), + ] diff --git a/hc/accounts/migrations/0018_auto_20190112_1426.py b/hc/accounts/migrations/0018_auto_20190112_1426.py new file mode 100644 index 00000000..e1d80f10 --- /dev/null +++ b/hc/accounts/migrations/0018_auto_20190112_1426.py @@ -0,0 +1,37 @@ +# Generated by Django 2.1.5 on 2019-01-11 14:49 + +from django.db import migrations + + +def create_projects(apps, schema_editor): + Profile = apps.get_model("accounts", "Profile") + Project = apps.get_model("accounts", "Project") + Member = apps.get_model("accounts", "Member") + for profile in Profile.objects.all(): + project = Project() + project.name = profile.team_name + project.owner_id = profile.user_id + project.api_key = profile.api_key + project.api_key_readonly = profile.api_key_readonly + project.save() + + profile.current_project = project + profile.save() + + Member.objects.filter(team=profile).update(project=project) + + for profile in Profile.objects.all(): + if profile.current_team_id: + profile.current_project = profile.current_team.current_project + profile.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0017_auto_20190112_1426'), + ] + + operations = [ + migrations.RunPython(create_projects, migrations.RunPython.noop), + ] diff --git a/hc/accounts/models.py b/hc/accounts/models.py index 6441f953..3ead56a2 100644 --- a/hc/accounts/models.py +++ b/hc/accounts/models.py @@ -1,6 +1,7 @@ from base64 import urlsafe_b64encode -import os from datetime import timedelta +import os +import uuid from django.conf import settings from django.contrib.auth.hashers import check_password, make_password @@ -54,6 +55,7 @@ class Profile(models.Model): api_key = models.CharField(max_length=128, blank=True) api_key_readonly = models.CharField(max_length=128, blank=True) current_team = models.ForeignKey("self", models.SET_NULL, null=True) + current_project = models.ForeignKey("Project", models.SET_NULL, null=True) last_sms_date = models.DateTimeField(null=True, blank=True) sms_limit = models.IntegerField(default=0) sms_sent = models.IntegerField(default=0) @@ -185,8 +187,9 @@ class Profile(models.Model): return self.member_set.count() < self.team_limit def invite(self, user): - member = Member(team=self, user=user) - member.save() + for project in self.user.project_set.all(): + member = Member(team=self, user=user, project=project) + member.save() # Switch the invited user over to the new team so they # notice the new team on next visit: @@ -231,6 +234,15 @@ class Profile(models.Model): q.update(next_nag_date=timezone.now() + models.F("nag_period")) +class Project(models.Model): + code = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) + name = models.CharField(max_length=200, blank=True) + owner = models.ForeignKey(User, models.CASCADE) + api_key = models.CharField(max_length=128, blank=True) + api_key_readonly = models.CharField(max_length=128, blank=True) + + class Member(models.Model): team = models.ForeignKey(Profile, models.CASCADE) user = models.ForeignKey(User, models.CASCADE, related_name="memberships") + project = models.ForeignKey(Project, models.CASCADE, null=True) diff --git a/hc/accounts/tests/test_close_account.py b/hc/accounts/tests/test_close_account.py index 63921d7e..df9a421d 100644 --- a/hc/accounts/tests/test_close_account.py +++ b/hc/accounts/tests/test_close_account.py @@ -27,6 +27,7 @@ class CloseAccountTestCase(BaseTestCase): # Bob's current team should now be None self.bobs_profile.refresh_from_db() self.assertIsNone(self.bobs_profile.current_team) + self.assertIsNone(self.bobs_profile.current_project) # Check should be gone self.assertFalse(Check.objects.exists()) diff --git a/hc/accounts/tests/test_profile.py b/hc/accounts/tests/test_profile.py index 387e4e97..f4d257c9 100644 --- a/hc/accounts/tests/test_profile.py +++ b/hc/accounts/tests/test_profile.py @@ -39,6 +39,9 @@ class ProfileTestCase(BaseTestCase): self.assertTrue(len(api_key) > 10) self.assertFalse("b'" in api_key) + self.project.refresh_from_db() + self.assertEqual(self.project.api_key, api_key) + def test_it_revokes_api_key(self): self.profile.api_key_readonly = "R" * 32 self.profile.save() @@ -53,6 +56,9 @@ class ProfileTestCase(BaseTestCase): self.assertEqual(self.profile.api_key, "") self.assertEqual(self.profile.api_key_readonly, "") + self.project.refresh_from_db() + self.assertEqual(self.project.api_key, "") + def test_it_sends_report(self): check = Check(name="Test Check", user=self.alice) check.last_ping = now() @@ -126,12 +132,16 @@ class ProfileTestCase(BaseTestCase): r = self.client.post("/accounts/profile/", form) self.assertEqual(r.status_code, 200) - member_emails = set() - for member in self.profile.member_set.all(): - member_emails.add(member.user.email) + members = self.profile.member_set.all() + self.assertEqual(members.count(), 2) - self.assertEqual(len(member_emails), 2) - self.assertTrue("frank@example.org" in member_emails) + frank_found = False + for member in members.all(): + self.assertEqual(member.project, self.project) + if member.user.email == "frank@example.org": + frank_found = True + + self.assertTrue(frank_found) # And an email should have been sent subj = ('You have been invited to join' @@ -159,6 +169,7 @@ class ProfileTestCase(BaseTestCase): self.bobs_profile.refresh_from_db() self.assertEqual(self.bobs_profile.current_team, None) + self.assertEqual(self.bobs_profile.current_project, None) def test_it_sets_team_name(self): self.client.login(username="alice@example.org", password="password") @@ -170,6 +181,9 @@ class ProfileTestCase(BaseTestCase): self.profile.refresh_from_db() self.assertEqual(self.profile.team_name, "Alpha Team") + self.project.refresh_from_db() + self.assertEqual(self.project.name, "Alpha Team") + def test_it_switches_to_own_team(self): self.client.login(username="bob@example.org", password="password") @@ -179,6 +193,7 @@ class ProfileTestCase(BaseTestCase): # to user's default team. self.bobs_profile.refresh_from_db() self.assertEqual(self.bobs_profile.current_team, self.bobs_profile) + self.assertEqual(self.bobs_profile.current_project, None) def test_it_sends_change_email_link(self): self.client.login(username="alice@example.org", password="password") diff --git a/hc/accounts/tests/test_signup.py b/hc/accounts/tests/test_signup.py index 2d601e51..aa2150b4 100644 --- a/hc/accounts/tests/test_signup.py +++ b/hc/accounts/tests/test_signup.py @@ -2,7 +2,8 @@ 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.api.models import Check +from hc.accounts.models import Project +from hc.api.models import Channel, Check from django.conf import settings @@ -15,16 +16,25 @@ class SignupTestCase(TestCase): self.assertContains(r, "Account created") # An user should have been created - self.assertEqual(User.objects.count(), 1) + user = User.objects.get() # And email sent self.assertEqual(len(mail.outbox), 1) subject = "Log in to %s" % settings.SITE_NAME self.assertEqual(mail.outbox[0].subject, subject) + # A project should have been created + project = Project.objects.get() + self.assertEqual(project.owner, user) + # And check should be associated with the new user check = Check.objects.get() self.assertEqual(check.name, "My First Check") + self.assertEqual(check.project, project) + + # A channel should have been created + channel = Channel.objects.get() + self.assertEqual(channel.project, project) @override_settings(REGISTRATION_OPEN=False) def test_it_obeys_registration_open(self): diff --git a/hc/accounts/tests/test_switch_team.py b/hc/accounts/tests/test_switch_team.py index 4b0da64b..2e44f3d9 100644 --- a/hc/accounts/tests/test_switch_team.py +++ b/hc/accounts/tests/test_switch_team.py @@ -5,6 +5,9 @@ from hc.api.models import Check class SwitchTeamTestCase(BaseTestCase): def test_it_switches(self): + self.bobs_profile.current_project = None + self.bobs_profile.save() + c = Check(user=self.alice, name="This belongs to Alice") c.save() @@ -15,6 +18,9 @@ class SwitchTeamTestCase(BaseTestCase): self.assertContains(r, "This belongs to Alice") + self.bobs_profile.refresh_from_db() + self.assertEqual(self.bobs_profile.current_project, self.project) + def test_it_checks_team_membership(self): self.client.login(username="charlie@example.org", password="password") diff --git a/hc/accounts/views.py b/hc/accounts/views.py index 0eeaccf7..c571ca31 100644 --- a/hc/accounts/views.py +++ b/hc/accounts/views.py @@ -21,7 +21,7 @@ from hc.accounts.forms import (ChangeEmailForm, EmailPasswordForm, ReportSettingsForm, SetPasswordForm, TeamNameForm, AvailableEmailForm, ExistingEmailForm) -from hc.accounts.models import Profile, Member +from hc.accounts.models import Profile, Project, Member from hc.api.models import Channel, Check from hc.lib.badges import get_badge_url from hc.payments.models import Subscription @@ -49,14 +49,19 @@ def _make_user(email): user.set_unusable_password() user.save() + project = Project(owner=user) + project.save() + # Ensure a profile gets created - Profile.objects.for_user(user) + profile = Profile.objects.for_user(user) + profile.current_project = project + profile.save() - check = Check(user=user) + check = Check(user=user, project=project) check.name = "My First Check" check.save() - channel = Channel(user=user) + channel = Channel(user=user, project=project) channel.kind = "email" channel.value = email channel.email_verified = True @@ -72,7 +77,10 @@ def _ensure_own_team(request): if request.team != request.profile: request.team = request.profile + request.project = request.user.project_set.first() + request.profile.current_team = request.profile + request.profile.current_project = request.project request.profile.save() @@ -200,6 +208,12 @@ def profile(request): return redirect("hc-link-sent") elif "create_api_keys" in request.POST: profile.set_api_keys() + + for project in request.user.project_set.all(): + project.api_key = profile.api_key + project.api_key_readonly = profile.api_key_readonly + project.save() + ctx["show_api_keys"] = True ctx["api_keys_created"] = True ctx["api_status"] = "success" @@ -208,6 +222,12 @@ def profile(request): profile.api_key = "" profile.api_key_readonly = "" profile.save() + + for project in request.user.project_set.all(): + project.api_key = "" + project.api_key_readonly = "" + project.save() + ctx["api_keys_revoked"] = True ctx["api_status"] = "info" elif "show_api_keys" in request.POST: @@ -236,6 +256,7 @@ def profile(request): email = form.cleaned_data["email"] farewell_user = User.objects.get(email=email) farewell_user.profile.current_team = None + farewell_user.profile.current_project = None farewell_user.profile.save() Member.objects.filter(team=profile, @@ -248,6 +269,11 @@ def profile(request): if form.is_valid(): profile.team_name = form.cleaned_data["team_name"] profile.save() + + for project in request.user.project_set.all(): + project.name = form.cleaned_data["team_name"] + project.save() + ctx["team_name_updated"] = True ctx["team_status"] = "success" @@ -427,6 +453,7 @@ def switch_team(request, target_username): return HttpResponseForbidden() request.profile.current_team = target_team + request.profile.current_project = target_team.user.project_set.first() request.profile.save() return redirect("hc-checks") diff --git a/hc/api/decorators.py b/hc/api/decorators.py index bb412582..a6d0ffae 100644 --- a/hc/api/decorators.py +++ b/hc/api/decorators.py @@ -24,6 +24,7 @@ def authorize(f): try: request.user = User.objects.get(profile__api_key=api_key) + request.project = request.user.project_set.first() except User.DoesNotExist: return error("wrong api key", 401) @@ -46,6 +47,7 @@ def authorize_read(f): read_key_match = Q(profile__api_key_readonly=api_key) try: request.user = User.objects.get(write_key_match | read_key_match) + request.project = request.user.project_set.first() except User.DoesNotExist: return error("wrong api key", 401) diff --git a/hc/api/migrations/0054_auto_20190112_1427.py b/hc/api/migrations/0054_auto_20190112_1427.py new file mode 100644 index 00000000..1fb62976 --- /dev/null +++ b/hc/api/migrations/0054_auto_20190112_1427.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1.5 on 2019-01-12 14:27 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0018_auto_20190112_1426'), + ('api', '0053_check_subject'), + ] + + operations = [ + migrations.AddField( + model_name='channel', + name='project', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.Project'), + ), + migrations.AddField( + model_name='check', + name='project', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='accounts.Project'), + ), + ] diff --git a/hc/api/migrations/0055_auto_20190112_1427.py b/hc/api/migrations/0055_auto_20190112_1427.py new file mode 100644 index 00000000..dcf61470 --- /dev/null +++ b/hc/api/migrations/0055_auto_20190112_1427.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.5 on 2019-01-12 14:27 + +from django.db import migrations + + +def fill_project_id(apps, schema_editor): + Project = apps.get_model("accounts", "Project") + Check = apps.get_model("api", "Check") + Channel = apps.get_model("api", "Channel") + for project in Project.objects.all(): + Check.objects.filter(user_id=project.owner_id).update(project=project) + Channel.objects.filter(user_id=project.owner_id).update(project=project) + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0054_auto_20190112_1427'), + ] + + operations = [ + migrations.RunPython(fill_project_id, migrations.RunPython.noop), + ] diff --git a/hc/api/models.py b/hc/api/models.py index 60739ce2..841639a3 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -12,6 +12,7 @@ from django.contrib.auth.models import User from django.db import models from django.urls import reverse from django.utils import timezone +from hc.accounts.models import Project from hc.api import transports from hc.lib import emails import requests @@ -67,6 +68,7 @@ class Check(models.Model): code = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) desc = models.TextField(blank=True) user = models.ForeignKey(User, models.CASCADE) + project = models.ForeignKey(Project, models.CASCADE, null=True) created = models.DateTimeField(auto_now_add=True) kind = models.CharField(max_length=10, default="simple", choices=CHECK_KINDS) @@ -262,6 +264,7 @@ class Channel(models.Model): name = models.CharField(max_length=100, blank=True) code = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) user = models.ForeignKey(User, models.CASCADE) + project = models.ForeignKey(Project, models.CASCADE, null=True,) created = models.DateTimeField(auto_now_add=True) kind = models.CharField(max_length=20, choices=CHANNEL_KINDS) value = models.TextField(blank=True) diff --git a/hc/api/tests/test_create_check.py b/hc/api/tests/test_create_check.py index 79376b20..6bc36c8c 100644 --- a/hc/api/tests/test_create_check.py +++ b/hc/api/tests/test_create_check.py @@ -47,6 +47,7 @@ class CreateCheckTestCase(BaseTestCase): self.assertEqual(check.tags, "bar,baz") self.assertEqual(check.timeout.total_seconds(), 3600) self.assertEqual(check.grace.total_seconds(), 60) + self.assertEqual(check.project, self.project) def test_it_handles_options(self): r = self.client.options(self.URL) diff --git a/hc/api/views.py b/hc/api/views.py index f5b7c73f..e9a384af 100644 --- a/hc/api/views.py +++ b/hc/api/views.py @@ -132,7 +132,7 @@ def create_check(request): if num_checks >= request.user.profile.check_limit: return HttpResponseForbidden() - check = Check(user=request.user) + check = Check(user=request.user, project=request.project) created = True _update(check, request.json) diff --git a/hc/front/tests/test_add_check.py b/hc/front/tests/test_add_check.py index 5f85d4c1..68cd2224 100644 --- a/hc/front/tests/test_add_check.py +++ b/hc/front/tests/test_add_check.py @@ -9,7 +9,19 @@ class AddCheckTestCase(BaseTestCase): self.client.login(username="alice@example.org", password="password") r = self.client.post(url) self.assertRedirects(r, "/checks/") - assert Check.objects.count() == 1 + check = Check.objects.get() + self.assertEqual(check.project, self.project) + + def test_it_handles_unset_current_project(self): + self.profile.current_project = None + self.profile.save() + + url = "/checks/add/" + self.client.login(username="alice@example.org", password="password") + r = self.client.post(url) + self.assertRedirects(r, "/checks/") + check = Check.objects.get() + self.assertEqual(check.project, self.project) def test_team_access_works(self): url = "/checks/add/" diff --git a/hc/front/tests/test_add_discord.py b/hc/front/tests/test_add_discord.py index dbdbcec7..5d05de20 100644 --- a/hc/front/tests/test_add_discord.py +++ b/hc/front/tests/test_add_discord.py @@ -51,6 +51,7 @@ class AddDiscordTestCase(BaseTestCase): ch = Channel.objects.get() self.assertEqual(ch.discord_webhook_url, "foo") + self.assertEqual(ch.project, self.project) # Session should now be clean self.assertFalse("discord" in self.client.session) diff --git a/hc/front/tests/test_add_email.py b/hc/front/tests/test_add_email.py index 44a98b4b..c2a6d4ee 100644 --- a/hc/front/tests/test_add_email.py +++ b/hc/front/tests/test_add_email.py @@ -21,6 +21,7 @@ class AddPdTestCase(BaseTestCase): self.assertEqual(c.kind, "email") self.assertEqual(c.value, "alice@example.org") self.assertFalse(c.email_verified) + self.assertEqual(c.project, self.project) def test_team_access_works(self): form = {"value": "bob@example.org"} diff --git a/hc/front/tests/test_add_hipchat.py b/hc/front/tests/test_add_hipchat.py index 758d6d8f..2703d5b8 100644 --- a/hc/front/tests/test_add_hipchat.py +++ b/hc/front/tests/test_add_hipchat.py @@ -34,3 +34,4 @@ class AddHipChatTestCase(BaseTestCase): c = Channel.objects.get() self.assertEqual(c.kind, "hipchat") self.assertEqual(c.value, "{}") + self.assertEqual(c.project, self.project) diff --git a/hc/front/tests/test_add_opsgenie.py b/hc/front/tests/test_add_opsgenie.py index 859281a9..a13d494d 100644 --- a/hc/front/tests/test_add_opsgenie.py +++ b/hc/front/tests/test_add_opsgenie.py @@ -20,6 +20,7 @@ class AddOpsGenieTestCase(BaseTestCase): c = Channel.objects.get() self.assertEqual(c.kind, "opsgenie") self.assertEqual(c.value, "123456") + self.assertEqual(c.project, self.project) def test_it_trims_whitespace(self): form = {"value": " 123456 "} diff --git a/hc/front/tests/test_add_pagertree.py b/hc/front/tests/test_add_pagertree.py index 6f06fed1..4ebe6e8a 100644 --- a/hc/front/tests/test_add_pagertree.py +++ b/hc/front/tests/test_add_pagertree.py @@ -20,6 +20,7 @@ class AddPagerTreeTestCase(BaseTestCase): c = Channel.objects.get() self.assertEqual(c.kind, "pagertree") self.assertEqual(c.value, "http://example.org") + self.assertEqual(c.project, self.project) def test_it_rejects_bad_url(self): form = {"value": "not an URL"} diff --git a/hc/front/tests/test_add_pd.py b/hc/front/tests/test_add_pd.py index a42b7918..0f6c863a 100644 --- a/hc/front/tests/test_add_pd.py +++ b/hc/front/tests/test_add_pd.py @@ -25,6 +25,7 @@ class AddPdTestCase(BaseTestCase): c = Channel.objects.get() self.assertEqual(c.kind, "pd") self.assertEqual(c.pd_service_key, "123") + self.assertEqual(c.project, self.project) def test_it_validates_code(self): session = self.client.session diff --git a/hc/front/tests/test_add_pushbullet.py b/hc/front/tests/test_add_pushbullet.py index 6356a046..68cebdb9 100644 --- a/hc/front/tests/test_add_pushbullet.py +++ b/hc/front/tests/test_add_pushbullet.py @@ -45,6 +45,7 @@ class AddPushbulletTestCase(BaseTestCase): ch = Channel.objects.get() self.assertEqual(ch.value, "test-token") + self.assertEqual(ch.project, self.project) # Session should now be clean self.assertFalse("pushbullet" in self.client.session) diff --git a/hc/front/tests/test_add_pushover.py b/hc/front/tests/test_add_pushover.py index 17ce471f..1206ed40 100644 --- a/hc/front/tests/test_add_pushover.py +++ b/hc/front/tests/test_add_pushover.py @@ -43,9 +43,9 @@ class AddPushoverTestCase(BaseTestCase): r = self.client.get("/integrations/add_pushover/?%s" % params) self.assertEqual(r.status_code, 302) - channels = list(Channel.objects.all()) - assert len(channels) == 1 - assert channels[0].value == "a|0|-1" + channel = Channel.objects.get() + self.assertEqual(channel.value, "a|0|-1") + self.assertEqual(channel.project, self.project) def test_it_validates_priority(self): self.client.login(username="alice@example.org", password="password") diff --git a/hc/front/tests/test_add_slack.py b/hc/front/tests/test_add_slack.py index ff6f4822..18b3ec6b 100644 --- a/hc/front/tests/test_add_slack.py +++ b/hc/front/tests/test_add_slack.py @@ -22,6 +22,7 @@ class AddSlackTestCase(BaseTestCase): c = Channel.objects.get() self.assertEqual(c.kind, "slack") self.assertEqual(c.value, "http://example.org") + self.assertEqual(c.project, self.project) @override_settings(SLACK_CLIENT_ID=None) def test_it_rejects_bad_url(self): diff --git a/hc/front/tests/test_add_slack_btn.py b/hc/front/tests/test_add_slack_btn.py index 1d508a6a..d78e95bd 100644 --- a/hc/front/tests/test_add_slack_btn.py +++ b/hc/front/tests/test_add_slack_btn.py @@ -54,6 +54,7 @@ class AddSlackBtnTestCase(BaseTestCase): self.assertEqual(ch.slack_team, "foo") self.assertEqual(ch.slack_channel, "bar") self.assertEqual(ch.slack_webhook_url, "http://example.org") + self.assertEqual(ch.project, self.project) # Session should now be clean self.assertFalse("slack" in self.client.session) diff --git a/hc/front/tests/test_add_sms.py b/hc/front/tests/test_add_sms.py index 0543cd9b..744b84fb 100644 --- a/hc/front/tests/test_add_sms.py +++ b/hc/front/tests/test_add_sms.py @@ -32,6 +32,7 @@ class AddSmsTestCase(BaseTestCase): self.assertEqual(c.kind, "sms") self.assertEqual(c.sms_number, "+1234567890") self.assertEqual(c.name, "My Phone") + self.assertEqual(c.project, self.project) def test_it_rejects_bad_number(self): form = {"value": "not a phone number address"} diff --git a/hc/front/tests/test_add_telegram.py b/hc/front/tests/test_add_telegram.py index aa1a02b7..f4a7a43d 100644 --- a/hc/front/tests/test_add_telegram.py +++ b/hc/front/tests/test_add_telegram.py @@ -1,5 +1,3 @@ -import json - from django.core import signing from hc.api.models import Channel from hc.test import BaseTestCase @@ -33,6 +31,7 @@ class AddTelegramTestCase(BaseTestCase): self.assertEqual(c.telegram_id, 123) self.assertEqual(c.telegram_type, "group") self.assertEqual(c.telegram_name, "My Group") + self.assertEqual(c.project, self.project) @patch("hc.api.transports.requests.request") def test_it_sends_invite(self, mock_get): diff --git a/hc/front/tests/test_add_victorops.py b/hc/front/tests/test_add_victorops.py index 8dae20f8..fc913680 100644 --- a/hc/front/tests/test_add_victorops.py +++ b/hc/front/tests/test_add_victorops.py @@ -20,6 +20,7 @@ class AddVictorOpsTestCase(BaseTestCase): c = Channel.objects.get() self.assertEqual(c.kind, "victorops") self.assertEqual(c.value, "http://example.org") + self.assertEqual(c.project, self.project) def test_it_rejects_bad_url(self): form = {"value": "not an URL"} diff --git a/hc/front/tests/test_add_webhook.py b/hc/front/tests/test_add_webhook.py index 763cf642..ab5cfd6a 100644 --- a/hc/front/tests/test_add_webhook.py +++ b/hc/front/tests/test_add_webhook.py @@ -19,6 +19,7 @@ class AddWebhookTestCase(BaseTestCase): c = Channel.objects.get() self.assertEqual(c.value, '{"headers": {}, "post_data": "", "url_down": "http://foo.com", "url_up": "https://bar.com"}') + self.assertEqual(c.project, self.project) def test_it_adds_webhook_using_team_access(self): form = {"url_down": "http://foo.com", "url_up": "https://bar.com"} diff --git a/hc/front/views.py b/hc/front/views.py index 51a70fa7..f39864ab 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -249,7 +249,7 @@ def add_check(request): if num_checks >= request.team.check_limit: return HttpResponseBadRequest() - check = Check(user=request.team.user) + check = Check(user=request.team.user, project=request.project) check.save() check.assign_all_channels() @@ -589,6 +589,7 @@ def add_email(request): form = AddEmailForm(request.POST) if form.is_valid(): channel = Channel(user=request.team.user, kind="email") + channel.project = request.project channel.value = form.cleaned_data["value"] channel.save() @@ -608,6 +609,7 @@ def add_webhook(request): form = AddWebhookForm(request.POST) if form.is_valid(): channel = Channel(user=request.team.user, kind="webhook") + channel.project = request.project channel.value = form.get_value() channel.save() @@ -658,9 +660,8 @@ def add_pd(request, state=None): messages.warning(request, "PagerDuty setup was cancelled") return redirect("hc-channels") - channel = Channel() + channel = Channel(kind="pd", project=request.project) channel.user = request.team.user - channel.kind = "pd" channel.value = json.dumps({ "service_key": request.GET.get("service_key"), "account": request.GET.get("account") @@ -687,6 +688,7 @@ def add_pagertree(request): form = AddUrlForm(request.POST) if form.is_valid(): channel = Channel(user=request.team.user, kind="pagertree") + channel.project = request.project channel.value = form.cleaned_data["value"] channel.save() @@ -707,6 +709,7 @@ def add_slack(request): form = AddUrlForm(request.POST) if form.is_valid(): channel = Channel(user=request.team.user, kind="slack") + channel.project = request.project channel.value = form.cleaned_data["value"] channel.save() @@ -741,9 +744,8 @@ def add_slack_btn(request): doc = result.json() if doc.get("ok"): - channel = Channel() + channel = Channel(kind="slack", project=request.project) channel.user = request.team.user - channel.kind = "slack" channel.value = result.text channel.save() channel.assign_all_checks() @@ -765,7 +767,7 @@ def add_hipchat(request): messages.warning(request, "Something went wrong!") return redirect("hc-channels") - channel = Channel(kind="hipchat") + channel = Channel(kind="hipchat", project=request.project) channel.user = request.team.user channel.value = response.text channel.save() @@ -810,7 +812,7 @@ def add_pushbullet(request): doc = result.json() if "access_token" in doc: - channel = Channel(kind="pushbullet") + channel = Channel(kind="pushbullet", project=request.project) channel.user = request.team.user channel.value = doc["access_token"] channel.save() @@ -858,7 +860,7 @@ def add_discord(request): doc = result.json() if "access_token" in doc: - channel = Channel(kind="discord") + channel = Channel(kind="discord", project=request.project) channel.user = request.team.user channel.value = result.text channel.save() @@ -933,6 +935,7 @@ def add_pushover(request): # Subscription channel = Channel(user=request.team.user, kind="po") + channel.project = request.project channel.value = "%s|%s|%s" % (key, prio, prio_up) channel.save() channel.assign_all_checks() @@ -955,6 +958,7 @@ def add_opsgenie(request): form = AddOpsGenieForm(request.POST) if form.is_valid(): channel = Channel(user=request.team.user, kind="opsgenie") + channel.project = request.project channel.value = form.cleaned_data["value"] channel.save() @@ -973,6 +977,7 @@ def add_victorops(request): form = AddUrlForm(request.POST) if form.is_valid(): channel = Channel(user=request.team.user, kind="victorops") + channel.project = request.project channel.value = form.cleaned_data["value"] channel.save() @@ -1021,6 +1026,7 @@ def add_telegram(request): if request.method == "POST": channel = Channel(user=request.team.user, kind="telegram") + channel.project = request.project channel.value = json.dumps({ "id": chat_id, "type": chat_type, @@ -1051,6 +1057,7 @@ def add_sms(request): form = AddSmsForm(request.POST) if form.is_valid(): channel = Channel(user=request.team.user, kind="sms") + channel.project = request.project channel.name = form.cleaned_data["label"] channel.value = json.dumps({ "value": form.cleaned_data["value"] diff --git a/hc/test.py b/hc/test.py index 0bad5ee0..f0e45c2c 100644 --- a/hc/test.py +++ b/hc/test.py @@ -1,7 +1,7 @@ from django.contrib.auth.models import User from django.test import TestCase -from hc.accounts.models import Member, Profile +from hc.accounts.models import Member, Profile, Project class BaseTestCase(TestCase): @@ -14,8 +14,12 @@ class BaseTestCase(TestCase): self.alice.set_password("password") self.alice.save() + self.project = Project(owner=self.alice, api_key="X" * 32) + self.project.save() + self.profile = Profile(user=self.alice, api_key="X" * 32) self.profile.sms_limit = 50 + self.profile.current_project = self.project self.profile.save() # Bob is on Alice's team and should have access to her stuff @@ -25,9 +29,11 @@ class BaseTestCase(TestCase): self.bobs_profile = Profile(user=self.bob) self.bobs_profile.current_team = self.profile + self.bobs_profile.current_project = self.project self.bobs_profile.save() - Member.objects.create(team=self.profile, user=self.bob) + Member.objects.create(team=self.profile, user=self.bob, + project=self.project) # Charlie should have no access to Alice's stuff self.charlie = User(username="charlie", email="charlie@example.org")