This makes subobject attachment and detachment generic, making the views much easier to code up.

This commit is contained in:
Michael DeHaan 2013-03-22 16:52:44 -04:00
parent dc071d1914
commit 8b698d13d0
3 changed files with 69 additions and 60 deletions

View File

@ -28,6 +28,36 @@ class BaseList(generics.ListCreateAPIView):
def get_queryset(self):
return self._get_queryset().filter(active=True)
class BaseSubList(BaseList):
''' used for subcollections with an overriden post '''
def post(self, request, *args, **kwargs):
parent_id = kwargs['pk']
sub_id = request.DATA.get('id')
main = self.__class__.parent_model.objects.get(pk=parent_id)
subs = self.__class__.model.objects.filter(pk=sub_id)
if len(subs) != 1:
return Response(status=status.HTTP_400_BAD_REQUEST)
sub = subs[0]
relationship = getattr(main, self.__class__.relationship)
if not 'disassociate' in request.DATA:
if not request.user.is_superuser or not self.__class__.parent_model.can_user_attach(request.user, main, sub, self.__class__.relationship):
print "cond1"
raise PermissionDenied()
if sub in relationship.all():
return Response(status=status.HTTP_409_CONFLICT)
relationship.add(sub)
else:
if not request.user.is_superuser and not self.__class__.parent_model.can_user_unattach(request.user, main, sub, self.__class__.relationship):
print "cond2"
raise PermissionDenied()
relationship.remove(sub)
return Response(status=status.HTTP_204_NO_CONTENT)
class BaseDetail(generics.RetrieveUpdateDestroyAPIView):
def pre_save(self, obj):

View File

@ -32,7 +32,7 @@ class CommonModel(models.Model):
return unicode(self.name)
@classmethod
def can_user_administrate(cls, user):
def can_user_administrate(cls, user, obj):
raise exceptions.NotImplementedError()
@classmethod
@ -43,6 +43,20 @@ class CommonModel(models.Model):
def can_user_read(cls, user, obj):
raise exceptions.NotImplementedError()
@classmethod
def can_user_attach(cls, user, obj, sub_obj, relationship):
if relationship in [ 'projects', 'admins', 'users' ]:
if not sub_obj.can_user_read(user, sub_obj):
return False
return cls.can_user_administrate(user, obj)
else:
raise Exception("unknown relationship type: %s" % relationship)
return False
@classmethod
def can_user_unattach(cls, user, obj, relationship):
return cls.can_user_attach(user, obj, relationship)
class Tag(models.Model):
'''
@ -97,11 +111,16 @@ class Organization(CommonModel):
@classmethod
def can_user_administrate(cls, user, obj):
return user in obj.admins.all()
if user.is_superuser:
return True
rc = user in obj.admins.all()
return rc
@classmethod
def can_user_read(cls, user, obj):
return cls.can_user_administrate(user,obj) or user in obj.users.all()
rc = cls.can_user_administrate(user,obj) or user in obj.users.all()
return rc
@classmethod
def can_user_delete(cls, user, obj):
@ -205,12 +224,22 @@ class Project(CommonModel):
@classmethod
def can_user_administrate(cls, user, obj):
organizations = Organization.filter(admins__in = [ user ], projects__in = [ obj ])
organizations = self.organizations()
if user.is_superuser:
return True
organizations = Organization.objects.filter(admins__in = [ user ], projects__in = [ obj ])
for org in organizations:
if org in project.organizations():
return True
return True
return False
@classmethod
def can_user_read(cls, user, obj):
if cls.can_user_administrate(user,obj):
return True
# and also if I happen to be on a team inside the project
# FIXME: add this too
return False
class Permission(CommonModel):
'''

View File

@ -12,7 +12,7 @@ from rest_framework.response import Response
from rest_framework import status
import exceptions
import datetime
from base_views import BaseList, BaseDetail
from base_views import BaseList, BaseDetail, BaseSubList
class OrganizationsList(BaseList):
@ -88,9 +88,11 @@ class OrganizationsAdminsList(BaseList):
).distinct()
class OrganizationsProjectsList(BaseList):
class OrganizationsProjectsList(BaseSubList):
model = Project
parent_model = Organization
relationship = 'projects'
serializer_class = ProjectSerializer
permission_classes = (CustomRbac,)
@ -109,43 +111,6 @@ class OrganizationsProjectsList(BaseList):
teams__users__in = [ self.request.user ]
).distinct()
# BOOKMARK
def post(self, request, *args, **kwargs):
# POST { pk: 7, disassociate: True }
organization_id = kwargs['pk']
project_id = request.DATA.get('id')
organization = Organization.objects.get(pk=organization_id)
projects = Project.objects.filter(pk=project_id)
if len(projects) != 1:
return Response(status=status.HTTP_400_BAD_REQUEST)
project = projects[0]
# you can only add a project to an organization if you are a superuser or
# the person who created the project. TODO -- want to defer this question
# to the model. (FIXME)
if not 'disassociate' in request.DATA:
# admin of another org can't add a project to their org
if not request.user.is_superuser or project.created_by == request.user:
raise PermissionDenied()
if project in organization.projects.all():
return Response(status=status.HTTP_409_CONFLICT)
organization.projects.add(project)
else:
# to disassociate, be the org admin or a superuser
# FIXME: sprinkle these throughout the object layer & simplify
if not request.user.is_superuser and not project.can_user_administrate(request.user):
raise PermissionDenied()
organization.projects.remove(project)
# multiple attempts to delete the same thing aren't an error, we're cool
return Response(status=status.HTTP_204_NO_CONTENT)
class OrganizationsTagsList(BaseList):
# FIXME: guts & tests
pass
@ -156,20 +121,5 @@ class ProjectsDetail(BaseDetail):
serializer_class = ProjectSerializer
permission_classes = (CustomRbac,)
# #def item_permissions_check(self, request, obj):
#
# # to get, must be in a team assigned to this project
# # or be an org admin of an org this project is in
#
# raise exceptions.NotImplementedError()
#
# #is_admin = request.user in obj.admins.all()
# #is_user = request.user in obj.users.all()
# #
# #if request.method == 'GET':
# # return is_admin or is_user
# #elif request.method in [ 'PUT' ]:
# # return is_admin
# #return False