Reestructuring permissions, now discarted django-guardian, and using custom permission system
parent
4c060d3b4e
commit
3245393b55
|
@ -1,7 +1,8 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
|
||||
from greenmine.base.models import Role
|
||||
from greenmine.base.models import Role, User
|
||||
|
||||
admin.site.unregister(Group)
|
||||
|
||||
|
@ -19,3 +20,4 @@ class RoleAdmin(admin.ModelAdmin):
|
|||
db_field, request=request, **kwargs)
|
||||
|
||||
admin.site.register(Role, RoleAdmin)
|
||||
admin.site.register(User, UserAdmin)
|
||||
|
|
|
@ -25,6 +25,7 @@ class ApiRoot(APIView):
|
|||
'user-stories': reverse('user-story-list', request=request, format=format),
|
||||
'changes': reverse('change-list', request=request, format=format),
|
||||
'change-attachments': reverse('change-attachment-list', request=request, format=format),
|
||||
'issues': reverse('issue-list', request=request, format=format),
|
||||
'tasks': reverse('task-list', request=request, format=format),
|
||||
'severities': reverse('severity-list', request=request, format=format),
|
||||
'issue-status': reverse('issue-status-list', request=request, format=format),
|
||||
|
|
|
@ -64,31 +64,3 @@ class Role(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return unicode(self.name)
|
||||
|
||||
|
||||
if not hasattr(Group, 'role'):
|
||||
field = models.ForeignKey(Role, blank=False, null=False, related_name='groups')
|
||||
field.contribute_to_class(Group, 'role')
|
||||
|
||||
|
||||
if not hasattr(Group, 'project'):
|
||||
field = models.ForeignKey(Project, blank=False, null=False, related_name='groups')
|
||||
field.contribute_to_class(Group, 'project')
|
||||
|
||||
|
||||
@receiver(post_save, sender=Role)
|
||||
def role_post_save(sender, instance, **kwargs):
|
||||
"""
|
||||
Recalculate projects groups
|
||||
"""
|
||||
from greenmine.base.services import RoleGroupsService
|
||||
RoleGroupsService().replicate_role_on_all_projects(instance)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Role.permissions.through)
|
||||
def role_m2m_changed(sender, instance, **kwargs):
|
||||
"""
|
||||
Recalculate projects groups
|
||||
"""
|
||||
from greenmine.base.services import RoleGroupsService
|
||||
RoleGroupsService().replicate_role_on_all_projects(instance)
|
||||
|
|
|
@ -6,6 +6,12 @@ from greenmine.scrum import models
|
|||
import reversion
|
||||
|
||||
|
||||
class MembershipInline(admin.TabularInline):
|
||||
model = models.Membership
|
||||
fields = ('user', 'project', 'role')
|
||||
extra = 0
|
||||
|
||||
|
||||
class MilestoneInline(admin.TabularInline):
|
||||
model = models.Milestone
|
||||
fields = ('name', 'owner', 'estimated_start', 'estimated_finish', 'closed', 'disponibility', 'order')
|
||||
|
@ -27,7 +33,7 @@ class UserStoryInline(admin.TabularInline):
|
|||
|
||||
class ProjectAdmin(reversion.VersionAdmin):
|
||||
list_display = ["name", "owner"]
|
||||
inlines = [MilestoneInline, UserStoryInline]
|
||||
inlines = [MembershipInline, MilestoneInline, UserStoryInline]
|
||||
|
||||
admin.site.register(models.Project, ProjectAdmin)
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from rest_framework import generics
|
|||
|
||||
from greenmine.scrum.serializers import *
|
||||
from greenmine.scrum.models import *
|
||||
|
||||
from greenmine.scrum.permissions import *
|
||||
|
||||
class SimpleFilterMixin(object):
|
||||
filter_fields = []
|
||||
|
@ -36,10 +36,14 @@ class ProjectList(generics.ListCreateAPIView):
|
|||
model = Project
|
||||
serializer_class = ProjectSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(members=self.request.user)
|
||||
|
||||
|
||||
class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Project
|
||||
serializer_class = ProjectSerializer
|
||||
permission_classes = (ProjectDetailPermission,)
|
||||
|
||||
|
||||
class MilestoneList(SimpleFilterMixin, generics.ListCreateAPIView):
|
||||
|
@ -47,10 +51,14 @@ class MilestoneList(SimpleFilterMixin, generics.ListCreateAPIView):
|
|||
serializer_class = MilestoneSerializer
|
||||
filter_fields = ('project',)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class MilestoneDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Milestone
|
||||
serializer_class = MilestoneSerializer
|
||||
permission_classes = (MilestoneDetailPermission,)
|
||||
|
||||
|
||||
class UserStoryList(SimpleFilterMixin, generics.ListCreateAPIView):
|
||||
|
@ -58,6 +66,9 @@ class UserStoryList(SimpleFilterMixin, generics.ListCreateAPIView):
|
|||
serializer_class = UserStorySerializer
|
||||
filter_fields = ('project', 'milestone')
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class UserStoryDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = UserStory
|
||||
|
@ -68,6 +79,9 @@ class ChangeList(generics.ListCreateAPIView):
|
|||
model = Change
|
||||
serializer_class = ChangeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class ChangeDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Change
|
||||
|
@ -78,17 +92,37 @@ class ChangeAttachmentList(generics.ListCreateAPIView):
|
|||
model = ChangeAttachment
|
||||
serializer_class = ChangeAttachmentSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(change__project__members=self.request.user)
|
||||
|
||||
|
||||
class ChangeAttachmentDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = ChangeAttachment
|
||||
serializer_class = ChangeAttachmentSerializer
|
||||
|
||||
|
||||
class IssueList(generics.ListCreateAPIView):
|
||||
model = Issue
|
||||
serializer_class = IssueSerializer
|
||||
filter_fields = ('project',)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class IssueDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Issue
|
||||
serializer_class = IssueSerializer
|
||||
|
||||
|
||||
class TaskList(generics.ListCreateAPIView):
|
||||
model = Task
|
||||
serializer_class = TaskSerializer
|
||||
filter_fields = ('user_story', 'milestone', 'project')
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class TaskDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Task
|
||||
|
@ -100,6 +134,9 @@ class SeverityList(generics.ListCreateAPIView):
|
|||
serializer_class = SeveritySerializer
|
||||
filter_fields = ('project',)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class SeverityDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Severity
|
||||
|
@ -111,6 +148,9 @@ class IssueStatusList(generics.ListCreateAPIView):
|
|||
serializer_class = IssueStatusSerializer
|
||||
filter_fields = ('project',)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class IssueStatusDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = IssueStatus
|
||||
|
@ -122,6 +162,9 @@ class TaskStatusList(SimpleFilterMixin, generics.ListCreateAPIView):
|
|||
serializer_class = TaskStatusSerializer
|
||||
filter_fields = ('project',)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class TaskStatusDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = TaskStatus
|
||||
|
@ -133,6 +176,9 @@ class UserStoryStatusList(generics.ListCreateAPIView):
|
|||
serializer_class = UserStoryStatusSerializer
|
||||
filter_fields = ('project',)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class UserStoryStatusDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = UserStoryStatus
|
||||
|
@ -144,6 +190,9 @@ class PriorityList(generics.ListCreateAPIView):
|
|||
serializer_class = PrioritySerializer
|
||||
filter_fields = ('project',)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class PriorityDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Priority
|
||||
|
@ -155,6 +204,9 @@ class IssueTypeList(generics.ListCreateAPIView):
|
|||
serializer_class = IssueTypeSerializer
|
||||
filter_fields = ('project',)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class IssueTypeDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = IssueType
|
||||
|
@ -166,6 +218,9 @@ class PointsList(generics.ListCreateAPIView):
|
|||
serializer_class = PointsSerializer
|
||||
filter_fields = ('project',)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(project__members=self.request.user)
|
||||
|
||||
|
||||
class PointsDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
model = Points
|
||||
|
|
|
@ -105,6 +105,14 @@ class Points(models.Model):
|
|||
return u"project({0})/point({1})".format(self.project.id, self.name)
|
||||
|
||||
|
||||
class Membership(models.Model):
|
||||
user = models.ForeignKey("base.User")
|
||||
project = models.ForeignKey("Project")
|
||||
role = models.ForeignKey("base.Role")
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'project')
|
||||
|
||||
class Project(models.Model):
|
||||
uuid = models.CharField(max_length=40, unique=True, blank=True)
|
||||
name = models.CharField(max_length=250, unique=True)
|
||||
|
@ -114,7 +122,8 @@ class Project(models.Model):
|
|||
created_date = models.DateTimeField(auto_now_add=True)
|
||||
modified_date = models.DateTimeField(auto_now_add=True, auto_now=True)
|
||||
|
||||
owner = models.ForeignKey("base.User", related_name="projects")
|
||||
owner = models.ForeignKey("base.User", related_name="owned_projects")
|
||||
members = models.ManyToManyField("base.User", related_name="projects", through='Membership')
|
||||
public = models.BooleanField(default=True)
|
||||
|
||||
last_us_ref = models.BigIntegerField(null=True, default=1)
|
||||
|
@ -170,6 +179,8 @@ class Project(models.Model):
|
|||
|
||||
('create_milestone', 'Can create milestones'),
|
||||
('modify_milestone', 'Can modify milestones'),
|
||||
('view_milestone', 'Can view milestones'),
|
||||
('delete_milestone', 'Can delete milestones'),
|
||||
|
||||
('manage_users', 'Can manage users'),
|
||||
)
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
from rest_framework import permissions
|
||||
|
||||
from greenmine.scrum.models import Membership
|
||||
|
||||
def has_project_perm(user, project, perm):
|
||||
if user.is_authenticated():
|
||||
try:
|
||||
membership = Membership.objects.get(project=project, user=user)
|
||||
if membership.role.permissions.filter(codename=perm).count() > 0:
|
||||
return True
|
||||
except Membership.DoesNotExist:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
class BaseDetailPermission(permissions.BasePermission):
|
||||
get_permission = None
|
||||
put_permission = None
|
||||
delete_permission = None
|
||||
safe_methods = ['HEAD', 'OPTIONS']
|
||||
path_to_project = []
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in self.safe_methods:
|
||||
return True
|
||||
|
||||
project_obj = obj
|
||||
for attrib in self.path_to_project:
|
||||
project_obj = getattr(project_obj, attrib)
|
||||
|
||||
if request.method == "GET":
|
||||
return has_project_perm(request.user, project_obj, self.get_permission)
|
||||
|
||||
elif request.method == "PUT":
|
||||
return has_project_perm(request.user, project_obj, self.put_permission)
|
||||
|
||||
elif request.method == "DELETE":
|
||||
return has_project_perm(request.user, project_obj, self.delete_permission)
|
||||
|
||||
return False
|
||||
|
||||
class ProjectDetailPermission(BaseDetailPermission):
|
||||
get_permission = "view_projects"
|
||||
put_permission = "modify_projects"
|
||||
delete_permission = "delete_projects"
|
||||
safe_methods = ['HEAD', 'OPTIONS']
|
||||
path_to_project = []
|
||||
|
||||
class MilestoneDetailPermission(BaseDetailPermission):
|
||||
get_permission = "view_milestone"
|
||||
put_permission = "modify_milestone"
|
||||
delete_permission = "delete_milestone"
|
||||
safe_methods = ['HEAD', 'OPTIONS']
|
||||
path_to_project = ['project']
|
|
@ -14,6 +14,8 @@ urlpatterns = format_suffix_patterns(patterns('',
|
|||
url(r'^changes/(?P<pk>[0-9]+)/$', api.ChangeDetail.as_view(), name='change-detail'),
|
||||
url(r'^change_attachments/$', api.ChangeAttachmentList.as_view(), name='change-attachment-list'),
|
||||
url(r'^change_attachments/(?P<pk>[0-9]+)/$', api.ChangeAttachmentDetail.as_view(), name='change-attachment-detail'),
|
||||
url(r'^issues/$', api.IssueList.as_view(), name='issue-list'),
|
||||
url(r'^issues/(?P<pk>[0-9]+)/$', api.IssueDetail.as_view(), name='issue-detail'),
|
||||
url(r'^tasks/$', api.TaskList.as_view(), name='task-list'),
|
||||
url(r'^tasks/(?P<pk>[0-9]+)/$', api.TaskDetail.as_view(), name='task-detail'),
|
||||
url(r'^severities/$', api.SeverityList.as_view(), name='severity-list'),
|
||||
|
|
|
@ -7,7 +7,6 @@ billiard==2.7.3.23
|
|||
celery==3.0.17
|
||||
django-celery==3.0.11
|
||||
django-grappelli==2.4.4
|
||||
django-guardian==1.1.0.beta
|
||||
django-reversion==1.7
|
||||
git+git://github.com/toastdriven/django-haystack.git
|
||||
django-picklefield==0.3.0
|
||||
|
|
Loading…
Reference in New Issue