@ -0,0 +1,26 @@ | |||
# -*- coding: utf-8 -*- | |||
# Generated by Django 1.11.6 on 2017-10-14 10:02 | |||
from __future__ import unicode_literals | |||
import datetime | |||
from django.db import migrations, models | |||
class Migration(migrations.Migration): | |||
dependencies = [ | |||
('accounts', '0011_profile_sort'), | |||
] | |||
operations = [ | |||
migrations.AddField( | |||
model_name='profile', | |||
name='nag_period', | |||
field=models.DurationField(choices=[(datetime.timedelta(0), 'Disabled'), (datetime.timedelta(0, 3600), 'Hourly'), (datetime.timedelta(1), 'Daily')], default=datetime.timedelta(0)), | |||
), | |||
migrations.AddField( | |||
model_name='profile', | |||
name='next_nag_date', | |||
field=models.DateTimeField(blank=True, null=True), | |||
), | |||
] |
@ -1,24 +1,60 @@ | |||
from datetime import timedelta as td | |||
from django.utils.timezone import now | |||
from hc.test import BaseTestCase | |||
class NotificationsTestCase(BaseTestCase): | |||
def test_it_saves_reports_allowed_true(self): | |||
self.profile.reports_allowed = False | |||
self.profile.save() | |||
self.client.login(username="[email protected]", password="password") | |||
form = {"reports_allowed": "on"} | |||
form = {"reports_allowed": "on", "nag_period": "0"} | |||
r = self.client.post("/accounts/profile/notifications/", form) | |||
assert r.status_code == 200 | |||
self.assertEqual(r.status_code, 200) | |||
self.alice.profile.refresh_from_db() | |||
self.assertTrue(self.alice.profile.reports_allowed) | |||
self.profile.refresh_from_db() | |||
self.assertTrue(self.profile.reports_allowed) | |||
self.assertIsNotNone(self.profile.next_report_date) | |||
def test_it_saves_reports_allowed_false(self): | |||
self.profile.reports_allowed = True | |||
self.profile.next_report_date = now() | |||
self.profile.save() | |||
self.client.login(username="[email protected]", password="password") | |||
form = {"nag_period": "0"} | |||
r = self.client.post("/accounts/profile/notifications/", form) | |||
self.assertEqual(r.status_code, 200) | |||
self.profile.refresh_from_db() | |||
self.assertFalse(self.profile.reports_allowed) | |||
self.assertIsNone(self.profile.next_report_date) | |||
def test_it_saves_hourly_nag_period(self): | |||
self.client.login(username="[email protected]", password="password") | |||
form = {"nag_period": "3600"} | |||
r = self.client.post("/accounts/profile/notifications/", form) | |||
self.assertEqual(r.status_code, 200) | |||
self.profile.refresh_from_db() | |||
self.assertEqual(self.profile.nag_period.total_seconds(), 3600) | |||
self.assertIsNotNone(self.profile.next_nag_date) | |||
def test_it_does_not_save_nonstandard_nag_period(self): | |||
self.profile.nag_period = td(seconds=3600) | |||
self.profile.save() | |||
self.client.login(username="[email protected]", password="password") | |||
form = {} | |||
form = {"nag_period": "1234"} | |||
r = self.client.post("/accounts/profile/notifications/", form) | |||
assert r.status_code == 200 | |||
self.assertEqual(r.status_code, 200) | |||
self.alice.profile.refresh_from_db() | |||
self.assertFalse(self.alice.profile.reports_allowed) | |||
self.profile.refresh_from_db() | |||
self.assertEqual(self.profile.nag_period.total_seconds(), 3600) |
@ -1,9 +1,11 @@ | |||
from datetime import timedelta as td | |||
from django.core import mail | |||
from django.conf import settings | |||
from django.utils.timezone import now | |||
from hc.test import BaseTestCase | |||
from hc.accounts.models import Member | |||
from hc.api.models import Check | |||
from django.conf import settings | |||
class ProfileTestCase(BaseTestCase): | |||
@ -16,8 +18,8 @@ class ProfileTestCase(BaseTestCase): | |||
assert r.status_code == 302 | |||
# profile.token should be set now | |||
self.alice.profile.refresh_from_db() | |||
token = self.alice.profile.token | |||
self.profile.refresh_from_db() | |||
token = self.profile.token | |||
self.assertTrue(len(token) > 10) | |||
# And an email should have been sent | |||
@ -32,8 +34,8 @@ class ProfileTestCase(BaseTestCase): | |||
r = self.client.post("/accounts/profile/", form) | |||
self.assertEqual(r.status_code, 200) | |||
self.alice.profile.refresh_from_db() | |||
api_key = self.alice.profile.api_key | |||
self.profile.refresh_from_db() | |||
api_key = self.profile.api_key | |||
self.assertTrue(len(api_key) > 10) | |||
def test_it_revokes_api_key(self): | |||
@ -43,14 +45,16 @@ class ProfileTestCase(BaseTestCase): | |||
r = self.client.post("/accounts/profile/", form) | |||
assert r.status_code == 200 | |||
self.alice.profile.refresh_from_db() | |||
self.assertEqual(self.alice.profile.api_key, "") | |||
self.profile.refresh_from_db() | |||
self.assertEqual(self.profile.api_key, "") | |||
def test_it_sends_report(self): | |||
check = Check(name="Test Check", user=self.alice) | |||
check.last_ping = now() | |||
check.save() | |||
self.alice.profile.send_report() | |||
sent = self.profile.send_report() | |||
self.assertTrue(sent) | |||
# And an email should have been sent | |||
self.assertEqual(len(mail.outbox), 1) | |||
@ -59,6 +63,38 @@ class ProfileTestCase(BaseTestCase): | |||
self.assertEqual(message.subject, 'Monthly Report') | |||
self.assertIn("Test Check", message.body) | |||
def test_it_sends_nag(self): | |||
check = Check(name="Test Check", user=self.alice) | |||
check.status = "down" | |||
check.last_ping = now() | |||
check.save() | |||
self.profile.nag_period = td(hours=1) | |||
self.profile.save() | |||
sent = self.profile.send_report(nag=True) | |||
self.assertTrue(sent) | |||
# And an email should have been sent | |||
self.assertEqual(len(mail.outbox), 1) | |||
message = mail.outbox[0] | |||
self.assertEqual(message.subject, 'Reminder: 1 check still down') | |||
self.assertIn("Test Check", message.body) | |||
def test_it_skips_nag_if_none_down(self): | |||
check = Check(name="Test Check", user=self.alice) | |||
check.last_ping = now() | |||
check.save() | |||
self.profile.nag_period = td(hours=1) | |||
self.profile.save() | |||
sent = self.profile.send_report(nag=True) | |||
self.assertFalse(sent) | |||
self.assertEqual(len(mail.outbox), 0) | |||
def test_it_adds_team_member(self): | |||
self.client.login(username="[email protected]", password="password") | |||
@ -67,7 +103,7 @@ class ProfileTestCase(BaseTestCase): | |||
self.assertEqual(r.status_code, 200) | |||
member_emails = set() | |||
for member in self.alice.profile.member_set.all(): | |||
for member in self.profile.member_set.all(): | |||
member_emails.add(member.user.email) | |||
self.assertEqual(len(member_emails), 2) | |||
@ -107,8 +143,8 @@ class ProfileTestCase(BaseTestCase): | |||
r = self.client.post("/accounts/profile/", form) | |||
self.assertEqual(r.status_code, 200) | |||
self.alice.profile.refresh_from_db() | |||
self.assertEqual(self.alice.profile.team_name, "Alpha Team") | |||
self.profile.refresh_from_db() | |||
self.assertEqual(self.profile.team_name, "Alpha Team") | |||
def test_it_switches_to_own_team(self): | |||
self.client.login(username="[email protected]", password="password") | |||
@ -128,8 +164,8 @@ class ProfileTestCase(BaseTestCase): | |||
assert r.status_code == 302 | |||
# profile.token should be set now | |||
self.alice.profile.refresh_from_db() | |||
token = self.alice.profile.token | |||
self.profile.refresh_from_db() | |||
token = self.profile.token | |||
self.assertTrue(len(token) > 10) | |||
# And an email should have been sent | |||
@ -0,0 +1,67 @@ | |||
/* Customize the label (the container) */ | |||
.checkbox-container { | |||
display: block; | |||
position: relative; | |||
padding-left: 30px; | |||
margin-left: 20px; | |||
margin-bottom: 12px; | |||
cursor: pointer; | |||
-webkit-user-select: none; | |||
-moz-user-select: none; | |||
-ms-user-select: none; | |||
user-select: none; | |||
font-weight: normal; | |||
} | |||
/* Hide the browser's default checkbox */ | |||
.checkbox-container input { | |||
position: absolute; | |||
opacity: 0; | |||
} | |||
/* Create a custom checkbox */ | |||
.checkmark { | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
height: 20px; | |||
width: 20px; | |||
border-radius: 3px; | |||
border: 1px solid #DDD; | |||
} | |||
/* On mouse-over tint the border */ | |||
.checkmark:hover { | |||
border-color: #5db4ea; | |||
} | |||
/* When the checkbox is checked, add a colored background */ | |||
.checkbox-container input:checked ~ .checkmark { | |||
border-color: #0091EA; | |||
background-color: #0091EA; | |||
} | |||
/* Create the checkmark/indicator (hidden when not checked) */ | |||
.checkmark:after { | |||
content: ""; | |||
position: absolute; | |||
display: none; | |||
} | |||
/* Show the checkmark when checked */ | |||
.checkbox-container input:checked ~ .checkmark:after { | |||
display: block; | |||
} | |||
/* Style the checkmark/indicator */ | |||
.checkbox-container .checkmark:after { | |||
left: 7px; | |||
top: 3px; | |||
width: 5px; | |||
height: 10px; | |||
border: solid white; | |||
border-width: 0 2px 2px 0; | |||
-webkit-transform: rotate(45deg); | |||
-ms-transform: rotate(45deg); | |||
transform: rotate(45deg); | |||
} |
@ -0,0 +1,64 @@ | |||
/* Customize the label (the container) */ | |||
.radio-container { | |||
display: block; | |||
position: relative; | |||
padding-left: 30px; | |||
margin-bottom: 12px; | |||
margin-left: 20px; | |||
cursor: pointer; | |||
-webkit-user-select: none; | |||
-moz-user-select: none; | |||
-ms-user-select: none; | |||
user-select: none; | |||
font-weight: normal; | |||
} | |||
/* Hide the browser's default radio button */ | |||
.radio-container input { | |||
position: absolute; | |||
opacity: 0; | |||
} | |||
/* Create a custom radio button */ | |||
.radiomark { | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
height: 20px; | |||
width: 20px; | |||
border-radius: 50%; | |||
border: 1px solid #DDD; | |||
} | |||
/* On mouse-over, tint the border */ | |||
.radiomark:hover { | |||
border-color: #5db4ea; | |||
} | |||
/* When the radio button is checked, add a colored background */ | |||
.radio-container input:checked ~ .radiomark { | |||
border-color: #0091EA; | |||
background-color: #0091EA; | |||
} | |||
/* Create the indicator (the dot/circle - hidden when not checked) */ | |||
.radiomark:after { | |||
content: ""; | |||
position: absolute; | |||
display: none; | |||
} | |||
/* Show the indicator (dot/circle) when checked */ | |||
.radio-container input:checked ~ .radiomark:after { | |||
display: block; | |||
} | |||
/* Style the indicator (dot/circle) */ | |||
.radio-container .radiomark:after { | |||
top: 6px; | |||
left: 6px; | |||
width: 6px; | |||
height: 6px; | |||
border-radius: 50%; | |||
background: white; | |||
} |
@ -1,2 +1,6 @@ | |||
Monthly Report | |||
{% if nag %} | |||
Reminder: {{ num_down }} check{{ num_down|pluralize }} still down | |||
{% else %} | |||
Monthly Report | |||
{% endif %} | |||