Add existing API into django rest framework - django

Besides the API I created by using the django rest framework, now I want to add the existing commerial API into the backend, how to do this?
The commerial API include several part(API), for example, /prepare, /upload, /merge,/get_result, etc. They all use the "POST" indeed.
Do I need to call the commerial API directly on the frontend? Or I need to integrate the commerial API in to one backend API? Any suggestions are appreciated.Thanks.
For example:
```
class TestView(APIView):
"""
Add all the commerial API here in order
/prepare(POST)
/upload(POST)
/merge(POST)
/get_result(POST)
return Result
"""
```

Depending on your needs, I suggest making the external API calls on backend.
As a good practice, you should seperate your external API calls from your views. As it can be messy as the project gets bigger.
Check the sample code of how I manage external API calls, by seperating them to a different file, for example api_client.py
My default api_client.py looks something like this.
(You need to install "requests" pip package by pip install requests)
import requests
from django.conf import settings
class MyApiClient(object):
def __init__(self):
self.base_url = settings.EXTERNAL_API_1.get('url')
self.auth_url = "{0}login/".format(self.base_url)
self.username = settings.EXTERNAL_API_1.get('username')
self.password = settings.EXTERNAL_API_1.get('password')
self.session = None
self.access_token = None
self.token_type = None
self.token_expires_in = None
def _request(self, url, data=None, method="POST", as_json=True):
if self.session is None:
self.session = requests.Session()
if not self.access_token:
self.authenticate()
r = requests.Request(method, url=url, data=data, headers={
"Accept": "application/json",
"Content-Type": "application/json",
'Authorization': 'Token {0}'.format(
self.access_token)
})
prepared_req = r.prepare()
res = self.session.send(prepared_req, timeout=60)
if as_json:
json_result = res.json()
return json_result
return res
def _get(self, url):
return self._request(url=url, method="GET")
def _post(self, url, data):
return self._request(url=url, data=data, method="POST")
def authenticate(self):
res = requests.post(self.auth_url, json={'username': self.username,
'password': self.password},
headers={"Content-Type": "application/json"})
if res.status_code != 200:
res.raise_for_status()
json_result = res.json()
self.access_token = json_result.get('response', None).get('token',None)
def prepare(self):
something = 'bla bla'
request_url = "{0}prepare".format(self.base_url)
result = self._post(url=request_url, data=something)
return result
And in your views.py
from api_client import MyApiClient
class TestView(APIView):
api_client = MyApiClient()
def post(request, *args, **kwargs):
res1 = api_client.prepare()
res2 = api_client.your_other_method()
res3 = api_client.your_last_method()
return Response(res3)
Edited! Hope this helps now :)

Related

Django unit test - patch multiple requests for external api

