Skip to content

Commit c1082e1

Browse files
rbinraismargaretmeehan
authored andcommitted
Role and RoleMapping Models
1 parent 8d66e69 commit c1082e1

File tree

10 files changed

+356
-39
lines changed

10 files changed

+356
-39
lines changed

app/api/migrations/0004_roles.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Generated by Django 2.1.7 on 2019-07-25 03:33
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
('api', '0003_support_sql_server'),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='Role',
18+
fields=[
19+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20+
('name', models.CharField(max_length=100, unique=True)),
21+
('description', models.TextField(default='', null=True)),
22+
('created_at', models.DateTimeField(auto_now_add=True)),
23+
('updated_at', models.DateTimeField(auto_now=True)),
24+
],
25+
),
26+
migrations.CreateModel(
27+
name='RoleMapping',
28+
fields=[
29+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
30+
('created_at', models.DateTimeField(auto_now_add=True)),
31+
('updated_at', models.DateTimeField(auto_now=True)),
32+
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='role_mapping', to='api.Project')),
33+
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Role')),
34+
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='role_mapping', to=settings.AUTH_USER_MODEL)),
35+
],
36+
),
37+
migrations.AlterUniqueTogether(
38+
name='rolemapping',
39+
unique_together={('user', 'project', 'role')},
40+
),
41+
]

app/api/models.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import string
22

33
from django.db import models
4+
from django.dispatch import receiver
5+
from django.db.models.signals import post_save, pre_save, pre_delete
46
from django.urls import reverse
57
from django.contrib.auth.models import User
68
from django.contrib.staticfiles.storage import staticfiles_storage
@@ -237,3 +239,49 @@ class Seq2seqAnnotation(Annotation):
237239

238240
class Meta:
239241
unique_together = ('document', 'user', 'text')
242+
243+
244+
class Role(models.Model):
245+
name = models.CharField(max_length=100, unique=True)
246+
description = models.TextField(default='')
247+
created_at = models.DateTimeField(auto_now_add=True)
248+
updated_at = models.DateTimeField(auto_now=True)
249+
250+
251+
class RoleMapping(models.Model):
252+
user = models.ForeignKey(User, related_name='role_mapping', on_delete=models.CASCADE)
253+
project = models.ForeignKey(Project, related_name='role_mapping', on_delete=models.CASCADE)
254+
role = models.ForeignKey(Role, on_delete=models.CASCADE)
255+
created_at = models.DateTimeField(auto_now_add=True)
256+
updated_at = models.DateTimeField(auto_now=True)
257+
258+
def clean(self):
259+
other_rolemappings = self.project.role_mappings.exclude(id=self.id)
260+
261+
if other_rolemappings.filter(user=self.user, project=self.project).exists():
262+
raise ValidationError('This user is already assigned to a role in this project.')
263+
264+
class Meta:
265+
unique_together = ("user", "project", "role")
266+
267+
268+
@receiver(post_save, sender=RoleMapping)
269+
def add_linked_project(sender, instance, created, **kwargs):
270+
userInstance = instance.user
271+
projectInstance = instance.project
272+
if created and userInstance and projectInstance:
273+
user = User.objects.get(pk=userInstance.pk)
274+
project = Project.objects.get(pk=projectInstance.pk)
275+
user.projects.add(project)
276+
user.save()
277+
278+
279+
@receiver(pre_delete, sender=RoleMapping)
280+
def delete_linked_project(sender, instance, using, **kwargs):
281+
userInstance = instance.user
282+
projectInstance = instance.project
283+
if userInstance and projectInstance:
284+
user = User.objects.get(pk=userInstance.pk)
285+
project = Project.objects.get(pk=projectInstance.pk)
286+
user.projects.remove(project)
287+
user.save()

app/api/permissions.py

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1+
from django.conf import settings
12
from django.contrib.auth.mixins import UserPassesTestMixin
3+
from django.db.models import Subquery
24
from django.shortcuts import get_object_or_404
35
from rest_framework.permissions import BasePermission, SAFE_METHODS, IsAdminUser
46

5-
from .models import Project
7+
from .models import Project, Role, RoleMapping
68

79

8-
class IsProjectUser(BasePermission):
10+
class ProjectMixin:
11+
def get_project_id(self, request, view):
12+
return view.kwargs.get('project_id') or request.query_params.get('project_id')
913

10-
def has_permission(self, request, view):
11-
user = request.user
12-
project_id = view.kwargs.get('project_id') or request.query_params.get('project_id')
13-
project = get_object_or_404(Project, pk=project_id)
1414

15+
class IsProjectUser(ProjectMixin, BasePermission):
16+
17+
def has_permission(self, request, view):
18+
project = get_object_or_404(Project, pk=self.get_project_id(request, view))
1519
return user in project.users.all()
1620

1721

@@ -30,13 +34,62 @@ def test_func(self):
3034
return self.request.user.is_superuser
3135

3236

33-
class IsOwnAnnotation(BasePermission):
37+
class IsOwnAnnotation(ProjectMixin, BasePermission):
3438

3539
def has_permission(self, request, view):
36-
project_id = view.kwargs.get('project_id')
40+
project_id = self.get_project_id(request, view)
3741
annotation_id = view.kwargs.get('annotation_id')
3842
project = get_object_or_404(Project, pk=project_id)
3943
model = project.get_annotation_class()
4044
annotation = model.objects.filter(id=annotation_id, user=request.user)
4145

4246
return annotation.exists()
47+
48+
49+
class RolePermission(ProjectMixin, BasePermission):
50+
UNSAFE_METHODS = ('POST', 'PATCH', 'DELETE')
51+
unsafe_methods_check = True
52+
role_name = ''
53+
54+
def is_super_user(self, user):
55+
return user.is_superuser
56+
57+
def has_permission(self, request, view):
58+
is_super_user = self.is_super_user(request.user)
59+
if is_super_user:
60+
return True
61+
62+
if self.unsafe_methods_check and request.method in self.UNSAFE_METHODS:
63+
return is_super_user
64+
65+
project_id = self.get_project_id(request, view)
66+
if not project_id and request.method in SAFE_METHODS:
67+
return True
68+
69+
return is_in_role(self.role_name, request.user.id, project_id)
70+
71+
72+
class IsProjectAdmin(RolePermission):
73+
unsafe_methods_check = False
74+
role_name = settings.ROLE_PROJECT_ADMIN
75+
76+
77+
class IsAnnotatorAndCreator(RolePermission):
78+
unsafe_methods_check = False
79+
role_name = settings.ROLE_ANNOTATOR
80+
81+
82+
class IsAnnotator(RolePermission):
83+
role_name = settings.ROLE_ANNOTATOR
84+
85+
86+
class IsAnnotationApprover(RolePermission):
87+
role_name = settings.ROLE_ANNOTATION_APPROVER
88+
89+
90+
def is_in_role(role_name, user_id, project_id):
91+
return RoleMapping.objects.filter(
92+
user_id=user_id,
93+
project_id=project_id,
94+
role_id=Subquery(Role.objects.filter(name=role_name).values('id')),
95+
).exists()

0 commit comments

Comments
 (0)