mirror of
https://github.com/ansible/awx.git
synced 2026-02-21 05:00:07 -03:30
Switch over to django-rest-framework from tastypie. Less black magic, seems to just work :)
This commit is contained in:
15
TODO.md
15
TODO.md
@@ -1,21 +1,16 @@
|
|||||||
TODO items for ansible commander
|
TODO items for ansible commander
|
||||||
================================
|
================================
|
||||||
|
|
||||||
* tastypie subresources? Maybe not. Are they needed?
|
* finish shift to DJANGO REST FRAMEWORK from tastypie, rework client lib code
|
||||||
|
* write custom rbac & validation classes
|
||||||
* tastypie authz (various subclasses) using RBAC permissions model
|
* determine how to auto-add hrefs to serializers (including sub-resources)
|
||||||
|
|
||||||
** for editing, is user able to edit the resource
|
|
||||||
** if they can, did they remove anything they should not remove or add anything they cannot add?
|
|
||||||
** did they set any properites on any resources beyond just creating them?
|
|
||||||
|
|
||||||
* tastypie tests using various users
|
|
||||||
|
|
||||||
* CLI client
|
* CLI client
|
||||||
* business logic
|
* business logic
|
||||||
* celery integration / job status API
|
* celery integration / job status API
|
||||||
* UI layer
|
* UI layer
|
||||||
* clean up initial migrations
|
* clean up initial migrations
|
||||||
|
* inventory plugin
|
||||||
|
* reporting plugin
|
||||||
|
|
||||||
NEXT STEPS
|
NEXT STEPS
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
from tastypie.authentication import Authentication
|
|
||||||
from tastypie.authorization import Authorization
|
|
||||||
|
|
||||||
# FIXME: this is completely stubbed out at this point!
|
|
||||||
# INTENTIONALLY NOT IMPLEMENTED CORRECTLY :)
|
|
||||||
|
|
||||||
class AcomAuthorization(Authorization):
|
|
||||||
|
|
||||||
def is_authorized(self, request, object=None):
|
|
||||||
if request.user.username == 'admin':
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Optional but useful for advanced limiting, such as per user.
|
|
||||||
def apply_limits(self, request, object_list):
|
|
||||||
#if request and hasattr(request, 'user'):
|
|
||||||
# return object_list.filter(author__username=request.user.username)
|
|
||||||
#return object_list.none()
|
|
||||||
return object_list.all()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# Empty models file.
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
from tastypie.resources import Resource, ModelResource, ALL
|
|
||||||
from tastypie.authentication import BasicAuthentication
|
|
||||||
from tastypie import fields, utils
|
|
||||||
from lib.api.auth import AcomAuthorization
|
|
||||||
#from django.conf.urls import url
|
|
||||||
import lib.main.models as models
|
|
||||||
from lib.vendor.extendedmodelresource import ExtendedModelResource
|
|
||||||
from tastypie.authorization import Authorization
|
|
||||||
|
|
||||||
class OrganizationAuthorization(Authorization):
|
|
||||||
"""
|
|
||||||
Our Authorization class for UserResource and its nested.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def is_authorized(self, request, object=None):
|
|
||||||
if request.user.username == 'admin':
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_authorized(self, request, object=None):
|
|
||||||
# HACK
|
|
||||||
if 'admin' in request.user.username:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def apply_limits(self, request, object_list):
|
|
||||||
return object_list.all()
|
|
||||||
|
|
||||||
def is_authorized_nested_projects(self, request, parent_object, object=None):
|
|
||||||
# Is request.user authorized to access the EntryResource as # nested?
|
|
||||||
return True
|
|
||||||
|
|
||||||
def apply_limits_nested_projects(self, request, parent_object, object_list):
|
|
||||||
# Advanced filtering.
|
|
||||||
# Note that object_list already only contains the objects that
|
|
||||||
# are associated to parent_object.
|
|
||||||
return object_list.all()
|
|
||||||
|
|
||||||
class Organizations(ExtendedModelResource):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
# related fields...
|
|
||||||
|
|
||||||
queryset = models.Organization.objects.all()
|
|
||||||
resource_name = 'organizations'
|
|
||||||
|
|
||||||
authentication = BasicAuthentication()
|
|
||||||
#authorization = AcomAuthorization()
|
|
||||||
authorization = OrganizationAuthorization()
|
|
||||||
|
|
||||||
class Nested:
|
|
||||||
#users = fields.ToManyField('lib.api.resources.Users', 'users', related_name='organizations', blank=True, help_text='list of all organization users')
|
|
||||||
#admins = fields.ToManyField('lib.api.resources.Users', 'admins', related_name='admin_of_organizations', blank=True, help_text='list of administrator users')
|
|
||||||
projects = fields.ToManyField('lib.api.resources.Projects', 'projects') # blank=True, help_text='list of projects')
|
|
||||||
|
|
||||||
def is_authorized(self, request, object=None):
|
|
||||||
return True
|
|
||||||
|
|
||||||
class Users(ExtendedModelResource):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
queryset = models.User.objects.all()
|
|
||||||
resource_name = 'users'
|
|
||||||
authorization = AcomAuthorization()
|
|
||||||
|
|
||||||
class Projects(ExtendedModelResource):
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
queryset = models.Project.objects.all()
|
|
||||||
resource_name = 'projects'
|
|
||||||
authorization = AcomAuthorization()
|
|
||||||
|
|
||||||
#organizations = fields.ToManyField('lib.api.resources.Organizations', 'organizations', help_text='which organizations is this project in?')
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
from django.conf import settings
|
|
||||||
from django.conf.urls import *
|
|
||||||
from tastypie.api import Api
|
|
||||||
from lib.api.resources import *
|
|
||||||
|
|
||||||
v1_api = Api(api_name='v1')
|
|
||||||
v1_api.register(Organizations())
|
|
||||||
v1_api.register(Projects())
|
|
||||||
v1_api.register(Users())
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
|
||||||
(r'', include(v1_api.urls)),
|
|
||||||
)
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# FIXME: as we've switched things over to django-rest-framework from tastypie, this will need some revision
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
|
|||||||
10
lib/main/serializers.py
Normal file
10
lib/main/serializers.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django.contrib.auth.models import User as DjangoUser
|
||||||
|
from lib.main.models import User, Organization, Project
|
||||||
|
from rest_framework import serializers, pagination
|
||||||
|
|
||||||
|
class OrganizationSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Organization
|
||||||
|
fields = ('name', 'description')
|
||||||
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# FIXME: do not use ResourceTestCase
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file demonstrates two different styles of tests (one doctest and one
|
This file demonstrates two different styles of tests (one doctest and one
|
||||||
unittest). These will both pass when you run "manage.py test".
|
unittest). These will both pass when you run "manage.py test".
|
||||||
@@ -11,11 +13,6 @@ import datetime
|
|||||||
from django.contrib.auth.models import User as DjangoUser
|
from django.contrib.auth.models import User as DjangoUser
|
||||||
from tastypie.test import ResourceTestCase
|
from tastypie.test import ResourceTestCase
|
||||||
from lib.main.models import User, Organization, Project
|
from lib.main.models import User, Organization, Project
|
||||||
# from entries.models import Entry
|
|
||||||
|
|
||||||
#class SimpleTest(TestCase):
|
|
||||||
# def test_basic_addition(self):
|
|
||||||
# self.failUnlessEqual(1 + 1, 2)
|
|
||||||
|
|
||||||
class BaseResourceTest(ResourceTestCase):
|
class BaseResourceTest(ResourceTestCase):
|
||||||
|
|
||||||
@@ -32,24 +29,35 @@ class BaseResourceTest(ResourceTestCase):
|
|||||||
acom_user = User.objects.create(name=username, auth_user=django_user)
|
acom_user = User.objects.create(name=username, auth_user=django_user)
|
||||||
return (django_user, acom_user)
|
return (django_user, acom_user)
|
||||||
|
|
||||||
|
def make_organizations(self, count=1):
|
||||||
|
results = []
|
||||||
|
for x in range(0, count):
|
||||||
|
results.append(Organization.objects.create(name="org%s" % x, description="org%s" % x))
|
||||||
|
return results
|
||||||
|
|
||||||
def setup_users(self):
|
def setup_users(self):
|
||||||
# Create a user.
|
# Create a user.
|
||||||
|
|
||||||
self.super_username = 'admin'
|
self.super_username = 'admin'
|
||||||
self.super_password = 'admin'
|
self.super_password = 'admin'
|
||||||
|
|
||||||
self.normal_username = 'normal'
|
self.normal_username = 'normal'
|
||||||
self.normal_password = 'normal'
|
self.normal_password = 'normal'
|
||||||
|
self.other_username = 'other'
|
||||||
|
self.other_password = 'other'
|
||||||
|
|
||||||
(self.super_django_user, self.super_acom_user) = self.make_user(self.super_username, self.super_password, super_user=True)
|
(self.super_django_user, self.super_acom_user) = self.make_user(self.super_username, self.super_password, super_user=True)
|
||||||
(self.normal_django_user, self.normal_acom_user) = self.make_user(self.normal_username, self.normal_password, super_user=False)
|
(self.normal_django_user, self.normal_acom_user) = self.make_user(self.normal_username, self.normal_password, super_user=False)
|
||||||
|
(self.other_django_user, self.other_acom_user) = self.make_user(self.other_username, self.other_password, super_user=False)
|
||||||
|
|
||||||
def get_super_credentials():
|
def get_super_credentials(self):
|
||||||
return self.create_basic(self.super_username, self.super_password)
|
return self.create_basic(self.super_username, self.super_password)
|
||||||
|
|
||||||
def get_normal_credentials(self):
|
def get_normal_credentials(self):
|
||||||
return self.create_basic(self.normal_username, self.normal_password)
|
return self.create_basic(self.normal_username, self.normal_password)
|
||||||
|
|
||||||
|
def get_other_credentials(self):
|
||||||
|
return self.create_basic(self.other_username, self.other_password)
|
||||||
|
|
||||||
def get_invalid_credentials(self):
|
def get_invalid_credentials(self):
|
||||||
return self.create_basic('random', 'combination')
|
return self.create_basic('random', 'combination')
|
||||||
|
|
||||||
@@ -60,16 +68,25 @@ class OrganizationsResourceTest(BaseResourceTest):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(OrganizationsResourceTest, self).setUp()
|
super(OrganizationsResourceTest, self).setUp()
|
||||||
|
self.organizations = self.make_organizations(10)
|
||||||
|
self.a_detail_url = "%s%s" % (self.collection(), self.organizations[0].pk)
|
||||||
|
self.b_detail_url = "%s%s" % (self.collection(), self.organizations[1].pk)
|
||||||
|
self.c_detail_url = "%s%s" % (self.collection(), self.organizations[2].pk)
|
||||||
|
|
||||||
|
# configuration:
|
||||||
|
# admin_user is an admin and regular user in all organizations
|
||||||
|
# other_user is all organizations
|
||||||
|
# normal_user is a user in organization 0, and an admin of organization 1
|
||||||
|
|
||||||
# Fetch the ``Entry`` object we'll use in testing.
|
for x in self.organizations:
|
||||||
# Note that we aren't using PKs because they can change depending
|
# NOTE: superuser does not have to be explicitly added to admin group
|
||||||
# on what other tests are running.
|
# x.admins.add(self.super_acom_user)
|
||||||
#self.entry_1 = Entry.objects.get(slug='first-post')
|
x.users.add(self.super_acom_user)
|
||||||
|
x.users.add(self.other_acom_user)
|
||||||
# We also build a detail URI, since we will be using it all over.
|
|
||||||
# DRY, baby. DRY.
|
self.organizations[0].users.add(self.normal_acom_user)
|
||||||
#self.detail_url = '/api/v1/entry/{0}/'.format(self.entry_1.pk)
|
self.organizations[0].users.add(self.normal_acom_user)
|
||||||
|
self.organizations[1].admins.add(self.normal_acom_user)
|
||||||
|
|
||||||
# The data we'll send on POST requests. Again, because we'll use it
|
# The data we'll send on POST requests. Again, because we'll use it
|
||||||
# frequently (enough).
|
# frequently (enough).
|
||||||
@@ -80,34 +97,94 @@ class OrganizationsResourceTest(BaseResourceTest):
|
|||||||
# 'created': '2012-05-01T22:05:12'
|
# 'created': '2012-05-01T22:05:12'
|
||||||
#}
|
#}
|
||||||
|
|
||||||
|
# TODO: combine this triplet.
|
||||||
def test_get_list_unauthorzied(self):
|
def test_get_list_unauthorzied(self):
|
||||||
|
|
||||||
|
# no credentials == 401
|
||||||
self.assertHttpUnauthorized(self.api_client.get(self.collection(), format='json'))
|
self.assertHttpUnauthorized(self.api_client.get(self.collection(), format='json'))
|
||||||
|
|
||||||
def test_get_list_invalid_authorization(self):
|
# wrong credentials == 401
|
||||||
self.assertHttpUnauthorized(self.api_client.get(self.collection(), format='json', authentication=self.get_invalid_credentials()))
|
self.assertHttpUnauthorized(self.api_client.get(self.collection(), format='json', authentication=self.get_invalid_credentials()))
|
||||||
|
|
||||||
def test_get_list_json(self):
|
# superuser credentials == 200, full list
|
||||||
|
resp = self.api_client.get(self.collection(), format='json', authentication=self.get_super_credentials())
|
||||||
|
self.assertValidJSONResponse(resp)
|
||||||
|
self.assertEqual(len(self.deserialize(resp)['objects']), 10)
|
||||||
|
# check member data
|
||||||
|
first = self.deserialize(resp)['objects'][0]
|
||||||
|
self.assertEqual(first['name'], 'org0')
|
||||||
|
|
||||||
|
# normal credentials == 200, get only organizations that I am actually added to (there are 2)
|
||||||
resp = self.api_client.get(self.collection(), format='json', authentication=self.get_normal_credentials())
|
resp = self.api_client.get(self.collection(), format='json', authentication=self.get_normal_credentials())
|
||||||
self.assertValidJSONResponse(resp)
|
self.assertValidJSONResponse(resp)
|
||||||
|
self.assertEqual(len(self.deserialize(resp)['objects']), 2)
|
||||||
|
|
||||||
# # Scope out the data for correctness.
|
# no admin rights? get empty list
|
||||||
# self.assertEqual(len(self.deserialize(resp)['objects']), 12)
|
resp = self.api_client.get(self.collection(), format='json', authentication=self.get_other_credentials())
|
||||||
# # Here, we're checking an entire structure for the expected data.
|
self.assertValidJSONResponse(resp)
|
||||||
# self.assertEqual(self.deserialize(resp)['objects'][0], {
|
self.assertEqual(len(self.deserialize(resp)['objects']), 0)
|
||||||
# 'pk': str(self.entry_1.pk),
|
|
||||||
# 'user': '/api/v1/user/{0}/'.format(self.user.pk),
|
def test_get_item(self):
|
||||||
# 'title': 'First post',
|
|
||||||
# 'slug': 'first-post',
|
# no credentials == 401
|
||||||
# 'created': '2012-05-01T19:13:42',
|
#self.assertHttpUnauthorized(self.api_client.get(self.a_detail_url, format='json'))
|
||||||
# 'resource_uri': '/api/v1/entry/{0}/'.format(self.entry_1.pk)
|
|
||||||
# })
|
# wrong crendentials == 401
|
||||||
#
|
#self.assertHttpUnauthorized(self.api_client.get(self.c_detail_url, format='json', authentication=self.get_invalid_credentials())
|
||||||
# def test_get_list_xml(self):
|
|
||||||
# self.assertValidXMLResponse(self.api_client.get('/api/v1/entries/', format='xml', authentication=self.get_credentials()))
|
# superuser credentials ==
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_item_subobjects_projects(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_get_item_subobjects_users(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_get_item_subobjects_admins(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_post_item(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_post_item_subobjects_projects(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_post_item_subobjects_users(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_post_item_subobjects_admins(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_put_item(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_put_item_subobjects_projects(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_put_item_subobjects_users(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_put_item_subobjects_admins(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_delete_item(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_delete_item_subobjects_projects(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_delete_item_subobjects_users(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_delete_item_subobjects_admins(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_get_list_xml(self):
|
||||||
|
self.assertValidXMLResponse(self.api_client.get(self.collection(), format='xml', authentication=self.get_normal_credentials()))
|
||||||
#
|
#
|
||||||
# def test_get_detail_unauthenticated(self):
|
# def test_get_detail_unauthenticated(self):
|
||||||
# self.assertHttpUnauthorized(self.api_client.get(self.detail_url, format='json'))
|
|
||||||
#
|
#
|
||||||
# def test_get_detail_json(self):
|
# def test_get_detail_json(self):
|
||||||
# resp = self.api_client.get(self.detail_url, format='json', authentication=self.get_credentials())
|
# resp = self.api_client.get(self.detail_url, format='json', authentication=self.get_credentials())
|
||||||
|
|||||||
@@ -1 +1,117 @@
|
|||||||
# Create your views here.
|
from django.http import HttpResponse
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
#from rest_framework.renderers import JSONRenderer
|
||||||
|
#from rest_framework.parsers import JSONParser
|
||||||
|
from lib.main.models import *
|
||||||
|
from lib.main.serializers import *
|
||||||
|
|
||||||
|
from rest_framework import mixins
|
||||||
|
from rest_framework import generics
|
||||||
|
from rest_framework import permissions
|
||||||
|
from rest_framework import permissions
|
||||||
|
|
||||||
|
# TODO: verify pagination
|
||||||
|
# TODO: how to add relative resources
|
||||||
|
# TODO:
|
||||||
|
|
||||||
|
class CustomRbac(permissions.BasePermission):
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
|
||||||
|
if request.method in permissions.SAFE_METHODS: # GET, HEAD, OPTIONS
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Write permissions are only allowed to the owner of the snippet
|
||||||
|
return obj.owner == request.user
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationsList(generics.ListCreateAPIView):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
model = Organization
|
||||||
|
serializer_class = OrganizationSerializer
|
||||||
|
|
||||||
|
permission_classes = (CustomRbac,)
|
||||||
|
|
||||||
|
#def pre_save(self, obj):
|
||||||
|
# obj.owner = self.request.user
|
||||||
|
|
||||||
|
class OrganizationsDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
model = Organization
|
||||||
|
serializer_class = OrganizationSerializer
|
||||||
|
|
||||||
|
permission_classes = (CustomRbac,)
|
||||||
|
|
||||||
|
#def pre_save(self, obj):
|
||||||
|
# obj.owner = self.request.user
|
||||||
|
|
||||||
|
#class OrganizationsList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.MultipleObjectAPIView):
|
||||||
|
#
|
||||||
|
# model = Organization
|
||||||
|
# serializer_class = OrganizationSerializer
|
||||||
|
#
|
||||||
|
# def get(self, request, *args, **kwargs):
|
||||||
|
# return self.list(request, *args, **kwargs)
|
||||||
|
#
|
||||||
|
# def post(self, request, *args, **kwargs):
|
||||||
|
# return self.create(request, *args, **kwargs)
|
||||||
|
|
||||||
|
#class JSONResponse(HttpResponse):
|
||||||
|
# """
|
||||||
|
# An HttpResponse that renders it's content into JSON.
|
||||||
|
# """
|
||||||
|
# def __init__(self, data, **kwargs):
|
||||||
|
# content = JSONRenderer().render(data)
|
||||||
|
# kwargs['content_type'] = 'application/json'
|
||||||
|
# super(JSONResponse, self).__init__(content, **kwargs)
|
||||||
|
|
||||||
|
#@csrf_exempt
|
||||||
|
#def organizations_list(request):
|
||||||
|
# """
|
||||||
|
# List all code snippets, or create a new snippet.
|
||||||
|
# """
|
||||||
|
# if request.method == 'GET':
|
||||||
|
# # TODO: FILTER
|
||||||
|
# organizations = Organization.objects.all()
|
||||||
|
# serializer = OrganizationSerializer(organizations, many=True)
|
||||||
|
# return JSONResponse(serializer.data)
|
||||||
|
#
|
||||||
|
# elif request.method == 'POST':
|
||||||
|
# data = JSONParser().parse(request)
|
||||||
|
# # TODO: DATA AUDIT
|
||||||
|
# serializer = OrganizationSerializer(data=data)
|
||||||
|
# if serializer.is_valid():
|
||||||
|
# serializer.save()
|
||||||
|
# return JSONResponse(serializer.data, status=201)
|
||||||
|
# else:
|
||||||
|
# return JSONResponse(serializer.errors, status=400)
|
||||||
|
|
||||||
|
#@csrf_exempt
|
||||||
|
#def snippet_detail(request, pk):
|
||||||
|
# """
|
||||||
|
# Retrieve, update or delete a code snippet.
|
||||||
|
# """
|
||||||
|
# try:
|
||||||
|
# snippet = Snippet.objects.get(pk=pk)
|
||||||
|
# except Snippet.DoesNotExist:
|
||||||
|
# return HttpResponse(status=404)
|
||||||
|
#
|
||||||
|
# if request.method == 'GET':
|
||||||
|
# serializer = SnippetSerializer(snippet)
|
||||||
|
# return JSONResponse(serializer.data)
|
||||||
|
#
|
||||||
|
# elif request.method == 'PUT':
|
||||||
|
# data = JSONParser().parse(request)
|
||||||
|
# serializer = SnippetSerializer(snippet, data=data)
|
||||||
|
# if serializer.is_valid():
|
||||||
|
# serializer.save()
|
||||||
|
# return JSONResponse(serializer.data)
|
||||||
|
# else:
|
||||||
|
# return JSONResponse(serializer.errors, status=400)
|
||||||
|
#
|
||||||
|
# elif request.method == 'DELETE':
|
||||||
|
# snippet.delete()
|
||||||
|
# return HttpResponse(status=204)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ ADMINS = (
|
|||||||
|
|
||||||
MANAGERS = ADMINS
|
MANAGERS = ADMINS
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'PAGINATE_BY': 10,
|
||||||
|
'PAGINATE_BY_PARAM': 'page_size'
|
||||||
|
}
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
@@ -116,12 +121,11 @@ INSTALLED_APPS = (
|
|||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'lib.main',
|
'lib.main',
|
||||||
'lib.api',
|
|
||||||
'lib.web',
|
'lib.web',
|
||||||
'south',
|
'south',
|
||||||
# not yet compatible with Django 1.5 unless using version from github
|
# not yet compatible with Django 1.5 unless using version from github
|
||||||
# 'devserver',
|
# 'devserver',
|
||||||
'tastypie',
|
'rest_framework',
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
'djcelery',
|
'djcelery',
|
||||||
'kombu.transport.django',
|
'kombu.transport.django',
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import *
|
from django.conf.urls import *
|
||||||
|
import lib.main.views as views
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'', include('lib.web.urls')),
|
url(r'', include('lib.web.urls')),
|
||||||
url(r'^api/', include('lib.api.urls')),
|
url(r'^api/v1/organizations/$', views.OrganizationsList.as_view()),
|
||||||
|
url(r'^api/v1/organizations/(?P<pk>[0-9]+)/$', views.OrganizationsDetail.as_view()),
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'django.contrib.admin' in settings.INSTALLED_APPS:
|
if 'django.contrib.admin' in settings.INSTALLED_APPS:
|
||||||
|
|||||||
620
lib/vendor/extendedmodelresource.py
vendored
620
lib/vendor/extendedmodelresource.py
vendored
@@ -1,620 +0,0 @@
|
|||||||
# modified version of https://github.com/tryolabs/django-tastypie-extendedmodelresource
|
|
||||||
# from PyPi, tweaked to make it work with latest tastypie
|
|
||||||
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
|
||||||
from django.core.urlresolvers import get_script_prefix, resolve, Resolver404
|
|
||||||
from django.conf.urls.defaults import patterns, url, include
|
|
||||||
|
|
||||||
from tastypie import fields, http
|
|
||||||
from tastypie.exceptions import NotFound, ImmediateHttpResponse
|
|
||||||
from tastypie.resources import ResourceOptions, ModelDeclarativeMetaclass, \
|
|
||||||
ModelResource, convert_post_to_put
|
|
||||||
from tastypie.utils import trailing_slash
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendedDeclarativeMetaclass(ModelDeclarativeMetaclass):
|
|
||||||
"""
|
|
||||||
Same as ``DeclarativeMetaclass`` but uses ``AnyIdAttributeResourceOptions``
|
|
||||||
instead of ``ResourceOptions`` and adds support for multiple nested fields
|
|
||||||
defined in a "Nested" class (the same way as "Meta") inside the resources.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __new__(cls, name, bases, attrs):
|
|
||||||
new_class = super(ExtendedDeclarativeMetaclass, cls).__new__(cls,
|
|
||||||
name, bases, attrs)
|
|
||||||
|
|
||||||
opts = getattr(new_class, 'Meta', None)
|
|
||||||
new_class._meta = ResourceOptions(opts)
|
|
||||||
|
|
||||||
# Will map nested fields names to the actual fields
|
|
||||||
nested_fields = {}
|
|
||||||
|
|
||||||
nested_class = getattr(new_class, 'Nested', None)
|
|
||||||
if nested_class is not None:
|
|
||||||
for field_name in dir(nested_class):
|
|
||||||
if not field_name.startswith('_'): # No internals
|
|
||||||
field_object = getattr(nested_class, field_name)
|
|
||||||
|
|
||||||
nested_fields[field_name] = field_object
|
|
||||||
if hasattr(field_object, 'contribute_to_class'):
|
|
||||||
field_object.contribute_to_class(new_class,
|
|
||||||
field_name)
|
|
||||||
|
|
||||||
new_class._nested = nested_fields
|
|
||||||
|
|
||||||
return new_class
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendedModelResource(ModelResource):
|
|
||||||
|
|
||||||
__metaclass__ = ExtendedDeclarativeMetaclass
|
|
||||||
|
|
||||||
def remove_api_resource_names(self, url_dict):
|
|
||||||
"""
|
|
||||||
Given a dictionary of regex matches from a URLconf, removes
|
|
||||||
``api_name`` and/or ``resource_name`` if found.
|
|
||||||
|
|
||||||
This is useful for converting URLconf matches into something suitable
|
|
||||||
for data lookup. For example::
|
|
||||||
|
|
||||||
Model.objects.filter(**self.remove_api_resource_names(matches))
|
|
||||||
"""
|
|
||||||
kwargs_subset = url_dict.copy()
|
|
||||||
|
|
||||||
for key in ['api_name', 'resource_name', 'related_manager',
|
|
||||||
'child_object', 'parent_resource', 'nested_name',
|
|
||||||
'parent_object']:
|
|
||||||
try:
|
|
||||||
del(kwargs_subset[key])
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return kwargs_subset
|
|
||||||
|
|
||||||
def get_detail_uri_name_regex(self):
|
|
||||||
"""
|
|
||||||
Return the regular expression to which the id attribute used in
|
|
||||||
resource URLs should match.
|
|
||||||
|
|
||||||
By default we admit any alphanumeric value and "-", but you may
|
|
||||||
override this function and provide your own.
|
|
||||||
"""
|
|
||||||
return r'\w[\w-]*'
|
|
||||||
|
|
||||||
def base_urls(self):
|
|
||||||
"""
|
|
||||||
Same as the original ``base_urls`` but supports using the custom
|
|
||||||
regex for the ``detail_uri_name`` attribute of the objects.
|
|
||||||
"""
|
|
||||||
# Due to the way Django parses URLs, ``get_multiple``
|
|
||||||
# won't work without a trailing slash.
|
|
||||||
return [
|
|
||||||
url(r"^(?P<resource_name>%s)%s$" %
|
|
||||||
(self._meta.resource_name, trailing_slash()),
|
|
||||||
self.wrap_view('dispatch_list'),
|
|
||||||
name="api_dispatch_list"),
|
|
||||||
url(r"^(?P<resource_name>%s)/schema%s$" %
|
|
||||||
(self._meta.resource_name, trailing_slash()),
|
|
||||||
self.wrap_view('get_schema'),
|
|
||||||
name="api_get_schema"),
|
|
||||||
url(r"^(?P<resource_name>%s)/set/(?P<%s_list>(%s;?)*)/$" %
|
|
||||||
(self._meta.resource_name,
|
|
||||||
self._meta.detail_uri_name,
|
|
||||||
self.get_detail_uri_name_regex()),
|
|
||||||
self.wrap_view('get_multiple'),
|
|
||||||
name="api_get_multiple"),
|
|
||||||
url(r"^(?P<resource_name>%s)/(?P<%s>%s)%s$" %
|
|
||||||
(self._meta.resource_name,
|
|
||||||
self._meta.detail_uri_name,
|
|
||||||
self.get_detail_uri_name_regex(),
|
|
||||||
trailing_slash()),
|
|
||||||
self.wrap_view('dispatch_detail'),
|
|
||||||
name="api_dispatch_detail"),
|
|
||||||
]
|
|
||||||
|
|
||||||
def nested_urls(self):
|
|
||||||
"""
|
|
||||||
Return the list of all urls nested under the detail view of a resource.
|
|
||||||
|
|
||||||
Each resource listed as Nested will generate one url.
|
|
||||||
"""
|
|
||||||
def get_nested_url(nested_name):
|
|
||||||
return url(r"^(?P<resource_name>%s)/(?P<%s>%s)/"
|
|
||||||
r"(?P<nested_name>%s)%s$" %
|
|
||||||
(self._meta.resource_name,
|
|
||||||
self._meta.detail_uri_name,
|
|
||||||
self.get_detail_uri_name_regex(),
|
|
||||||
nested_name,
|
|
||||||
trailing_slash()),
|
|
||||||
self.wrap_view('dispatch_nested'),
|
|
||||||
name='api_dispatch_nested')
|
|
||||||
|
|
||||||
return [get_nested_url(nested_name)
|
|
||||||
for nested_name in self._nested.keys()]
|
|
||||||
|
|
||||||
def detail_actions(self):
|
|
||||||
"""
|
|
||||||
Return urls of custom actions to be performed on the detail view of a
|
|
||||||
resource. These urls will be appended to the url of the detail view.
|
|
||||||
This allows a finer control by providing a custom view for each of
|
|
||||||
these actions in the resource.
|
|
||||||
|
|
||||||
A resource should override this method and provide its own list of
|
|
||||||
detail actions urls, if needed.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
return [
|
|
||||||
url(r"^show_schema/$", self.wrap_view('get_schema'),
|
|
||||||
name="api_get_schema")
|
|
||||||
]
|
|
||||||
|
|
||||||
will add show schema capabilities to a detail resource URI (ie.
|
|
||||||
/api/user/3/show_schema/ will work just like /api/user/schema/).
|
|
||||||
"""
|
|
||||||
return []
|
|
||||||
|
|
||||||
def detail_actions_urlpatterns(self):
|
|
||||||
"""
|
|
||||||
Return the url patterns corresponding to the detail actions available
|
|
||||||
on this resource.
|
|
||||||
"""
|
|
||||||
if self.detail_actions():
|
|
||||||
detail_url = "^(?P<resource_name>%s)/(?P<%s>%s)/" % (
|
|
||||||
self._meta.resource_name,
|
|
||||||
self._meta.detail_uri_name,
|
|
||||||
self.get_detail_uri_name_regex()
|
|
||||||
)
|
|
||||||
return patterns('', (detail_url, include(self.detail_actions())))
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def urls(self):
|
|
||||||
"""
|
|
||||||
The endpoints this ``Resource`` responds to.
|
|
||||||
|
|
||||||
Same as the original ``urls`` attribute but supports nested urls as
|
|
||||||
well as detail actions urls.
|
|
||||||
"""
|
|
||||||
urls = self.override_urls() + self.base_urls() + self.nested_urls()
|
|
||||||
return patterns('', *urls) + self.detail_actions_urlpatterns()
|
|
||||||
|
|
||||||
def is_authorized_over_parent(self, request, parent_object):
|
|
||||||
"""
|
|
||||||
Allows the ``Authorization`` class to check if a request to a nested
|
|
||||||
resource has permissions over the parent.
|
|
||||||
|
|
||||||
Will call the ``is_authorized_parent`` function of the
|
|
||||||
``Authorization`` class.
|
|
||||||
"""
|
|
||||||
if hasattr(self._meta.authorization, 'is_authorized_parent'):
|
|
||||||
return self._meta.authorization.is_authorized_parent(request,
|
|
||||||
parent_object)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def parent_obj_get(self, request=None, **kwargs):
|
|
||||||
"""
|
|
||||||
Same as the original ``obj_get`` but called when a nested resource
|
|
||||||
wants to get its parent.
|
|
||||||
|
|
||||||
Will check authorization to see if the request is allowed to act on
|
|
||||||
the parent resource.
|
|
||||||
"""
|
|
||||||
parent_object = self.get_object_list(request).get(**kwargs)
|
|
||||||
|
|
||||||
# If I am not authorized for the parent
|
|
||||||
if not self.is_authorized_over_parent(request, parent_object):
|
|
||||||
stringified_kwargs = ', '.join(["%s=%s" % (k, v)
|
|
||||||
for k, v in kwargs.items()])
|
|
||||||
raise self._meta.object_class.DoesNotExist("Couldn't find an "
|
|
||||||
"instance of '%s' which matched '%s'." %
|
|
||||||
(self._meta.object_class.__name__, stringified_kwargs))
|
|
||||||
|
|
||||||
return parent_object
|
|
||||||
|
|
||||||
def parent_cached_obj_get(self, request=None, **kwargs):
|
|
||||||
"""
|
|
||||||
Same as the original ``cached_obj_get`` but called when a nested
|
|
||||||
resource wants to get its parent.
|
|
||||||
"""
|
|
||||||
cache_key = self.generate_cache_key('detail', **kwargs)
|
|
||||||
bundle = self._meta.cache.get(cache_key)
|
|
||||||
|
|
||||||
if bundle is None:
|
|
||||||
bundle = self.parent_obj_get(request=request, **kwargs)
|
|
||||||
self._meta.cache.set(cache_key, bundle)
|
|
||||||
|
|
||||||
return bundle
|
|
||||||
|
|
||||||
def get_via_uri_resolver(self, uri):
|
|
||||||
"""
|
|
||||||
Do the work of the original ``get_via_uri`` except calling ``obj_get``.
|
|
||||||
|
|
||||||
Use this as a helper function.
|
|
||||||
"""
|
|
||||||
prefix = get_script_prefix()
|
|
||||||
chomped_uri = uri
|
|
||||||
|
|
||||||
if prefix and chomped_uri.startswith(prefix):
|
|
||||||
chomped_uri = chomped_uri[len(prefix) - 1:]
|
|
||||||
|
|
||||||
try:
|
|
||||||
_view, _args, kwargs = resolve(chomped_uri)
|
|
||||||
except Resolver404:
|
|
||||||
raise NotFound("The URL provided '%s' was not a link to a valid "
|
|
||||||
"resource." % uri)
|
|
||||||
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
def get_nested_via_uri(self, uri, parent_resource,
|
|
||||||
parent_object, nested_name, request=None):
|
|
||||||
"""
|
|
||||||
Obtain a nested resource from an uri, a parent resource and a parent
|
|
||||||
object.
|
|
||||||
|
|
||||||
Calls ``obj_get`` which handles the authorization checks.
|
|
||||||
"""
|
|
||||||
# TODO: improve this to get parent resource & object from uri too?
|
|
||||||
kwargs = self.get_via_uri_resolver(uri)
|
|
||||||
return self.obj_get(nested_name=nested_name,
|
|
||||||
parent_resource=parent_resource,
|
|
||||||
parent_object=parent_object,
|
|
||||||
request=request,
|
|
||||||
**self.remove_api_resource_names(kwargs))
|
|
||||||
|
|
||||||
def get_via_uri_no_auth_check(self, uri, request=None):
|
|
||||||
"""
|
|
||||||
Obtain a nested resource from an uri, a parent resource and a
|
|
||||||
parent object.
|
|
||||||
|
|
||||||
Does *not* do authorization checks, those must be performed manually.
|
|
||||||
This function is useful be called from custom views over a resource
|
|
||||||
which need access to objects and can do the check of permissions
|
|
||||||
theirselves.
|
|
||||||
"""
|
|
||||||
kwargs = self.get_via_uri_resolver(uri)
|
|
||||||
return self.obj_get_no_auth_check(request=request,
|
|
||||||
**self.remove_api_resource_names(kwargs))
|
|
||||||
|
|
||||||
def obj_get(self, request=None, **kwargs):
|
|
||||||
"""
|
|
||||||
Same as the original ``obj_get`` but knows when it is being called to
|
|
||||||
get an object from a nested resource uri.
|
|
||||||
|
|
||||||
Performs authorization checks in every case.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
nested_name = kwargs.pop('nested_name', None)
|
|
||||||
parent_resource = kwargs.pop('parent_resource', None)
|
|
||||||
parent_object = kwargs.pop('parent_object', None)
|
|
||||||
bundle = kwargs.pop('bundle', None) # MPD: fixup
|
|
||||||
|
|
||||||
base_object_list = self.get_object_list(request).filter(**kwargs)
|
|
||||||
|
|
||||||
if nested_name is not None:
|
|
||||||
# TODO: throw exception if parent_resource or parent_object are
|
|
||||||
# None
|
|
||||||
object_list = self.apply_nested_authorization_limits(request,
|
|
||||||
nested_name, parent_resource,
|
|
||||||
parent_object, base_object_list)
|
|
||||||
else:
|
|
||||||
object_list = self.apply_authorization_limits(request,
|
|
||||||
base_object_list)
|
|
||||||
|
|
||||||
stringified_kwargs = ', '.join(["%s=%s" % (k, v)
|
|
||||||
for k, v in kwargs.items()])
|
|
||||||
|
|
||||||
if len(object_list) <= 0:
|
|
||||||
raise self._meta.object_class.DoesNotExist("Couldn't find an "
|
|
||||||
"instance of '%s' which matched '%s'." %
|
|
||||||
(self._meta.object_class.__name__,
|
|
||||||
stringified_kwargs))
|
|
||||||
elif len(object_list) > 1:
|
|
||||||
raise MultipleObjectsReturned("More than '%s' matched '%s'." %
|
|
||||||
(self._meta.object_class.__name__, stringified_kwargs))
|
|
||||||
|
|
||||||
return object_list[0]
|
|
||||||
except ValueError:
|
|
||||||
raise NotFound("Invalid resource lookup data provided (mismatched "
|
|
||||||
"type).")
|
|
||||||
|
|
||||||
def obj_get_no_auth_check(self, request=None, **kwargs):
|
|
||||||
"""
|
|
||||||
Same as the original ``obj_get`` knows when it is being called to get
|
|
||||||
a nested resource.
|
|
||||||
|
|
||||||
Does *not* do authorization checks.
|
|
||||||
"""
|
|
||||||
# TODO: merge this and original obj_get and use another argument in
|
|
||||||
# kwargs to know if we should check for auth?
|
|
||||||
try:
|
|
||||||
object_list = self.get_object_list(request).filter(**kwargs)
|
|
||||||
stringified_kwargs = ', '.join(["%s=%s" % (k, v)
|
|
||||||
for k, v in kwargs.items()])
|
|
||||||
|
|
||||||
if len(object_list) <= 0:
|
|
||||||
raise self._meta.object_class.DoesNotExist("Couldn't find an "
|
|
||||||
"instance of '%s' which matched '%s'." %
|
|
||||||
(self._meta.object_class.__name__,
|
|
||||||
stringified_kwargs))
|
|
||||||
elif len(object_list) > 1:
|
|
||||||
raise MultipleObjectsReturned("More than '%s' matched '%s'." %
|
|
||||||
(self._meta.object_class.__name__, stringified_kwargs))
|
|
||||||
|
|
||||||
return object_list[0]
|
|
||||||
except ValueError:
|
|
||||||
raise NotFound("Invalid resource lookup data provided (mismatched "
|
|
||||||
"type).")
|
|
||||||
|
|
||||||
def apply_nested_authorization_limits(self, request, nested_name,
|
|
||||||
parent_resource, parent_object,
|
|
||||||
object_list):
|
|
||||||
"""
|
|
||||||
Allows the ``Authorization`` class to further limit the object list.
|
|
||||||
Also a hook to customize per ``Resource``.
|
|
||||||
"""
|
|
||||||
method_name = 'apply_limits_nested_%s' % nested_name
|
|
||||||
if hasattr(parent_resource._meta.authorization, method_name):
|
|
||||||
method = getattr(parent_resource._meta.authorization, method_name)
|
|
||||||
object_list = method(request, parent_object, object_list)
|
|
||||||
|
|
||||||
return object_list
|
|
||||||
|
|
||||||
def dispatch_nested(self, request, **kwargs):
|
|
||||||
"""
|
|
||||||
Dispatch a request to the nested resource.
|
|
||||||
"""
|
|
||||||
# We don't check for is_authorized here since it will be
|
|
||||||
# parent_cached_obj_get which will check that we have permissions
|
|
||||||
# over the parent.
|
|
||||||
self.is_authenticated(request)
|
|
||||||
self.throttle_check(request)
|
|
||||||
|
|
||||||
nested_name = kwargs.pop('nested_name')
|
|
||||||
nested_field = self._nested[nested_name]
|
|
||||||
|
|
||||||
try:
|
|
||||||
obj = self.parent_cached_obj_get(request=request,
|
|
||||||
**self.remove_api_resource_names(kwargs))
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return http.HttpNotFound()
|
|
||||||
except MultipleObjectsReturned:
|
|
||||||
return http.HttpMultipleChoices("More than one parent resource is "
|
|
||||||
"found at this URI.")
|
|
||||||
|
|
||||||
# The nested resource needs to get the api_name from its parent because
|
|
||||||
# it is possible that the resource being used as nested is not
|
|
||||||
# registered in the API (ie. it can only be used as nested)
|
|
||||||
nested_resource = nested_field.to_class()
|
|
||||||
nested_resource._meta.api_name = self._meta.api_name
|
|
||||||
|
|
||||||
# TODO: comment further to make sense of this block
|
|
||||||
manager = None
|
|
||||||
try:
|
|
||||||
if isinstance(nested_field.attribute, basestring):
|
|
||||||
name = nested_field.attribute
|
|
||||||
manager = getattr(obj, name, None)
|
|
||||||
elif callable(nested_field.attribute):
|
|
||||||
manager = nested_field.attribute(obj)
|
|
||||||
else:
|
|
||||||
raise fields.ApiFieldError(
|
|
||||||
"The model '%r' has an empty attribute '%s' \
|
|
||||||
and doesn't allow a null value." % (
|
|
||||||
obj,
|
|
||||||
nested_field.attribute
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
kwargs['nested_name'] = nested_name
|
|
||||||
kwargs['parent_resource'] = self
|
|
||||||
kwargs['parent_object'] = obj
|
|
||||||
|
|
||||||
if manager is None or not hasattr(manager, 'all'):
|
|
||||||
dispatch_type = 'detail'
|
|
||||||
kwargs['child_object'] = manager
|
|
||||||
else:
|
|
||||||
dispatch_type = 'list'
|
|
||||||
kwargs['related_manager'] = manager
|
|
||||||
|
|
||||||
return nested_resource.dispatch(
|
|
||||||
dispatch_type,
|
|
||||||
request,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
# MPD: fixup upstream module
|
|
||||||
def is_authorized(self, request):
|
|
||||||
auth = getattr(self._meta, 'authorization')
|
|
||||||
if auth is not None:
|
|
||||||
return auth.is_authorized(request)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_authorized_nested(self, request, nested_name,
|
|
||||||
parent_resource, parent_object, object=None):
|
|
||||||
"""
|
|
||||||
Handles checking of permissions to see if the user has authorization
|
|
||||||
to GET, POST, PUT, or DELETE this resource. If ``object`` is provided,
|
|
||||||
the authorization backend can apply additional row-level permissions
|
|
||||||
checking.
|
|
||||||
"""
|
|
||||||
# We use the authorization of the parent resource
|
|
||||||
method_name = 'is_authorized_nested_%s' % nested_name
|
|
||||||
if hasattr(parent_resource._meta.authorization, method_name):
|
|
||||||
method = getattr(parent_resource._meta.authorization, method_name)
|
|
||||||
auth_result = method(request, parent_object, object)
|
|
||||||
|
|
||||||
if isinstance(auth_result, HttpResponse):
|
|
||||||
raise ImmediateHttpResponse(response=auth_result)
|
|
||||||
|
|
||||||
if not auth_result is True:
|
|
||||||
raise ImmediateHttpResponse(response=http.HttpUnauthorized())
|
|
||||||
|
|
||||||
def dispatch(self, request_type, request, **kwargs):
|
|
||||||
"""
|
|
||||||
Same as the usual dispatch, but knows if its being called from a nested
|
|
||||||
resource.
|
|
||||||
"""
|
|
||||||
allowed_methods = getattr(self._meta,
|
|
||||||
"%s_allowed_methods" % request_type, None)
|
|
||||||
request_method = self.method_check(request, allowed=allowed_methods)
|
|
||||||
|
|
||||||
method = getattr(self, "%s_%s" % (request_method, request_type), None)
|
|
||||||
|
|
||||||
if method is None:
|
|
||||||
raise ImmediateHttpResponse(response=http.HttpNotImplemented())
|
|
||||||
|
|
||||||
self.is_authenticated(request)
|
|
||||||
self.throttle_check(request)
|
|
||||||
|
|
||||||
nested_name = kwargs.get('nested_name', None)
|
|
||||||
parent_resource = kwargs.get('parent_resource', None)
|
|
||||||
parent_object = kwargs.get('parent_object', None)
|
|
||||||
if nested_name is None:
|
|
||||||
self.is_authorized(request)
|
|
||||||
else:
|
|
||||||
self.is_authorized_nested(request, nested_name,
|
|
||||||
parent_resource,
|
|
||||||
parent_object)
|
|
||||||
|
|
||||||
# All clear. Process the request.
|
|
||||||
request = convert_post_to_put(request)
|
|
||||||
# MPD: fixup
|
|
||||||
response = method(request, **kwargs)
|
|
||||||
|
|
||||||
# Add the throttled request.
|
|
||||||
self.log_throttled_access(request)
|
|
||||||
|
|
||||||
# If what comes back isn't a ``HttpResponse``, assume that the
|
|
||||||
# request was accepted and that some action occurred. This also
|
|
||||||
# prevents Django from freaking out.
|
|
||||||
if not isinstance(response, HttpResponse):
|
|
||||||
return http.HttpNoContent()
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def obj_create(self, bundle, request=None, **kwargs):
|
|
||||||
related_manager = kwargs.pop('related_manager', None)
|
|
||||||
# Remove the other parameters used for the nested resources, if they
|
|
||||||
# are present.
|
|
||||||
kwargs.pop('nested_name', None)
|
|
||||||
kwargs.pop('parent_resource', None)
|
|
||||||
kwargs.pop('parent_object', None)
|
|
||||||
|
|
||||||
bundle.obj = self._meta.object_class()
|
|
||||||
|
|
||||||
for key, value in kwargs.items():
|
|
||||||
setattr(bundle.obj, key, value)
|
|
||||||
|
|
||||||
bundle = self.full_hydrate(bundle)
|
|
||||||
|
|
||||||
# Save FKs just in case.
|
|
||||||
self.save_related(bundle)
|
|
||||||
|
|
||||||
if related_manager is not None:
|
|
||||||
related_manager.add(bundle.obj)
|
|
||||||
|
|
||||||
# Save the main object.
|
|
||||||
bundle.obj.save()
|
|
||||||
|
|
||||||
# Now pick up the M2M bits.
|
|
||||||
m2m_bundle = self.hydrate_m2m(bundle)
|
|
||||||
self.save_m2m(m2m_bundle)
|
|
||||||
return bundle
|
|
||||||
|
|
||||||
def get_list(self, request, **kwargs):
|
|
||||||
"""
|
|
||||||
Returns a serialized list of resources.
|
|
||||||
|
|
||||||
Calls ``obj_get_list`` to provide the data, then handles that result
|
|
||||||
set and serializes it.
|
|
||||||
|
|
||||||
Should return a HttpResponse (200 OK).
|
|
||||||
"""
|
|
||||||
if 'related_manager' in kwargs:
|
|
||||||
manager = kwargs.pop('related_manager')
|
|
||||||
base_objects = manager.all()
|
|
||||||
|
|
||||||
nested_name = kwargs.pop('nested_name', None)
|
|
||||||
parent_resource = kwargs.pop('parent_resource', None)
|
|
||||||
parent_object = kwargs.pop('parent_object', None)
|
|
||||||
|
|
||||||
objects = self.apply_nested_authorization_limits(request,
|
|
||||||
nested_name, parent_resource, parent_object,
|
|
||||||
base_objects)
|
|
||||||
else:
|
|
||||||
|
|
||||||
# MPD: fixup compat with tastypie
|
|
||||||
basic_bundle = self.build_bundle(request=request)
|
|
||||||
|
|
||||||
objects = self.obj_get_list(
|
|
||||||
basic_bundle, # WAS: request=request,
|
|
||||||
**self.remove_api_resource_names(kwargs)
|
|
||||||
)
|
|
||||||
|
|
||||||
sorted_objects = self.apply_sorting(objects, options=request.GET)
|
|
||||||
|
|
||||||
paginator = self._meta.paginator_class(
|
|
||||||
request.GET, sorted_objects,
|
|
||||||
resource_uri=self.get_resource_uri(),
|
|
||||||
limit=self._meta.limit,
|
|
||||||
max_limit=self._meta.max_limit,
|
|
||||||
collection_name=self._meta.collection_name
|
|
||||||
)
|
|
||||||
|
|
||||||
to_be_serialized = paginator.page()
|
|
||||||
|
|
||||||
# Dehydrate the bundles in preparation for serialization.
|
|
||||||
bundles = []
|
|
||||||
for obj in to_be_serialized['objects']:
|
|
||||||
bundles.append(
|
|
||||||
self.full_dehydrate(
|
|
||||||
self.build_bundle(obj=obj, request=request)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
to_be_serialized['objects'] = bundles
|
|
||||||
to_be_serialized = self.alter_list_data_to_serialize(request,
|
|
||||||
to_be_serialized)
|
|
||||||
return self.create_response(request, to_be_serialized)
|
|
||||||
|
|
||||||
def get_detail(self, request, **kwargs):
|
|
||||||
"""
|
|
||||||
Returns a single serialized resource.
|
|
||||||
|
|
||||||
Calls ``cached_obj_get/obj_get`` to provide the data, then handles that
|
|
||||||
result set and serializes it.
|
|
||||||
|
|
||||||
Should return a HttpResponse (200 OK).
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# If call was made through Nested we should already have the
|
|
||||||
# child object.
|
|
||||||
if 'child_object' in kwargs:
|
|
||||||
obj = kwargs.pop('child_object', None)
|
|
||||||
if obj is None:
|
|
||||||
return http.HttpNotFound()
|
|
||||||
else:
|
|
||||||
# MPD: fixed up
|
|
||||||
basic_bundle = self.build_bundle(request=request)
|
|
||||||
# MPD: fixup
|
|
||||||
if 'bundle' in kwargs:
|
|
||||||
kwargs.pop('bundle')
|
|
||||||
obj = self.cached_obj_get(basic_bundle, **self.remove_api_resource_names(kwargs))
|
|
||||||
except AttributeError:
|
|
||||||
return http.HttpNotFound()
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return http.HttpNotFound()
|
|
||||||
except MultipleObjectsReturned:
|
|
||||||
return http.HttpMultipleChoices("More than one resource is found "
|
|
||||||
"at this URI.")
|
|
||||||
|
|
||||||
bundle = self.build_bundle(obj=obj, request=request)
|
|
||||||
bundle = self.full_dehydrate(bundle)
|
|
||||||
bundle = self.alter_detail_data_to_serialize(request, bundle)
|
|
||||||
return self.create_response(request, bundle)
|
|
||||||
|
|
||||||
@@ -3,8 +3,10 @@ django-celery==3.0.11
|
|||||||
django-devserver==0.4.0
|
django-devserver==0.4.0
|
||||||
django-extensions==1.1.1
|
django-extensions==1.1.1
|
||||||
django-jsonfield==0.9.2
|
django-jsonfield==0.9.2
|
||||||
django-tastypie==0.9.12
|
|
||||||
ipython==0.13.1
|
ipython==0.13.1
|
||||||
South==0.7.6
|
South==0.7.6
|
||||||
python-dateutil==1.5
|
python-dateutil==1.5
|
||||||
requests
|
requests
|
||||||
|
djangorestframework
|
||||||
|
markdown
|
||||||
|
django-filter
|
||||||
|
|||||||
Reference in New Issue
Block a user