Browse Source

feat: add manager role

pull/551/head
swoga 3 years ago
committed by Pēteris Caune
parent
commit
9640d2242f
7 changed files with 87 additions and 25 deletions
  1. +2
    -2
      hc/accounts/forms.py
  2. +17
    -0
      hc/accounts/migrations/0043_add_role_manager.py
  3. +3
    -3
      hc/accounts/models.py
  4. +36
    -9
      hc/accounts/tests/test_project.py
  5. +10
    -3
      hc/accounts/views.py
  6. +1
    -1
      hc/test.py
  7. +18
    -7
      templates/accounts/project.html

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

@ -6,7 +6,7 @@ from django import forms
from django.core.exceptions import ValidationError
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from hc.accounts.models import REPORT_CHOICES
from hc.accounts.models import REPORT_CHOICES, Member
from hc.api.models import TokenBucket
import pytz
@ -136,7 +136,7 @@ class ChangeEmailForm(forms.Form):
class InviteTeamMemberForm(forms.Form):
email = LowercaseEmailField(max_length=254)
rw = forms.BooleanField(required=False)
role = forms.ChoiceField(choices=Member.Role.choices)
class RemoveTeamMemberForm(forms.Form):


+ 17
- 0
hc/accounts/migrations/0043_add_role_manager.py View File

@ -0,0 +1,17 @@
from django.db import migrations, models
from hc.accounts.models import Member
class Migration(migrations.Migration):
dependencies = [
('accounts', '0042_remove_member_rw'),
]
operations = [
migrations.AlterField(
model_name='member',
name='role',
field=models.CharField(choices=Member.Role.choices, default=Member.Role.REGULAR, max_length=1),
),
]

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

@ -351,14 +351,13 @@ class Project(models.Model):
used = q.distinct().count()
return used < self.owner_profile.team_limit
def invite(self, user, rw):
def invite(self, user, role):
if Member.objects.filter(user=user, project=self).exists():
return False
if self.owner_id == user.id:
return False
role = Member.Role.REGULAR if rw else Member.Role.READONLY
Member.objects.create(user=user, project=self, role=role)
checks_url = reverse("hc-checks", args=[self.code])
user.profile.send_instant_login_link(self, redirect_url=checks_url)
@ -423,6 +422,7 @@ class Member(models.Model):
class Role(models.TextChoices):
READONLY = "r", "Read-only"
REGULAR = "w", "Member"
MANAGER = "m", "Manager"
user = models.ForeignKey(User, models.CASCADE, related_name="memberships")
project = models.ForeignKey(Project, models.CASCADE)
@ -441,7 +441,7 @@ class Member(models.Model):
@property
def is_rw(self):
return self.role in (Member.Role.REGULAR,)
return self.role in (Member.Role.REGULAR, Member.Role.MANAGER)
class Credential(models.Model):


+ 36
- 9
hc/accounts/tests/test_project.py View File

@ -66,7 +66,7 @@ class ProjectTestCase(BaseTestCase):
def test_it_adds_team_member(self):
self.client.login(username="[email protected]", password="password")
form = {"invite_team_member": "1", "email": "[email protected]", "rw": "1"}
form = {"invite_team_member": "1", "email": "[email protected]", "role": "w"}
r = self.client.post(self.url, form)
self.assertEqual(r.status_code, 200)
@ -90,7 +90,7 @@ class ProjectTestCase(BaseTestCase):
def test_it_adds_readonly_team_member(self):
self.client.login(username="[email protected]", password="password")
form = {"invite_team_member": "1", "email": "[email protected]"}
form = {"invite_team_member": "1", "email": "[email protected]", "role": "r"}
r = self.client.post(self.url, form)
self.assertEqual(r.status_code, 200)
@ -100,6 +100,20 @@ class ProjectTestCase(BaseTestCase):
self.assertEqual(member.role, member.Role.READONLY)
def test_it_adds_manager_team_member(self):
self.client.login(username="[email protected]", password="password")
form = {"invite_team_member": "1", "email": "[email protected]", "role": "m"}
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 have role manager
self.assertEqual(member.role, member.Role.MANAGER)
def test_it_adds_member_from_another_team(self):
# With team limit at zero, we should not be able to invite any new users
self.profile.team_limit = 0
@ -111,7 +125,7 @@ class ProjectTestCase(BaseTestCase):
Member.objects.create(user=self.charlie, project=p2)
self.client.login(username="[email protected]", password="password")
form = {"invite_team_member": "1", "email": "[email protected]"}
form = {"invite_team_member": "1", "email": "[email protected]", "role": "r"}
r = self.client.post(self.url, form)
self.assertEqual(r.status_code, 200)
@ -125,7 +139,7 @@ class ProjectTestCase(BaseTestCase):
def test_it_rejects_duplicate_membership(self):
self.client.login(username="[email protected]", password="password")
form = {"invite_team_member": "1", "email": "[email protected]"}
form = {"invite_team_member": "1", "email": "[email protected]", "role": "r"}
r = self.client.post(self.url, form)
self.assertContains(r, "[email protected] is already a member")
@ -135,7 +149,7 @@ class ProjectTestCase(BaseTestCase):
def test_it_rejects_owner_as_a_member(self):
self.client.login(username="[email protected]", password="password")
form = {"invite_team_member": "1", "email": "[email protected]"}
form = {"invite_team_member": "1", "email": "[email protected]", "role": "r"}
r = self.client.post(self.url, form)
self.assertContains(r, "[email protected] is already a member")
@ -146,7 +160,7 @@ class ProjectTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password")
aaa = "a" * 300
form = {"invite_team_member": "1", "email": f"frank+{aaa}@example.org"}
form = {"invite_team_member": "1", "email": f"frank+{aaa}@example.org", "role": "r"}
r = self.client.post(self.url, form)
self.assertEqual(r.status_code, 200)
@ -161,7 +175,7 @@ class ProjectTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password")
form = {"invite_team_member": "1", "email": "[email protected]"}
form = {"invite_team_member": "1", "email": "[email protected]", "role": "r"}
r = self.client.post(self.url, form)
self.assertContains(r, "Too Many Requests")
@ -170,7 +184,7 @@ class ProjectTestCase(BaseTestCase):
def test_it_requires_owner_to_add_team_member(self):
self.client.login(username="[email protected]", password="password")
form = {"invite_team_member": "1", "email": "[email protected]"}
form = {"invite_team_member": "1", "email": "[email protected]", "role": "r"}
r = self.client.post(self.url, form)
self.assertEqual(r.status_code, 403)
@ -180,7 +194,7 @@ class ProjectTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password")
form = {"invite_team_member": "1", "email": "[email protected]"}
form = {"invite_team_member": "1", "email": "[email protected]", "role": "r"}
r = self.client.post(self.url, form)
self.assertEqual(r.status_code, 403)
@ -200,6 +214,19 @@ class ProjectTestCase(BaseTestCase):
r = self.client.post(self.url, form)
self.assertEqual(r.status_code, 403)
def test_it_rejects_manager_remove_self(self):
self.bobs_membership.role = "m"
self.bobs_membership.save()
self.client.login(username="[email protected]", password="password")
form = {"remove_team_member": "1", "email": "[email protected]"}
r = self.client.post(self.url, form)
self.assertEqual(r.status_code, 400)
# The number of memberships should have not decreased
self.assertEqual(self.project.member_set.count(), 1)
def test_it_checks_membership_when_removing_team_member(self):
self.client.login(username="[email protected]", password="password")


