forked from GithubBackups/healthchecks
Specify the read-write/read-only flag when inviting a team member.
This commit is contained in:
parent
adb004b333
commit
d73de68f70
@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Less verbose output in the `senddeletionnotices` command
|
- Less verbose output in the `senddeletionnotices` command
|
||||||
- Host a read-only dashboard (from github.com/healthchecks/dashboard/)
|
- Host a read-only dashboard (from github.com/healthchecks/dashboard/)
|
||||||
- LINE Notify integration (#412)
|
- LINE Notify integration (#412)
|
||||||
|
- Read-only team members
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
- Handle excessively long email addresses in the signup form
|
- Handle excessively long email addresses in the signup form
|
||||||
|
@ -99,6 +99,7 @@ class ChangeEmailForm(forms.Form):
|
|||||||
|
|
||||||
class InviteTeamMemberForm(forms.Form):
|
class InviteTeamMemberForm(forms.Form):
|
||||||
email = LowercaseEmailField(max_length=254)
|
email = LowercaseEmailField(max_length=254)
|
||||||
|
rw = forms.BooleanField(required=False)
|
||||||
|
|
||||||
|
|
||||||
class RemoveTeamMemberForm(forms.Form):
|
class RemoveTeamMemberForm(forms.Form):
|
||||||
|
@ -318,14 +318,14 @@ class Project(models.Model):
|
|||||||
used = q.distinct().count()
|
used = q.distinct().count()
|
||||||
return used < self.owner_profile.team_limit
|
return used < self.owner_profile.team_limit
|
||||||
|
|
||||||
def invite(self, user):
|
def invite(self, user, rw):
|
||||||
if Member.objects.filter(user=user, project=self).exists():
|
if Member.objects.filter(user=user, project=self).exists():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.owner_id == user.id:
|
if self.owner_id == user.id:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
Member.objects.create(user=user, project=self)
|
Member.objects.create(user=user, project=self, rw=rw)
|
||||||
checks_url = reverse("hc-checks", args=[self.code])
|
checks_url = reverse("hc-checks", args=[self.code])
|
||||||
user.profile.send_instant_login_link(self, redirect_url=checks_url)
|
user.profile.send_instant_login_link(self, redirect_url=checks_url)
|
||||||
return True
|
return True
|
||||||
|
@ -65,7 +65,7 @@ class ProjectTestCase(BaseTestCase):
|
|||||||
def test_it_adds_team_member(self):
|
def test_it_adds_team_member(self):
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
form = {"invite_team_member": "1", "email": "frank@example.org"}
|
form = {"invite_team_member": "1", "email": "frank@example.org", "rw": "1"}
|
||||||
r = self.client.post(self.url, form)
|
r = self.client.post(self.url, form)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
@ -76,6 +76,9 @@ class ProjectTestCase(BaseTestCase):
|
|||||||
project=self.project, user__email="frank@example.org"
|
project=self.project, user__email="frank@example.org"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# The read-write flag should be set
|
||||||
|
self.assertTrue(member.rw)
|
||||||
|
|
||||||
# The new user should not have their own project
|
# The new user should not have their own project
|
||||||
self.assertFalse(member.user.project_set.exists())
|
self.assertFalse(member.user.project_set.exists())
|
||||||
|
|
||||||
@ -86,6 +89,20 @@ class ProjectTestCase(BaseTestCase):
|
|||||||
)
|
)
|
||||||
self.assertHTMLEqual(mail.outbox[0].subject, subj)
|
self.assertHTMLEqual(mail.outbox[0].subject, subj)
|
||||||
|
|
||||||
|
def test_it_adds_readonly_team_member(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
|
form = {"invite_team_member": "1", "email": "frank@example.org"}
|
||||||
|
r = self.client.post(self.url, form)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
member = Member.objects.get(
|
||||||
|
project=self.project, user__email="frank@example.org"
|
||||||
|
)
|
||||||
|
|
||||||
|
# The new user should not have their own project
|
||||||
|
self.assertFalse(member.rw)
|
||||||
|
|
||||||
def test_it_adds_member_from_another_team(self):
|
def test_it_adds_member_from_another_team(self):
|
||||||
# With team limit at zero, we should not be able to invite any new users
|
# With team limit at zero, we should not be able to invite any new users
|
||||||
self.profile.team_limit = 0
|
self.profile.team_limit = 0
|
||||||
|
@ -306,7 +306,7 @@ def project(request, code):
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
user = _make_user(email, with_project=False)
|
user = _make_user(email, with_project=False)
|
||||||
|
|
||||||
if project.invite(user):
|
if project.invite(user, rw=form.cleaned_data["rw"]):
|
||||||
ctx["team_member_invited"] = email
|
ctx["team_member_invited"] = email
|
||||||
ctx["team_status"] = "success"
|
ctx["team_status"] = "success"
|
||||||
else:
|
else:
|
||||||
|
@ -162,15 +162,21 @@
|
|||||||
<td>Owner</td>
|
<td>Owner</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% for user in project.team %}
|
{% for m in project.member_set.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="email">{{ user.email }}</td>
|
<td class="email">{{ m.user.email }}</td>
|
||||||
<td>Member</td>
|
<td>
|
||||||
|
{% if m.rw %}
|
||||||
|
Member
|
||||||
|
{% else %}
|
||||||
|
Read-only
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if is_owner %}
|
{% if is_owner %}
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-email="{{ user.email }}"
|
data-email="{{ m.user.email }}"
|
||||||
class="pull-right member-remove">Remove</a>
|
class="pull-right member-remove">Remove</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
@ -378,9 +384,10 @@
|
|||||||
<li>Team Members can create and manage Checks and Integrations</li>
|
<li>Team Members can create and manage Checks and Integrations</li>
|
||||||
<li>Only the project owner (you) can view and edit billing settings</li>
|
<li>Only the project owner (you) can view and edit billing settings</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<br />
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="itm-email" class="col-sm-2 control-label">Email</label>
|
<label for="itm-email" class="col-sm-3 control-label">Email</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-8">
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@ -390,6 +397,37 @@
|
|||||||
placeholder="friend@example.org">
|
placeholder="friend@example.org">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-3 control-label">Access Level</label>
|
||||||
|
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<label class="radio-container">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="rw"
|
||||||
|
value="1"
|
||||||
|
checked>
|
||||||
|
<span class="radiomark"></span>
|
||||||
|
Team Member
|
||||||
|
</label>
|
||||||
|
<label class="radio-container">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="rw"
|
||||||
|
value="">
|
||||||
|
<span class="radiomark"></span>
|
||||||
|
Read-only
|
||||||
|
<span class="help-block">
|
||||||
|
Cannot modify checks or integrations.
|
||||||
|
Cannot access project's API keys.
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user