A massive amount of work upgrading the API to Tastypie >= 0.9.14 with new authorization schema.
parent
fb0142f249
commit
e669c5f25c
|
@ -1,4 +1,4 @@
|
||||||
from django.conf.urls.defaults import *
|
from django.conf.urls import *
|
||||||
|
|
||||||
from accounts import views
|
from accounts import views
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.conf.urls.defaults import *
|
from django.conf.urls import *
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^$', views.blog, name='blog'),
|
url(r'^$', views.blog, name='blog'),
|
||||||
|
|
|
@ -151,9 +151,15 @@ SESSION_COOKIE_AGE = 15801100
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': False,
|
'disable_existing_loggers': False,
|
||||||
|
'filters': {
|
||||||
|
'require_debug_false': {
|
||||||
|
'()': 'django.utils.log.RequireDebugFalse'
|
||||||
|
}
|
||||||
|
},
|
||||||
'handlers': {
|
'handlers': {
|
||||||
'mail_admins': {
|
'mail_admins': {
|
||||||
'level': 'ERROR',
|
'level': 'ERROR',
|
||||||
|
'filters': ['require_debug_false'],
|
||||||
'class': 'django.utils.log.AdminEmailHandler'
|
'class': 'django.utils.log.AdminEmailHandler'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
162
snipts/api.py
162
snipts/api.py
|
@ -3,6 +3,7 @@ from tastypie.authentication import ApiKeyAuthentication
|
||||||
from tastypie.authorization import Authorization
|
from tastypie.authorization import Authorization
|
||||||
from django.template.defaultfilters import date, urlize, linebreaksbr
|
from django.template.defaultfilters import date, urlize, linebreaksbr
|
||||||
from tastypie.resources import ModelResource
|
from tastypie.resources import ModelResource
|
||||||
|
from tastypie.exceptions import Unauthorized
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from tastypie.validation import Validation
|
from tastypie.validation import Validation
|
||||||
from tastypie.models import create_api_key
|
from tastypie.models import create_api_key
|
||||||
|
@ -22,8 +23,137 @@ import parsedatetime.parsedatetime as pdt
|
||||||
models.signals.post_save.connect(create_api_key, sender=User)
|
models.signals.post_save.connect(create_api_key, sender=User)
|
||||||
|
|
||||||
|
|
||||||
|
class PrivateFavoriteAuthorization(Authorization):
|
||||||
|
def read_list(self, object_list, bundle):
|
||||||
|
return object_list.filter(user=bundle.request.user)
|
||||||
|
|
||||||
|
def read_detail(self, object_list, bundle):
|
||||||
|
return bundle.obj.user == bundle.request.user
|
||||||
|
|
||||||
|
def create_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def create_detail(self, object_list, bundle):
|
||||||
|
return bundle.obj.user == bundle.request.user
|
||||||
|
|
||||||
|
def update_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def update_detail(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def delete_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def delete_detail(self, object_list, bundle):
|
||||||
|
return bundle.obj.user == bundle.request.user
|
||||||
|
|
||||||
|
class PrivateSniptAuthorization(Authorization):
|
||||||
|
def read_list(self, object_list, bundle):
|
||||||
|
return object_list.filter(user=bundle.request.user)
|
||||||
|
|
||||||
|
def read_detail(self, object_list, bundle):
|
||||||
|
return bundle.obj.user == bundle.request.user
|
||||||
|
|
||||||
|
def create_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def create_detail(self, object_list, bundle):
|
||||||
|
return bundle.obj.user == bundle.request.user
|
||||||
|
|
||||||
|
def update_list(self, object_list, bundle):
|
||||||
|
return object_list.filter(user=bundle.request.user)
|
||||||
|
|
||||||
|
def update_detail(self, object_list, bundle):
|
||||||
|
return bundle.obj.user == bundle.request.user
|
||||||
|
|
||||||
|
def delete_list(self, object_list, bundle):
|
||||||
|
return object_list.filter(user=bundle.request.user)
|
||||||
|
|
||||||
|
def delete_detail(self, object_list, bundle):
|
||||||
|
return bundle.obj.user == bundle.request.user
|
||||||
|
|
||||||
|
class PrivateTagAuthorization(Authorization):
|
||||||
|
def read_list(self, object_list, bundle):
|
||||||
|
object_list = object_list.filter(snipt__user=bundle.request.user)
|
||||||
|
object_list = object_list.annotate(count=models.Count('taggit_taggeditem_items__id'))
|
||||||
|
object_list = object_list.order_by('-count', 'name')
|
||||||
|
return object_list
|
||||||
|
|
||||||
|
def read_detail(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def create_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def create_detail(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def update_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def update_detail(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def delete_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def delete_detail(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
class PrivateUserProfileAuthorization(Authorization):
|
||||||
|
def read_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def read_detail(self, object_list, bundle):
|
||||||
|
return bundle.obj.user == bundle.request.user
|
||||||
|
|
||||||
|
def create_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def create_detail(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def update_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def update_detail(self, object_list, bundle):
|
||||||
|
return bundle.obj.user == bundle.request.user
|
||||||
|
|
||||||
|
def delete_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def delete_detail(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
class PrivateUserAuthorization(Authorization):
|
||||||
|
def read_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def read_detail(self, object_list, bundle):
|
||||||
|
return bundle.obj == bundle.request.user
|
||||||
|
|
||||||
|
def create_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def create_detail(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def update_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def update_detail(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def delete_list(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
def delete_detail(self, object_list, bundle):
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
|
||||||
class FavoriteValidation(Validation):
|
class FavoriteValidation(Validation):
|
||||||
def is_valid(self, bundle):
|
def is_valid(self, bundle, request=None):
|
||||||
errors = {}
|
errors = {}
|
||||||
snipt = bundle.data['snipt']
|
snipt = bundle.data['snipt']
|
||||||
|
|
||||||
|
@ -33,7 +163,7 @@ class FavoriteValidation(Validation):
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
class UserProfileValidation(Validation):
|
class UserProfileValidation(Validation):
|
||||||
def is_valid(self, bundle):
|
def is_valid(self, bundle, request=None):
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
if not bundle.request.user.profile.is_pro:
|
if not bundle.request.user.profile.is_pro:
|
||||||
|
@ -142,13 +272,10 @@ class PrivateUserProfileResource(ModelResource):
|
||||||
allowed_methods = ['get', 'put']
|
allowed_methods = ['get', 'put']
|
||||||
list_allowed_methods = []
|
list_allowed_methods = []
|
||||||
authentication = ApiKeyAuthentication()
|
authentication = ApiKeyAuthentication()
|
||||||
authorization = Authorization()
|
authorization = PrivateUserProfileAuthorization()
|
||||||
always_return_data = True
|
always_return_data = True
|
||||||
max_limit = 200
|
max_limit = 200
|
||||||
|
|
||||||
def apply_authorization_limits(self, request, object_list):
|
|
||||||
return object_list.filter(user=request.user)
|
|
||||||
|
|
||||||
def dehydrate(self, bundle):
|
def dehydrate(self, bundle):
|
||||||
bundle.data['email'] = bundle.obj.user.email
|
bundle.data['email'] = bundle.obj.user.email
|
||||||
bundle.data['username'] = bundle.obj.user.username
|
bundle.data['username'] = bundle.obj.user.username
|
||||||
|
@ -168,14 +295,11 @@ class PrivateUserResource(ModelResource):
|
||||||
allowed_methods = ['get']
|
allowed_methods = ['get']
|
||||||
list_allowed_methods = []
|
list_allowed_methods = []
|
||||||
authentication = ApiKeyAuthentication()
|
authentication = ApiKeyAuthentication()
|
||||||
authorization = Authorization()
|
authorization = PrivateUserAuthorization()
|
||||||
always_return_data = True
|
always_return_data = True
|
||||||
max_limit = 200
|
max_limit = 200
|
||||||
cache = SimpleCache()
|
cache = SimpleCache()
|
||||||
|
|
||||||
def apply_authorization_limits(self, request, object_list):
|
|
||||||
return object_list.filter(username=request.user.username)
|
|
||||||
|
|
||||||
def dehydrate(self, bundle):
|
def dehydrate(self, bundle):
|
||||||
bundle.data['email_md5'] = hashlib.md5(bundle.obj.email.lower()).hexdigest()
|
bundle.data['email_md5'] = hashlib.md5(bundle.obj.email.lower()).hexdigest()
|
||||||
bundle.data['is_pro'] = bundle.obj.profile.is_pro
|
bundle.data['is_pro'] = bundle.obj.profile.is_pro
|
||||||
|
@ -190,7 +314,7 @@ class PrivateTagResource(ModelResource):
|
||||||
fields = ['id', 'name',]
|
fields = ['id', 'name',]
|
||||||
allowed_methods = ['get']
|
allowed_methods = ['get']
|
||||||
authentication = ApiKeyAuthentication()
|
authentication = ApiKeyAuthentication()
|
||||||
authorization = Authorization()
|
authorization = PrivateTagAuthorization()
|
||||||
always_return_data = True
|
always_return_data = True
|
||||||
max_limit = 200
|
max_limit = 200
|
||||||
cache = SimpleCache()
|
cache = SimpleCache()
|
||||||
|
@ -205,6 +329,7 @@ class PrivateTagResource(ModelResource):
|
||||||
orm_filters['slug'] = filters['q']
|
orm_filters['slug'] = filters['q']
|
||||||
|
|
||||||
return orm_filters
|
return orm_filters
|
||||||
|
|
||||||
def dehydrate(self, bundle):
|
def dehydrate(self, bundle):
|
||||||
bundle.data['absolute_url'] = '/%s/tag/%s/' % (bundle.request.user.username,
|
bundle.data['absolute_url'] = '/%s/tag/%s/' % (bundle.request.user.username,
|
||||||
bundle.obj.slug)
|
bundle.obj.slug)
|
||||||
|
@ -216,11 +341,6 @@ class PrivateTagResource(ModelResource):
|
||||||
|
|
||||||
return bundle
|
return bundle
|
||||||
|
|
||||||
def apply_authorization_limits(self, request, object_list):
|
|
||||||
object_list = object_list.filter(snipt__user=request.user)
|
|
||||||
object_list = object_list.annotate(count=models.Count('taggit_taggeditem_items__id'))
|
|
||||||
object_list = object_list.order_by('-count', 'name')
|
|
||||||
return object_list
|
|
||||||
|
|
||||||
class PrivateSniptResource(ModelResource):
|
class PrivateSniptResource(ModelResource):
|
||||||
user = fields.ForeignKey(PrivateUserResource, 'user', full=True)
|
user = fields.ForeignKey(PrivateUserResource, 'user', full=True)
|
||||||
|
@ -236,7 +356,7 @@ class PrivateSniptResource(ModelResource):
|
||||||
detail_allowed_methods = ['get', 'patch', 'put', 'delete']
|
detail_allowed_methods = ['get', 'patch', 'put', 'delete']
|
||||||
list_allowed_methods = ['get', 'post']
|
list_allowed_methods = ['get', 'post']
|
||||||
authentication = ApiKeyAuthentication()
|
authentication = ApiKeyAuthentication()
|
||||||
authorization = Authorization()
|
authorization = PrivateSniptAuthorization()
|
||||||
ordering = ['created', 'modified',]
|
ordering = ['created', 'modified',]
|
||||||
always_return_data = True
|
always_return_data = True
|
||||||
max_limit = 200
|
max_limit = 200
|
||||||
|
@ -321,9 +441,6 @@ class PrivateSniptResource(ModelResource):
|
||||||
|
|
||||||
return orm_filters
|
return orm_filters
|
||||||
|
|
||||||
def apply_authorization_limits(self, request, object_list):
|
|
||||||
return object_list.filter(user=request.user)
|
|
||||||
|
|
||||||
def save_m2m(self, bundle):
|
def save_m2m(self, bundle):
|
||||||
tags = bundle.data.get('tags_list', [])
|
tags = bundle.data.get('tags_list', [])
|
||||||
if tags != '':
|
if tags != '':
|
||||||
|
@ -343,7 +460,7 @@ class PrivateFavoriteResource(ModelResource):
|
||||||
detail_allowed_methods = ['get', 'post', 'delete']
|
detail_allowed_methods = ['get', 'post', 'delete']
|
||||||
list_allowed_methods = ['get', 'post',]
|
list_allowed_methods = ['get', 'post',]
|
||||||
authentication = ApiKeyAuthentication()
|
authentication = ApiKeyAuthentication()
|
||||||
authorization = Authorization()
|
authorization = PrivateFavoriteAuthorization()
|
||||||
ordering = ['created',]
|
ordering = ['created',]
|
||||||
always_return_data = True
|
always_return_data = True
|
||||||
max_limit = 200
|
max_limit = 200
|
||||||
|
@ -359,6 +476,3 @@ class PrivateFavoriteResource(ModelResource):
|
||||||
bundle.data['snipt'] = Snipt.objects.get(pk=bundle.data['snipt'])
|
bundle.data['snipt'] = Snipt.objects.get(pk=bundle.data['snipt'])
|
||||||
return super(PrivateFavoriteResource, self).obj_create(bundle,
|
return super(PrivateFavoriteResource, self).obj_create(bundle,
|
||||||
user=bundle.request.user, **kwargs)
|
user=bundle.request.user, **kwargs)
|
||||||
|
|
||||||
def apply_authorization_limits(self, request, object_list):
|
|
||||||
return object_list.filter(user=request.user)
|
|
||||||
|
|
|
@ -1,9 +1,89 @@
|
||||||
from django.test import TestCase
|
from django.contrib.auth.models import User
|
||||||
|
from tastypie.test import ResourceTestCase
|
||||||
|
from tastypie.models import ApiKey
|
||||||
|
from snipts.models import Snipt
|
||||||
|
|
||||||
|
|
||||||
class SniptAPITest(TestCase):
|
class SniptResourceTest(ResourceTestCase):
|
||||||
def test_get_snipt(self):
|
fixtures = ['test_entries.json']
|
||||||
"""
|
|
||||||
We should be able to get a public snipt from the API.
|
def setUp(self):
|
||||||
"""
|
super(SniptResourceTest, self).setUp()
|
||||||
self.assertEqual(1 + 1, 2)
|
|
||||||
|
# Johnny
|
||||||
|
self.johnny = User.objects.create_user('johnny', 'johnny@snipt.net', 'password')
|
||||||
|
ApiKey.objects.get_or_create(user=self.johnny)
|
||||||
|
self.johnny_auth = self.create_apikey(self.johnny, self.johnny.api_key.key)
|
||||||
|
self.johnny_private = Snipt(title='Private snipt for Johnny', lexer='text', public=False, user=self.johnny)
|
||||||
|
self.johnny_public = Snipt(title='Public snipt for Johnny', lexer='text', public=True, user=self.johnny)
|
||||||
|
self.johnny_private.save()
|
||||||
|
self.johnny_public.save()
|
||||||
|
|
||||||
|
# Bob
|
||||||
|
self.bob = User.objects.create_user('bob', 'bob@snipt.net', 'password')
|
||||||
|
ApiKey.objects.get_or_create(user=self.bob)
|
||||||
|
self.bob_auth = self.create_apikey(self.bob, self.bob.api_key.key)
|
||||||
|
self.bob_private = Snipt(title='Private snipt for Bob', lexer='text', public=False, user=self.bob)
|
||||||
|
self.bob_public = Snipt(title='Public snipt for Bob', lexer='text', public=True, user=self.bob)
|
||||||
|
self.bob_private.save()
|
||||||
|
self.bob_public.save()
|
||||||
|
|
||||||
|
|
||||||
|
# Private
|
||||||
|
def test_get_private_list(self):
|
||||||
|
|
||||||
|
resp = self.api_client.get('/api/private/snipt/', format='json', authentication=self.johnny_auth)
|
||||||
|
|
||||||
|
self.assertHttpOK(resp)
|
||||||
|
self.assertValidJSONResponse(resp)
|
||||||
|
self.assertEqual(len(self.deserialize(resp)['objects']), 2)
|
||||||
|
|
||||||
|
def test_get_private_detail(self):
|
||||||
|
|
||||||
|
resp = self.api_client.get('/api/private/snipt/{}/'.format(self.johnny_private.pk), format='json', authentication=self.johnny_auth)
|
||||||
|
|
||||||
|
self.assertHttpOK(resp)
|
||||||
|
self.assertValidJSONResponse(resp)
|
||||||
|
self.assertEqual(self.deserialize(resp)['key'], self.johnny_private.key)
|
||||||
|
|
||||||
|
# Unauthenticated request.
|
||||||
|
resp = self.api_client.get('/api/private/snipt/{}/'.format(self.johnny_private.pk), format='json')
|
||||||
|
self.assertHttpUnauthorized(resp)
|
||||||
|
|
||||||
|
# Unauthorized request.
|
||||||
|
resp = self.api_client.get('/api/private/snipt/{}/'.format(self.johnny_private.pk), format='json', authentication=self.bob_auth)
|
||||||
|
self.assertHttpUnauthorized(resp)
|
||||||
|
|
||||||
|
def test_post_private_list(self):
|
||||||
|
|
||||||
|
new_snipt = {
|
||||||
|
'title': 'A new private snipt.',
|
||||||
|
'lexer': 'text',
|
||||||
|
'public': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = self.api_client.post('/api/private/snipt/',
|
||||||
|
data=new_snipt,
|
||||||
|
format='json',
|
||||||
|
authentication=self.johnny_auth)
|
||||||
|
|
||||||
|
self.assertHttpCreated(resp)
|
||||||
|
self.assertEqual(Snipt.objects.count(), 5)
|
||||||
|
|
||||||
|
resp = self.api_client.get('/api/private/snipt/', format='json', authentication=self.johnny_auth)
|
||||||
|
self.assertEqual(len(self.deserialize(resp)['objects']), 3)
|
||||||
|
|
||||||
|
resp = self.api_client.get('/api/public/snipt/', format='json')
|
||||||
|
self.assertEqual(len(self.deserialize(resp)['objects']), 2)
|
||||||
|
|
||||||
|
|
||||||
|
# Public
|
||||||
|
def test_get_public_list(self):
|
||||||
|
|
||||||
|
self.assertEqual(Snipt.objects.count(), 4)
|
||||||
|
|
||||||
|
resp = self.api_client.get('/api/public/snipt/', format='json')
|
||||||
|
|
||||||
|
self.assertHttpOK(resp)
|
||||||
|
self.assertValidJSONResponse(resp)
|
||||||
|
self.assertEqual(len(self.deserialize(resp)['objects']), 2)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.conf.urls.defaults import *
|
from django.conf.urls import *
|
||||||
|
|
||||||
from snipts import views
|
from snipts import views
|
||||||
|
|
||||||
|
|
2
urls.py
2
urls.py
|
@ -1,5 +1,5 @@
|
||||||
from views import (homepage, lexers, pro_signup, sitemap, tags, pro_signup_complete)
|
from views import (homepage, lexers, pro_signup, sitemap, tags, pro_signup_complete)
|
||||||
from django.conf.urls.defaults import include, patterns, url
|
from django.conf.urls import include, patterns, url
|
||||||
from utils.views import SniptRegistrationView
|
from utils.views import SniptRegistrationView
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
|
|
Loading…
Reference in New Issue