I need to write 'one' test code with external api, which requires requests 'twice'.
first, I need to check if user is valid.
So I handled this with decorator in ./users/utils.py
import requests
def login_decorator(func):
def wrapper(self, request, *args, **kwargs):
# this access token is issued by external api
access_token = request.headers.get('Authorization', None)
# first requests, which gives me an info about user.
response = requests.get(
'https://kapi.kakao.com/v2/user/me',
headers={'Authorization':f'Bearer {access_token}'}
)
user_email = response.json()['kakao_account']['email']
request.user = User.objects.get(email=user_email)
return func(self, request, *args, **kwargs)
return wrapper
and then, I need to send that user a message with the external api again.
this code is in ./bids/views.py
import requests
class BiddingView(View):
#it uses login_decorator above
#login_decorator
def post(self, request, art_id):
try:
user = request.user
data = json.loads(request.body)
with transaction.atomic():
#handle things with bidding system#
#the external api requires its token to use a message api.
token = request.headers.get('Authorization', None)
#second requests, with post method
response = requests.post(
'https://kapi.kakao.com/v2/api/talk/memo/default/send',
headers = {'Authorization' : f'Bearer {token}'},
data = {"template_object" : json.dumps({'message':'contents'})}
)
return JsonResponse({'MESSAGE' : 'SUCCESS'}, status=200)
except KeyError:
return JsonResponse({'MESSAGE' : 'KEY ERROR'}, status=400)
This is my unit test code about BiddingView so far, which obviously only works for decorator
#patch('users.utils.requests')
def test_kakao_message_success(self, mock_requests):
class MockResponse:
def json(self):
return {'kakao_account' : {'email' : 'test#test.com'}}
mock_requests.get = MagicMock(return_value=MockResponse())
header = {'HTTP_Authorization' : 'ACCESS_TOKEN'}
body = {'offered_price' : 10000}
response = client.post(
'/bidding/1',
json.dumps(body),
content_type='application/json', **header
)
but I need to patch both .users.utils.requests and .bids.views.requests for my mock test.
#patch('users.utils.requests') # + #patch('bids.views.requests')
def test_kakao_message_success(self, mock_requests):
I want to know how to patch two requests at the same time.
I simplified your source code for ease of testing so that we can concentrate on the problem which are the external requests.
./utils.py
import requests
def login_decorator(func):
def wrapper(self, request, *args, **kwargs):
# first requests, which gives me an info about user.
response = requests.get(
'https://kapi.kakao.com/v2/user/me',
headers={'Authorization': 'Bearer access_token'}
)
request.user = response.json()['kakao_account']['email']
return func(self, request, *args, **kwargs)
return wrapper
./views.py
import json
import requests
from utils import login_decorator
class BiddingView:
#it uses login_decorator above
#login_decorator
def post(self, request, art_id):
print(f"The current user is {request.user}")
#second requests, with post method
response = requests.post(
'https://kapi.kakao.com/v2/api/talk/memo/default/send',
headers = {'Authorization' : 'Bearer token'},
data = {"template_object" : json.dumps({'message':'contents'})}
)
return response.text
Solution 1 - Manual patching of requests for each source file
You can stack the unittest.mock.patch decorator, one after the other.
from unittest.mock import MagicMock, patch
from views import BiddingView
class MockLoginResponse:
def json(self):
return {'kakao_account' : {'email' : 'test#test.com'}}
class MockViewResponse:
text = "He alone, who owns the youth, gains the future."
#patch('utils.requests.get', MagicMock(return_value=MockLoginResponse()))
#patch('views.requests.post', MagicMock(return_value=MockViewResponse()))
def test_kakao_message_success():
response = BiddingView().post(
request=MagicMock(),
art_id="some art"
)
print(f"Response: {response}")
Output:
__________________________________________________________________________________ test_kakao_message_success ___________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
The current user is test#test.com
Response: He alone, who owns the youth, gains the future.
Solution 2.1 - Instead of patching requests per file, patch the exact target request
This requires you to install library https://pypi.org/project/requests-mock/
from unittest.mock import MagicMock
import requests_mock
from views import BiddingView
def test_kakao_message_success_with_library():
with requests_mock.Mocker() as requests_mocker:
# Mock the external requests
requests_mocker.get(
"https://kapi.kakao.com/v2/user/me",
json={'kakao_account' : {'email' : 'test#test.com'}},
)
requests_mocker.post(
"https://kapi.kakao.com/v2/api/talk/memo/default/send",
text="He alone, who owns the youth, gains the future.",
)
response = BiddingView().post(
request=MagicMock(),
art_id="some art"
)
print(f"Response: {response}")
Output:
Same as above
Solution 2.2 - Instead of patching requests per file, patch the exact target request. But now, apply it automatically to any test using pytest's autouse feature.
from unittest.mock import MagicMock
import pytest
import requests_mock
from views import BiddingView
#pytest.fixture(autouse=True)
def setup_external_requests():
with requests_mock.Mocker() as requests_mocker:
# Mock the external requests
requests_mocker.get(
"https://kapi.kakao.com/v2/user/me",
json={'kakao_account' : {'email' : 'test#test.com'}},
)
requests_mocker.post(
"https://kapi.kakao.com/v2/api/talk/memo/default/send",
text="He alone, who owns the youth, gains the future.",
)
# It is required to perform a yield instead of return to not teardown the requests_mocker
yield requests_mocker
def test_kakao_message_success_with_library_2():
response = BiddingView().post(
request=MagicMock(),
art_id="some art"
)
print(f"Response: {response}")
Output:
Same as above

How can I use JWT token in DRF tests?

