A massive amount of work upgrading the API to Tastypie >= 0.9.14 with new authorization schema.

master
Nick Sergeant 2013-03-24 22:38:04 -04:00
parent fb0142f249
commit e669c5f25c
7 changed files with 235 additions and 35 deletions

View File

@ -1,4 +1,4 @@
from django.conf.urls.defaults import * from django.conf.urls import *
from accounts import views from accounts import views

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
from django.conf.urls.defaults import * from django.conf.urls import *
from snipts import views from snipts import views

View File

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