+ 10
- 3
hc/accounts/views.py View File

@ -284,9 +284,11 @@ def project(request, code):
is_owner = project.owner_id == request.user.id
if request.user.is_superuser or is_owner:
is_manager = True
rw = True
else:
membership = get_object_or_404(Member, project=project, user=request.user)
is_manager = membership.role == Member.Role.MANAGER
rw = membership.is_rw
ctx = {
@ -294,6 +296,7 @@ def project(request, code):
"rw": rw,
"project": project,
"is_owner": is_owner,
"is_manager": is_manager,
"show_api_keys": "show_api_keys" in request.GET,
"enable_prometheus": settings.PROMETHEUS_ENABLED is True,
}
@ -319,7 +322,7 @@ def project(request, code):
elif "show_api_keys" in request.POST:
ctx["show_api_keys"] = True
elif "invite_team_member" in request.POST:
if not is_owner:
if not is_manager:
return HttpResponseForbidden()
form = forms.InviteTeamMemberForm(request.POST)
@ -341,7 +344,7 @@ def project(request, code):
except User.DoesNotExist:
user = _make_user(email, with_project=False)
if project.invite(user, rw=form.cleaned_data["rw"]):
if project.invite(user, role=form.cleaned_data["role"]):
ctx["team_member_invited"] = email
ctx["team_status"] = "success"
else:
@ -349,7 +352,7 @@ def project(request, code):
ctx["team_status"] = "info"
elif "remove_team_member" in request.POST:
if not is_owner:
if not is_manager:
return HttpResponseForbidden()
form = forms.RemoveTeamMemberForm(request.POST)
@ -361,6 +364,9 @@ def project(request, code):
if farewell_user is None:
return HttpResponseBadRequest()
if farewell_user == request.user:
return HttpResponseBadRequest()
Member.objects.filter(project=project, user=farewell_user).delete()
ctx["team_member_removed"] = form.cleaned_data["email"]
@ -428,6 +434,7 @@ def project(request, code):
project.save()
ctx["is_owner"] = True
ctx["is_manager"] = True
messages.success(request, "You are now the owner of this project!")
elif "reject_transfer" in request.POST:


+ 1
- 1
hc/test.py View File

@ -36,7 +36,7 @@ class BaseTestCase(TestCase):
self.bobs_profile.save()
self.bobs_membership = Member.objects.create(
user=self.bob, project=self.project
user=self.bob, project=self.project, role=Member.Role.REGULAR
)
# Charlie should have no access to Alice's stuff


+ 18
- 7
templates/accounts/project.html View File

@ -169,7 +169,7 @@
<td class="email">{{ m.user.email }}</td>
<td>{{ m.get_role_display}}</td>
<td>
{% if is_owner %}
{% if is_manager and m.user != request.user %}
<a
href="#"
data-email="{{ m.user.email }}"
@ -179,7 +179,7 @@
</tr>
{% endfor %}
{% if is_owner and invite_suggestions %}
{% if is_manager and invite_suggestions %}
<tr id="suggestions-row">
<td colspan="3">
Add Users from Other Teams
@ -210,7 +210,7 @@
<br />
{% if is_owner %}
{% if is_manager %}
{% if project.can_invite_new_users %}
<a
href="#"
@ -401,8 +401,19 @@
<label class="radio-container">
<input
type="radio"
name="rw"
value="1"
name="role"
value="m">
<span class="radiomark"></span>
Manager
<span class="help-block">
Can invite/remove other members.
</span>
</label>
<label class="radio-container">
<input
type="radio"
name="role"
value="w"
checked>
<span class="radiomark"></span>
Team Member
@ -410,8 +421,8 @@
<label class="radio-container">
<input
type="radio"
name="rw"
value="">
name="role"
value="r">
<span class="radiomark"></span>
Read-only
<span class="help-block">


Loading…
Cancel
Save