mirror of
https://github.com/ansible/awx.git
synced 2026-03-09 05:29:26 -02:30
This allows for creation of subresources when posting new objects to a subcollection.
This commit is contained in:
@@ -29,6 +29,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
import exceptions
|
import exceptions
|
||||||
import datetime
|
import datetime
|
||||||
|
import json as python_json
|
||||||
|
|
||||||
# FIXME: machinery for auto-adding audit trail logs to all CREATE/EDITS
|
# FIXME: machinery for auto-adding audit trail logs to all CREATE/EDITS
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ class BaseList(generics.ListCreateAPIView):
|
|||||||
return True
|
return True
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if self.__class__.model in [ User ]:
|
if self.__class__.model in [ User ]:
|
||||||
ok = self.request.user.is_superuser or (self.request.user.admin_of_organizations.count() > 0)
|
ok = request.user.is_superuser or (request.user.admin_of_organizations.count() > 0)
|
||||||
if not ok:
|
if not ok:
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
return True
|
return True
|
||||||
@@ -83,9 +84,58 @@ class BaseSubList(BaseList):
|
|||||||
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
|
||||||
parent_id = kwargs['pk']
|
parent_id = kwargs['pk']
|
||||||
sub_id = request.DATA.get('id')
|
sub_id = request.DATA.get('id', None)
|
||||||
main = self.__class__.parent_model.objects.get(pk=parent_id)
|
main = self.__class__.parent_model.objects.get(pk=parent_id)
|
||||||
subs = self.__class__.model.objects.filter(pk=sub_id)
|
|
||||||
|
subs = None
|
||||||
|
|
||||||
|
if sub_id:
|
||||||
|
subs = self.__class__.model.objects.filter(pk=sub_id)
|
||||||
|
else:
|
||||||
|
if 'disassociate' in request.DATA:
|
||||||
|
raise PermissionDenied() # ID is required to disassociate
|
||||||
|
else:
|
||||||
|
|
||||||
|
# this is a little tricky and a little manual
|
||||||
|
# the object ID was not specified, so it probably doesn't exist in the DB yet.
|
||||||
|
# we want to see if we can create it. The URL may choose to inject it's primary key into the object
|
||||||
|
# because we are posting to a subcollection. Use all the normal access control mechanisms.
|
||||||
|
|
||||||
|
inject_primary_key = getattr(self.__class__, 'inject_primary_key_on_post_as', None)
|
||||||
|
|
||||||
|
if inject_primary_key is not None:
|
||||||
|
|
||||||
|
# add the key to the post data using the pk from the URL
|
||||||
|
request.DATA[inject_primary_key] = kwargs['pk']
|
||||||
|
|
||||||
|
# attempt to deserialize the object
|
||||||
|
ser = self.__class__.serializer_class(data=request.DATA)
|
||||||
|
if not ser.is_valid():
|
||||||
|
return Response(status=status.HTTP_400_BAD_REQUEST, data=python_json.dumps(dict(msg='invalid post data')))
|
||||||
|
|
||||||
|
# ask the usual access control settings
|
||||||
|
if not self.__class__.model.can_user_add(request.user, ser.init_data):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
# save the object through the serializer, reload and returned the saved object deserialized
|
||||||
|
obj = ser.save()
|
||||||
|
ser = self.__class__.serializer_class(obj)
|
||||||
|
|
||||||
|
# now make sure we could have already attached the two together. If we could not have, raise an exception
|
||||||
|
# such that the transaction does not commit.
|
||||||
|
if not self.__class__.parent_model.can_user_attach(request.user, main, obj, self.__class__.relationship):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
return Response(status=status.HTTP_201_CREATED, data=python_json.dumps(ser.data))
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
# view didn't specify a way to get the pk from the URL, so not even trying
|
||||||
|
return Response(status=status.HTTP_400_BAD_REQUEST, data=python_json.dumps(dict(msg='object cannot be created')))
|
||||||
|
|
||||||
|
# we didn't have to create the object, so this is just associating the two objects together now...
|
||||||
|
# (or disassociating them)
|
||||||
|
|
||||||
if len(subs) != 1:
|
if len(subs) != 1:
|
||||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||||
sub = subs[0]
|
sub = subs[0]
|
||||||
|
|||||||
@@ -371,6 +371,10 @@ class Host(CommonModelNameNotUnique):
|
|||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_user_read(cls, user, obj):
|
||||||
|
return Inventory.can_user_read(user, obj.inventory)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def can_user_add(cls, user, data):
|
def can_user_add(cls, user, data):
|
||||||
|
|||||||
@@ -130,14 +130,14 @@ class InventoryTest(BaseTest):
|
|||||||
new_host_c = dict(name='asdf2.example.com', inventory=inv.pk)
|
new_host_c = dict(name='asdf2.example.com', inventory=inv.pk)
|
||||||
new_host_d = dict(name='asdf3.example.com', inventory=inv.pk)
|
new_host_d = dict(name='asdf3.example.com', inventory=inv.pk)
|
||||||
new_host_e = dict(name='asdf4.example.com', inventory=inv.pk)
|
new_host_e = dict(name='asdf4.example.com', inventory=inv.pk)
|
||||||
data0 = self.post(hosts, data=invalid, expect=400, auth=self.get_super_credentials())
|
host_data0 = self.post(hosts, data=invalid, expect=400, auth=self.get_super_credentials())
|
||||||
data0 = self.post(hosts, data=new_host_a, expect=201, auth=self.get_super_credentials())
|
host_data0 = self.post(hosts, data=new_host_a, expect=201, auth=self.get_super_credentials())
|
||||||
|
|
||||||
# an org admin can add hosts
|
# an org admin can add hosts
|
||||||
data1 = self.post(hosts, data=new_host_e, expect=201, auth=self.get_normal_credentials())
|
host_data1 = self.post(hosts, data=new_host_e, expect=201, auth=self.get_normal_credentials())
|
||||||
|
|
||||||
# a normal user cannot add hosts
|
# a normal user cannot add hosts
|
||||||
data2 = self.post(hosts, data=new_host_b, expect=403, auth=self.get_nobody_credentials())
|
host_data2 = self.post(hosts, data=new_host_b, expect=403, auth=self.get_nobody_credentials())
|
||||||
|
|
||||||
# a normal user with inventory edit permissions (on any inventory) can create hosts
|
# a normal user with inventory edit permissions (on any inventory) can create hosts
|
||||||
edit_perm = Permission.objects.create(
|
edit_perm = Permission.objects.create(
|
||||||
@@ -145,10 +145,10 @@ class InventoryTest(BaseTest):
|
|||||||
inventory = Inventory.objects.get(pk=inv.pk),
|
inventory = Inventory.objects.get(pk=inv.pk),
|
||||||
permission_type = PERM_INVENTORY_WRITE
|
permission_type = PERM_INVENTORY_WRITE
|
||||||
)
|
)
|
||||||
data3 = self.post(hosts, data=new_host_c, expect=201, auth=self.get_other_credentials())
|
host_data3 = self.post(hosts, data=new_host_c, expect=201, auth=self.get_other_credentials())
|
||||||
|
|
||||||
# hostnames must be unique inside an organization
|
# hostnames must be unique inside an organization
|
||||||
data4 = self.post(hosts, data=new_host_c, expect=400, auth=self.get_other_credentials())
|
host_data4 = self.post(hosts, data=new_host_c, expect=400, auth=self.get_other_credentials())
|
||||||
|
|
||||||
###########################################
|
###########################################
|
||||||
# GROUPS
|
# GROUPS
|
||||||
@@ -163,10 +163,10 @@ class InventoryTest(BaseTest):
|
|||||||
data0 = self.post(groups, data=new_group_a, expect=201, auth=self.get_super_credentials())
|
data0 = self.post(groups, data=new_group_a, expect=201, auth=self.get_super_credentials())
|
||||||
|
|
||||||
# an org admin can add hosts
|
# an org admin can add hosts
|
||||||
data1 = self.post(groups, data=new_group_e, expect=201, auth=self.get_normal_credentials())
|
group_data1 = self.post(groups, data=new_group_e, expect=201, auth=self.get_normal_credentials())
|
||||||
|
|
||||||
# a normal user cannot add hosts
|
# a normal user cannot add hosts
|
||||||
data2 = self.post(groups, data=new_group_b, expect=403, auth=self.get_nobody_credentials())
|
group_data2 = self.post(groups, data=new_group_b, expect=403, auth=self.get_nobody_credentials())
|
||||||
|
|
||||||
# a normal user with inventory edit permissions (on any inventory) can create hosts
|
# a normal user with inventory edit permissions (on any inventory) can create hosts
|
||||||
# already done!
|
# already done!
|
||||||
@@ -175,13 +175,18 @@ class InventoryTest(BaseTest):
|
|||||||
# inventory = Inventory.objects.get(pk=inv.pk),
|
# inventory = Inventory.objects.get(pk=inv.pk),
|
||||||
# permission_type = PERM_INVENTORY_WRITE
|
# permission_type = PERM_INVENTORY_WRITE
|
||||||
#)
|
#)
|
||||||
data3 = self.post(groups, data=new_group_c, expect=201, auth=self.get_other_credentials())
|
group_data3 = self.post(groups, data=new_group_c, expect=201, auth=self.get_other_credentials())
|
||||||
|
|
||||||
# hostnames must be unique inside an organization
|
# hostnames must be unique inside an organization
|
||||||
data4 = self.post(groups, data=new_group_c, expect=400, auth=self.get_other_credentials())
|
group_data4 = self.post(groups, data=new_group_c, expect=400, auth=self.get_other_credentials())
|
||||||
|
|
||||||
#################################################
|
#################################################
|
||||||
# HOSTS->inventories
|
# HOSTS->inventories POST via subcollection
|
||||||
|
|
||||||
|
url = '/api/v1/inventories/1/hosts/'
|
||||||
|
|
||||||
|
new_host = dict(name='web100.example.com')
|
||||||
|
added_by_collection = self.post(url, data=new_host, expect=201, auth=self.get_super_credentials())
|
||||||
|
|
||||||
# a super user can associate hosts with inventories
|
# a super user can associate hosts with inventories
|
||||||
|
|
||||||
@@ -192,7 +197,7 @@ class InventoryTest(BaseTest):
|
|||||||
# a normal user with edit permission on the inventory can associate hosts with inventories
|
# a normal user with edit permission on the inventory can associate hosts with inventories
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
# GROUPS->inventories
|
# GROUPS->inventories POST via subcollection
|
||||||
|
|
||||||
# a super user can associate groups with inventories
|
# a super user can associate groups with inventories
|
||||||
|
|
||||||
|
|||||||
@@ -260,9 +260,7 @@ class InventoryList(BaseList):
|
|||||||
serializer_class = InventorySerializer
|
serializer_class = InventorySerializer
|
||||||
permission_classes = (CustomRbac,)
|
permission_classes = (CustomRbac,)
|
||||||
|
|
||||||
def _get_queryset(self):
|
def _filter_queryset(self, base):
|
||||||
''' I can see inventory when I'm a superuser, an org admin of the inventory, or I have permissions on it '''
|
|
||||||
base = Inventory.objects
|
|
||||||
if self.request.user.is_superuser:
|
if self.request.user.is_superuser:
|
||||||
return base.all()
|
return base.all()
|
||||||
admin_of = base.filter(organization__admins__in = [ self.request.user ]).distinct()
|
admin_of = base.filter(organization__admins__in = [ self.request.user ]).distinct()
|
||||||
@@ -276,6 +274,11 @@ class InventoryList(BaseList):
|
|||||||
).distinct()
|
).distinct()
|
||||||
return admin_of | has_user_perms | has_team_perms
|
return admin_of | has_user_perms | has_team_perms
|
||||||
|
|
||||||
|
def _get_queryset(self):
|
||||||
|
''' I can see inventory when I'm a superuser, an org admin of the inventory, or I have permissions on it '''
|
||||||
|
base = Inventory.objects
|
||||||
|
return self._filter_queryset(base)
|
||||||
|
|
||||||
class InventoryDetail(BaseDetail):
|
class InventoryDetail(BaseDetail):
|
||||||
|
|
||||||
model = Inventory
|
model = Inventory
|
||||||
@@ -315,6 +318,23 @@ class HostsDetail(BaseDetail):
|
|||||||
serializer_class = HostSerializer
|
serializer_class = HostSerializer
|
||||||
permission_classes = (CustomRbac,)
|
permission_classes = (CustomRbac,)
|
||||||
|
|
||||||
|
class InventoryHostsList(BaseSubList):
|
||||||
|
|
||||||
|
model = Host
|
||||||
|
serializer_class = HostSerializer
|
||||||
|
permission_classes = (CustomRbac,)
|
||||||
|
# to allow the sub-aspect listing
|
||||||
|
parent_model = Inventory
|
||||||
|
relationship = 'hosts'
|
||||||
|
# to allow posting to this resource to create resources
|
||||||
|
postable = True
|
||||||
|
# FIXME: go back and add these to other SubLists
|
||||||
|
inject_primary_key_on_post_as = 'inventory'
|
||||||
|
|
||||||
|
def _get_queryset(self):
|
||||||
|
# FIXME: more DRY methods like this
|
||||||
|
return Inventory._filter_queryset(Inventory.objects.get(pk=self.kwargs['pk']).hosts)
|
||||||
|
|
||||||
class GroupsList(BaseList):
|
class GroupsList(BaseList):
|
||||||
|
|
||||||
model = Group
|
model = Group
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ TEMPLATE_CONTEXT_PROCESSORS += (
|
|||||||
MIDDLEWARE_CLASSES += (
|
MIDDLEWARE_CLASSES += (
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'lib.middleware.exceptions.ExceptionMiddleware',
|
'lib.middleware.exceptions.ExceptionMiddleware',
|
||||||
#'django.middleware.transaction.TransactionMiddleware',
|
'django.middleware.transaction.TransactionMiddleware',
|
||||||
|
# middleware loaded after this point will be subject to transactions
|
||||||
)
|
)
|
||||||
|
|
||||||
TEMPLATE_DIRS = (
|
TEMPLATE_DIRS = (
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ views_ProjectsDetail = views.OrganizationsDetail.as_view()
|
|||||||
# inventory service
|
# inventory service
|
||||||
views_InventoryList = views.InventoryList.as_view()
|
views_InventoryList = views.InventoryList.as_view()
|
||||||
views_InventoryDetail = views.InventoryDetail.as_view()
|
views_InventoryDetail = views.InventoryDetail.as_view()
|
||||||
|
views_InventoryHostsList = views.InventoryHostsList.as_view()
|
||||||
|
|
||||||
# group service
|
# group service
|
||||||
views_GroupsList = views.GroupsList.as_view()
|
views_GroupsList = views.GroupsList.as_view()
|
||||||
@@ -95,6 +96,7 @@ urlpatterns = patterns('',
|
|||||||
# inventory service
|
# inventory service
|
||||||
url(r'^api/v1/inventories/$', views_InventoryList),
|
url(r'^api/v1/inventories/$', views_InventoryList),
|
||||||
url(r'^api/v1/inventories/(?P<pk>[0-9]+)/$', views_InventoryDetail),
|
url(r'^api/v1/inventories/(?P<pk>[0-9]+)/$', views_InventoryDetail),
|
||||||
|
url(r'^api/v1/inventories/(?P<pk>[0-9]+)/hosts/$', views_InventoryHostsList),
|
||||||
|
|
||||||
# host service
|
# host service
|
||||||
url(r'^api/v1/hosts/$', views_HostsList),
|
url(r'^api/v1/hosts/$', views_HostsList),
|
||||||
|
|||||||
Reference in New Issue
Block a user