Skip to content
This repository was archived by the owner on Jan 25, 2018. It is now read-only.

Commit 6cb37f5

Browse files
author
Andy McKay
committed
drf, just the unimportant bits first (bug 875540)
1 parent 9042a13 commit 6cb37f5

File tree

11 files changed

+258
-87
lines changed

11 files changed

+258
-87
lines changed

lib/bango/resources/refund.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
from functools import partial
21
import uuid
2+
from functools import partial
33

44
from django.conf import settings
55

66
from tastypie import fields
77
from tastypie.exceptions import ImmediateHttpResponse
88
from tastypie.http import HttpNotFound
99

10+
from cached import SimpleResource
1011
from lib.bango.client import ClientMock
1112
from lib.bango.constants import CANT_REFUND, NOT_SUPPORTED, OK, PENDING
1213
from lib.bango.errors import BangoFormError
@@ -15,7 +16,6 @@
1516
STATUS_PENDING, TYPE_REFUND)
1617
from lib.transactions.models import Transaction
1718
from lib.transactions.resources import TransactionResource
18-
from cached import SimpleResource
1919

2020
from solitude.logger import getLogger
2121

lib/services/resources.py

Lines changed: 78 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
1-
import json
21
import urlparse
32

43
from django.conf import settings
54
from django.core.cache import cache
5+
from django.core.exceptions import PermissionDenied
6+
from django.views import debug
67

78
import requests
89
from aesfield.field import AESField
9-
from tastypie import http
10-
from tastypie.exceptions import ImmediateHttpResponse
11-
from tastypie_services.services import StatusError, StatusObject as Base
10+
from rest_framework.decorators import api_view
11+
from rest_framework.response import Response
1212

1313
from lib.sellers.models import Seller
14-
from solitude.base import Resource
1514
from solitude.logger import getLogger
1615

1716
log = getLogger('s.services')
1817

1918

20-
class StatusObject(Base):
19+
class StatusObject(object):
20+
21+
def __init__(self):
22+
self.status = {}
23+
self.error = None
2124

2225
@property
2326
def is_proxy(self):
@@ -26,22 +29,25 @@ def is_proxy(self):
2629
def test_cache(self):
2730
# caching fails silently so we have to read from it after writing.
2831
cache.set('status', 'works')
29-
3032
if cache.get('status') == 'works':
31-
self.cache = True
33+
return True
34+
35+
return False
3236

3337
def test_db(self):
3438
try:
3539
# exists is one of the fastest queries one can run.
3640
Seller.objects.exists()
37-
self.db = True
41+
return True
3842
except Exception:
3943
log.error('Error connection to the db', exc_info=True)
44+
return False
4045

4146
def test_settings(self):
4247
# Warn if the settings are confused and the proxy settings are
4348
# mixed with non-proxy settings. At this time we can't tell if you
4449
# are running just the database server or solitude all in one.
50+
self.status['settings'] = True
4551
caches = getattr(settings, 'CACHES', {})
4652
dbs = getattr(settings, 'DATABASES', {})
4753

@@ -52,7 +58,7 @@ def test_settings(self):
5258
if (db.get('ENGINE', '') not in ['',
5359
'django.db.backends.dummy']):
5460
log.error('Proxy db set to: %s' % engine)
55-
self.settings = False
61+
return False
5662

5763
# There could be an issue if you share a proxy with the database
5864
# server, a local cache should be fine.
@@ -62,13 +68,17 @@ def test_settings(self):
6268
'django.core.cache.backends.dummy.DummyCache',
6369
'django.core.cache.backends.locmem.LocMemCache']):
6470
log.error('Proxy cache set to: %s' % backend)
65-
self.settings = False
71+
return False
6672

6773
# Tuck the encrypt test into settings.
6874
test = AESField(aes_key='bango:signature')
6975
if test._decrypt(test._encrypt('foo')) != 'foo':
70-
self.settings = False
76+
return False
7177

78+
return True
79+
80+
def test_proxies(self):
81+
self.status['proxies'] = True
7282
if not self.is_proxy and settings.BANGO_PROXY:
7383
# Ensure that we can speak to the proxy.
7484
home = urlparse.urlparse(settings.BANGO_PROXY)
@@ -77,7 +87,7 @@ def test_settings(self):
7787
requests.get(proxy, verify=True, timeout=5)
7888
except:
7989
log.error('Proxy error: %s' % proxy, exc_info=True)
80-
self.settings = False
90+
return False
8191

