Reestructuring permissions, now discarted django-guardian, and using custom permission system

remotes/origin/enhancement/email-actions
Jesús Espino 2013-03-30 11:56:50 +01:00
parent 4c060d3b4e
commit 3245393b55
9 changed files with 135 additions and 33 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'),
)

View File

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

View File

@ -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'),

View File

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