I need to test my API. For example, I have images list page. I need to do tests for this page, but I cannot do this without authentication. I use JWT. I do not know how to do it.
Help me please.
tests.py
class ImagesListTestCase(APITestCase):
def test_images_list(self):
response = self.client.get('/api/', HTTP_AUTHORIZATION="JWT {}".format("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTkzNzUxMzMxLCJqdGkiOiI3NWE3NDNkMGU3MDQ0MGNiYjQ3NDExNjQ3MTI5NWVjNSIsInVzZXJfaWQiOjF9.7HkhZ1hRV8OtQJMMLEAVwUnJ0yDt8agFadAsJztFb6A"))
self.assertEqual(response.status_code, status.HTTP_200_OK)
I tried to do
response = self.client.get('/api/', Authorization="Bearer <token>")
Also
response = self.client.get('/api/', Authorization="JWT <token>")
response = self.client.get('/api/', HTTP_AUTHORIZATION="Bearer <token>")
django creates a temporary database for test; so it's better to get token from username and password like this:
class ImagesListTestCase(APITestCase):
def setUp(self) :
self.register_url = reverse("your:register:view") # for example : "users:register"
self.user_data = {
"username": "test_user",
"email": "test_user#gmail.com",
"password": "123456"
}
self.client.post(self.register_url,self.user_data) # user created
auth_url = reverse("your:login:view") #for example :"users:token_obtain_pair"
self.access_token = self.client.post(auth_url,{
"username" : self.user_data.get("username") ,
"password" : self.user_data.get("password")
}).data.get("access") # get access_token for authorization
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.access_token}')
def test_images_list(self):
response = self.client.get('/api/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
You can create users that require JWT authentication like:
def setUp(self) -> None:
self.client = APIClient()
if os.environ.get('GITHUB_WORKFLOW'):
local_token_url = 'https://testing/app/token/jwt'
else:
local_token_url = 'http://127.0.0.1:8000/app/token/jwt'
response = requests.post(local_token_url, {'email': 'test_contact1#user.com', 'password': '123pass321'})
self.test_user1 = json.loads(response.text)
response = requests.post(local_token_url, {'email': 'test_contact2#user.com', 'password': '123pass321'})
self.test_user2 = json.loads(response.text)
self.contact_person = apm.AppUser.objects.create(email="test_contact1#user.com", first_name="John",
last_name="Doe",
company_name="test_company_1", phone_number="08077745673",
is_active=True)
you can then use them like below parsing data = {}
self.client.credentials(HTTP_AUTHORIZATION="Bearer {}".format(self.test_user1.get('access')))
response = self.client.post(reverse('create-list'), data=data, format='json')
print(response.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATE
Instead of invoking api to get the token, Another very simple solution can as follows
from rest_framework_simplejwt.tokens import AccessToken
def setUp(self):
self.factory = APIRequestFactory()
user = User.objects.create_user(
username='jacob', email='jacob#gmail.com', password='top_secret')
self.client = APIClient()
token = AccessToken.for_user(user=user)
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
and it will be used as
def test_get_cities(self):
response = self.client.get('/<actual path to url>/cities/')
assert response.status_code == 200
assert len(response.data) == 0
self.assertEqual(response.data, [])
Important thing to notice here is in setUp function token is generated using AcessToken class from JWT itself, so no actual API is required for testing other flows in your app.

How to make a POST request to the end point, in views in django for chatterbot?

i am new to django!
I want to make a chatterbot chatbot in my website, for which i need to make a POST request in views. I have created a model. I am using mysql database for this.
I have visited github and other website and finally got a code, but it doesn't have the POST request
this is my models.py:
class Response(models.Model):
statement = models.ForeignKey(
'Statement',
related_name='in_response_to',
on_delete=False
)
response = models.ForeignKey(
'Statement',
related_name='+',
on_delete=False
)
unique_together = (('statement', 'response'),)
occurrence = models.PositiveIntegerField(default=0)
def __str__(self):
s = self.statement.text if len(self.statement.text) <= 20 else self.statement.text[:17] + '...'
s += ' => '
s += self.response.text if len(self.response.text) <= 40 else self.response.text[:37] + '...'
return s
this is where i need to make a POST request in views.py
def post(self, request, *args, **kwargs):
response = Response.objects.all()
if request.is_ajax():
input_data = json.loads(request.read().decode('utf-8'))
else:
input_data = json.loads(request.body.decode('utf-8'))
self.validate(input_data)
response_data = self.chatterbot.get_response(input_data)
return JsonResponse(response, response_data, status=200)
def get(self, request, *args, **kwargs):
data = {
'detail': 'You should make a POST request to this endpoint.',
'name': self.chatterbot.name,
'recent_statements': self._serialize_recent_statements()
}
# Return a method not allowed response
return JsonResponse(data, status=405)
If you're using django rest framework (DRF), i recommend you start by doing QuickStart and then Serialization steps. DRF has a really good documentation and in the Serialization you could find how to make a POST request by defining:
Models
Serializers
Api
Routers

Q: How to store session data before authentication occured

I'm using django-all auth with GMail login.
There one of my view that will receive HTTP-POST from a Hotspot login page in other server (actualy it's mikrotik hotspot redirect).
I need to read their posted data AFTER social login.
I read https://stackoverflow.com/a/32250781/5901318
Looks like the safest way is to store the POST data in session, and later my view will get it from request.session
but I don't know how to 'store data safely in request.session before the authentication occurs'.
def my_login_required(function):
#https://stackoverflow.com/a/39256685/5901318
def wrapper(request, *args, **kwargs):
decorated_view_func = login_required(request)
if not decorated_view_func.user.is_authenticated:
if request.method == "POST" :
print('my_login_required POST:',request.POST.__dict__)
print('my_login_required ARGS:',args)
print('my_login_required KWARGS:',kwargs)
print('my_login_required SESSION:',request.session.__dict__)
wrapper.__doc__ = function.__doc__
wrapper.__name__ = function.__name__
return wrapper
##receiver(user_logged_in)
#csrf_exempt
#my_login_required
def hotspotlogin(request,*args,**kwargs):
print('HOTSPOTLOGIN')
I tried to access it using requests :
r = requests.post('http://mysite:8000/radius/hotspotlogin/', json={"NAMA": "BINO"}, headers = {'Content-type': 'application/json', 'Accept': 'text/plain'})
but in django shell I only got :
my_login_required POST: {'_encoding': 'utf-8', '_mutable': False}
my_login_required ARGS: ()
my_login_required KWARGS: {}
my_login_required SESSION: {'storage_path': '/opt/djangos/radius03/mysessions/', 'file_prefix': 'sessionid', '_SessionBase__session_key': None, 'accessed': True, 'modified': False, 'serializer': <class 'django.core.signing.JSONSerializer'>, '_session_cache': {}}
Kindly please give me any clue to do that.
Sincerely
-bino-
Got priceless help from a friend, and here is the solution.
def my_login_required(function):
def wrapper(request, *args, **kwargs):
old_data=dict()
try :
old_data['POST'] = dict(request.POST)
except :
old_data['POST'] = dict()
try :
old_data['GET'] = dict(request.GET)
except :
old_data['GET'] = dict()
old_data['method'] = request.method
decorated_view_func = login_required(request)
if not decorated_view_func.user.is_authenticated: #Only if user not authenticated
request.session['old'] = old_data #put old data in request.session['old']
return decorated_view_func(request) # return redirect to signin
return function(request, *args, **kwargs)
wrapper.__doc__ = function.__doc__
wrapper.__name__ = function.__name__
return wrapper
#my_login_required
def testview(request,*args,**kwargs):
print('SESSION DATA:', request.session.get('old')) #take original post/get data from request.session['old']

How to send request from one django server to another server

I have a django problem. I want to send datas which are from browser or business logic
on my django server to another django server or just the same server but different port, to handle the request. How can I do? I have tried to achieve using socket,but it seems no working.
Following is my code:
accept the client's request:
def im(request):
userp = None
try:
userp = UserProfile.objects.get(user = request.user)
except:
pass
if not userp:
return HttpResponse("error")
print '111'
if request.method == "GET":
import json
msg = json.loads(request.GET.get('msg'))
try:
msg['from_id'] = userp.id
if msg.get('type', '') == 'sync': #页面同步消息
msg['to_id'] = userp.id
push_msg(msg)
return HttpResponse("success")
except:
return HttpResponse("error")
#return HttpResponseRedirect("http://127.0.0.1:9000/on_message")
return HttpResponse("error")
helper.py:push_msg:
def push_msg(msg):
print '111'
params = str(msg)
headers = {"Content-type":"application/x-www-form-urlencoded", "Accept":"text/plain"}
conn = httplib.HTTPConnection("http://127.0.0.1:9000/push_msg/")
conn.request("POST", "/cgi-bin/query", params, headers)
url(r'^push_msg/$', 'chat.events.on_message')
events.py:on_message
def on_message(request):
msg = request.POST.get('msg')
msg = eval(msg)
try:
print 'handle messages'
from_id = int(msg['from_id'])
to_id = int(msg['to_id'])
user_to = UserProfile.objects.get(id = msg['to_id'])
django_socketio.broadcast_channel(msg, user_to.channel)
if msg.get('type', '') == 'chat':
ct = Chat.objects.send_msg(from_id=from_id,to_id=to_id,content=data['content'],type=1)
ct.read = 1
ct.save()
except:
pass
use python requests module to do this requests has more features then httplib2 and it is very easy to use http://docs.python-requests.org/
I have used httplib2 to accomplish something similar. From the httplib2 documentation try:
import httplib2
import urllib
data = {'name': 'fred', 'address': '123 shady lane'}
body = urllib.urlencode(data)
h = httplib2.Http()
resp, content = h.request("http://example.com", method="POST", body=body)
You should then be able to handle the POST in your second django server, and return the appropriate results to the first django server.