8292
if self.is_proxy:
8393
# Ensure that we can speak to Bango.
@@ -86,35 +96,60 @@ def test_settings(self):
8696
requests.get(url, verify=True, timeout=30)
8797
except:
8898
log.error('Bango error: %s' % proxy, exc_info=True)
89-
self.settings = False
99+
return False
90100

91-
def test(self):
92-
self.test_cache()
93-
self.test_db()
94-
self.test_settings()
101+
return True
95102

96-
if self.is_proxy:
97-
if self.settings and not (self.db and self.cache):
98-
return self
99-
raise StatusError(str(self))
100-
101-
if self.db and self.cache:
102-
return self
103-
raise StatusError(str(self))
104-
105-
106-
class RequestResource(Resource):
107-
"""
108-
This is a resource that does nothing, just returns some information
109-
about the request. Useful for testing that solitude is working for you.
110-
"""
111-
112-
class Meta(Resource.Meta):
113-
list_allowed_methods = ['get']
114-
resource_name = 'request'
115-
116-
def obj_get_list(self, request, **kwargs):
117-
content = {'authenticated': request.OAUTH_KEY}
118-
response = http.HttpResponse(content=json.dumps(content),
119-
content_type='application/json')
120-
raise ImmediateHttpResponse(response=response)
103+
104+
class TestError(Exception):
105+
pass
106+
107+
108+
@api_view(['GET'])
109+
def error(request):
110+
raise TestError('This is a test.')
111+
112+
113+
@api_view(['GET'])
114+
def status(request):
115+
obj = StatusObject()
116+
for key, method in (('proxies', obj.test_proxies),
117+
('db', obj.test_db),
118+
('cache', obj.test_cache),
119+
('settings', obj.test_settings)):
120+
obj.status[key] = method()
121+
122+
if obj.is_proxy:
123+
if (obj.status['settings'] and not
124+
(obj.status['db'] and obj.status['cache'])):
125+
code = 200
126+
else:
127+
# The proxy should have good settings but not the db or cache.
128+
code = 500
129+
130+
if obj.status['db'] and obj.status['cache']:
131+
code = 200
132+
else:
133+
# The db instance should have a good db and cache.
134+
code = 500
135+
return Response(obj.status, status=code)
136+
137+
138+
@api_view(['GET'])
139+
def request_resource(request):
140+
return Response({'authenticated': request.OAUTH_KEY})
141+
142+
143+
@api_view(['GET'])
144+
def settings_list(request):
145+
if not getattr(settings, 'CLEANSED_SETTINGS_ACCESS', False):
146+
raise PermissionDenied
147+
return Response(sorted(debug.get_safe_settings().keys()))
148+
149+
150+
@api_view(['GET'])
151+
def settings_view(request, setting):
152+
if not getattr(settings, 'CLEANSED_SETTINGS_ACCESS', False):
153+
raise PermissionDenied
154+
return Response({'key': setting,
155+
'value': debug.get_safe_settings()[setting]})

lib/services/tests.py

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,27 @@
22

33
from django.conf import settings
44
from django.core.cache import cache
5+
from django.core.urlresolvers import reverse
56

67
from mock import patch
8+
from nose import SkipTest
79
from nose.tools import eq_
810

11+
912
from solitude.base import APITest
10-
from lib.services.resources import StatusObject
13+
from lib.services.resources import TestError
1114

1215

1316
@patch.object(settings, 'DEBUG', False)
1417
class TestStatus(APITest):
1518

1619
def setUp(self):
17-
self.api_name = 'services'
18-
self.list_url = self.get_list_url('status')
20+
self.list_url = reverse('services.status')
1921

2022
def failed(self, res, on):
2123
eq_(res.status_code, 500)
2224
data = json.loads(res.content)
23-
assert '%s: False' % on in data['error_message'], data
25+
assert False in data.values()
2426

