Browse Source

Specify the read-write/read-only flag when inviting a team member.

pull/419/head
Pēteris Caune 4 years ago
parent
commit
d73de68f70
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
6 changed files with 67 additions and 10 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +1
    -0
      hc/accounts/forms.py
  3. +2
    -2
      hc/accounts/models.py
  4. +18
    -1
      hc/accounts/tests/test_project.py
  5. +1
    -1
      hc/accounts/views.py
  6. +44
    -6
      templates/accounts/project.html

+ 1
- 0
CHANGELOG.md View File

@ -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


+ 1
- 0
hc/accounts/forms.py View File

@ -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):


+ 2
- 2
hc/accounts/models.py View File

@ -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


+ 18
- 1
hc/accounts/tests/test_project.py View File

@ -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="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
form = {"invite_team_member": "1", "email": "[email protected]"}
form = {"invite_team_member": "1", "email": "[email protected]", "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="[email protected]" project=self.project, user__email="[email protected]"
) )
# 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="[email protected]", password="password")
form = {"invite_team_member": "1", "email": "[email protected]"}
r = self.client.post(self.url, form)
self.assertEqual(r.status_code, 200)
member = Member.objects.get(
project=self.project, user__email="[email protected]"
)
# 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


+ 1
- 1
hc/accounts/views.py View File

@ -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:


+ 44
- 6
templates/accounts/project.html View File

@ -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>Member</td>
<td class="email">{{ m.user.email }}</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>
<div class="col-sm-9">
<label for="itm-email" class="col-sm-3 control-label">Email</label>
<div class="col-sm-8">
<input <input
type="email" type="email"
class="form-control" class="form-control"
@ -390,6 +397,37 @@
placeholder="[email protected]"> placeholder="[email protected]">
</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…
Cancel
Save