2527
@patch.object(cache, 'get', lambda x: None)
2628
def test_failure_status(self):
@@ -30,8 +32,8 @@ def test_failure_status(self):
3032
# Note that Django will use the values in the settings, altering
3133
# CACHES right now will still work if your settings allow it. Urk.
3234
@patch('requests.get')
33-
@patch.object(StatusObject, 'test_cache')
34-
@patch.object(StatusObject, 'test_db')
35+
@patch('lib.services.resources.StatusObject.test_cache')
36+
@patch('lib.services.resources.StatusObject.test_db')
3537
def test_proxy(self, test_db, test_cache, requests):
3638
with self.settings(SOLITUDE_PROXY=True,
3739
DATABASES={'default': {'ENGINE': ''}},
@@ -40,31 +42,25 @@ def test_proxy(self, test_db, test_cache, requests):
4042
eq_(res.status_code, 200, res.content)
4143

4244
@patch('requests.get')
43-
@patch.object(StatusObject, 'test_cache')
44-
@patch.object(StatusObject, 'test_db')
45+
@patch('lib.services.resources.StatusObject.test_cache')
46+
@patch('lib.services.resources.StatusObject.test_db')
4547
def test_proxy_db(self, test_db, test_cache, requests):
48+
test_db.return_value = False
49+
test_cache.return_value = False
4650
with self.settings(SOLITUDE_PROXY=True,
4751
DATABASES={'default': {'ENGINE': 'foo'}},
4852
CACHES={}):
4953
self.failed(self.client.get(self.list_url), 'settings')
5054

5155

52-
@patch.object(settings, 'DEBUG', False)
53-
class TestError(APITest):
54-
55-
def setUp(self):
56-
self.api_name = 'services'
57-
self.list_url = self.get_list_url('error')
56+
class TestErrors(APITest):
5857

5958
def test_throws_error(self):
60-
res = self.client.get(self.list_url)
61-
eq_(res.status_code, 500)
62-
data = json.loads(res.content)
63-
eq_(data['error_message'], 'This is a test.')
59+
with self.assertRaises(TestError):
60+
self.client.get(reverse('services.error'))
6461

6562

6663
class TestNoop(APITest):
6764

6865
def test_noop(self):
69-
self.api_name = 'services'
70-
eq_(self.client.get(self.get_list_url('request')).status_code, 200)
66+
eq_(self.client.get(reverse('services.request')).status_code, 200)

lib/transactions/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ class Transaction(Model):
2424
currency = models.CharField(max_length=3, blank=True)
2525
provider = models.PositiveIntegerField(choices=constants.SOURCES_CHOICES)
2626
related = models.ForeignKey('self', blank=True, null=True,
27-
on_delete=models.PROTECT)
27+
on_delete=models.PROTECT,
28+
related_name='relations')
2829
seller_product = models.ForeignKey('sellers.SellerProduct', db_index=True)
2930
status = models.PositiveIntegerField(default=constants.STATUS_DEFAULT,
3031
choices=constants.STATUSES_CHOICES)

requirements/prod.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dj-database-url==0.2.1
1212
django-aesfield==0.1
1313
django-celery==2.2.4
1414
django-extensions==0.9
15+
django-filter==0.6
1516
django-multidb-router==0.5
1617
django-nose==1.1
1718
django-paranoia==0.1.7
@@ -20,6 +21,7 @@ django-raven-metlog==0.1
2021
django-sha2==0.4
2122
django-statsd-mozilla==0.3.8.3
2223
django-tastypie==0.9.11
24+
djangorestframework==2.3.5
2325
funfactory==2.1.1
2426
gunicorn==0.17.2
2527
httplib2==0.7.6
@@ -46,7 +48,6 @@ six==1.2.0
4648
slumber==0.5.3
4749
statsd==1.0.0
4850
suds==0.4
49-
tastypie-services==0.2.2
5051
tower==0.3.4
5152

5253
# not on pypi

solitude/authentication.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from django.conf import settings
44
import oauth2
55

6+
from rest_framework.authentication import BaseAuthentication
7+
from rest_framework.exceptions import AuthenticationFailed
68
from tastypie.authentication import Authentication
79

810
from solitude.logger import getLogger
@@ -59,6 +61,36 @@ def is_authenticated(self, request, **kwargs):
5961
return False
6062

6163

64+
class DummyUser(object):
65+
pass
66+
67+
68+
class RestOAuthAuthentication(BaseAuthentication):
69+
70+
def authenticate(self, request):
71+
auth_header_value = request.META.get('HTTP_AUTHORIZATION', None)
72+
request.OAUTH_KEY = None
73+
oauth_server, oauth_request = initialize_oauth_server_request(request)
74+
try:
75+
key = get_oauth_consumer_key_from_header(auth_header_value)
76+
if not key:
77+
if settings.REQUIRE_OAUTH:
78+
return None
79+
return (DummyUser(), None)
80+
oauth_server.verify_request(oauth_request, Consumer(key), None)
81+
request.OAUTH_KEY = key
82+
log.info(u'Access granted: %s' % key)
83+
return (DummyUser(), None)
84+
85+
except KeyError:
86+
log.error(u'No key: %s' % key)
87+
return AuthenticationFailed
88+
89+
except:
90+
log.error(u'Access failed: %s' % key, exc_info=True)
91+
return AuthenticationFailed
92+
93+
6294
def initialize_oauth_server_request(request):
6395
"""
6496
OAuth initialization.

0 commit comments

Comments
 (0)