When using django.test.client.post I get Bad Request - python

I use pytest-django to do unit test on my django project. The view is
def news(request):
"""
Interface for newslist
"""
page = 1
if request.method == 'POST':
content = request.body
try:
content = json.loads(content)
except ValueError as error:
return err_response("Value Error of post: {}".format(error))
if 'page' in content:
page = content['page']
articlelist = Article.objects.all().order_by('-time')
paginator = Paginator(articlelist, 10)
try:
current_list = paginator.page(page)
except InvalidPage as error:
return err_response(error)
# coping with the paginator
...
newsnum = len(Article.objects.all())
return JsonResponse({
'newsnum': newsnum,
'pagelist': list(pagelist),
'data': [{
'title': newsitem.title,
'source': newsitem.source,
'time': newsitem.time.strftime("%Y-%m-%d %H:%M:%S"),
'content': newsitem.content,
'href': newsitem.href,
'image': newsitem.image,
} for newsitem in current_list]
}, status=200)
When I use pytest-django to test it
#pytest.mark.django_db
def test_view_news(client):
"""
Test view news
"""
url = reverse("news")
data = {
'page': 1
}
response = client.post(url, data=data)
assert response.status_code == 200
It gives Bad Request and code 400. But when I use client.get(), the response is normal (code 200).In the settings, I already set
DEBUG = True
ALLOWED_HOSTS = ['*']
Can anyone tells me what happened?

Override the default server_name used by client
Just override the default server name used by client to match one of those you have in allowed hosts e.g
ALLOWED_HOSTS = ['localhost',...]
...
.....
response = client.post(url, data, SERVER_NAME='localhost')

Related

Flask App Route returning "Bad Request" error

I'm learning full stack in Flask and am having trouble with a particular route from an API. The API being developed is a list of books and in particular I am trying to reach the data for a particular book, say book with ID = 8. The URI is http://127.0.0.1:5000/books/8. However this returns a 400 error (bad request).
I really can't spot what is going wrong. I have defined the route '/books/int:book_id' with methods GET and PATCH, so I would expect the route to work. I also see errors when I test the route with curl, for example:
curl -X PATCH -H "Content-Type: application/json" -d '{"rating":"1"}' http://127.0.0.1:5000/books/8
See below for the particular route in question:
#app.route('/books/<int:book_id>', methods=['GET', 'PATCH'])
def update_book_rating(book_id):
body = request.get_json()
try:
book = Book.query.filter_by(Book.id==book_id).one_or_none()
if book is None:
abort(404)
if 'rating' in body:
book.rating = int(body.get('rating'))
book.update() #Class book in models.py has an update method which executes a commit()
return jsonify({
'success': True,
'id': book.id
})
except Exception as e:
print(e)
abort(400)
If it helps, I am also adding the full code. Note that the Book object is defined in a separate file, which I won't put here.
import os
from flask import Flask, request, abort, jsonify
from flask_sqlalchemy import SQLAlchemy # , or_
from flask_cors import CORS
import random
from models import setup_db, Book
BOOKS_PER_SHELF = 8
# #TODO: General Instructions
# - As you're creating endpoints, define them and then search for 'TODO' within the frontend to update the endpoints there.
# If you do not update the endpoints, the lab will not work - of no fault of your API code!
# - Make sure for each route that you're thinking through when to abort and with which kind of error
# - If you change any of the response body keys, make sure you update the frontend to correspond.
def paginate_books(request, selection):
page = request.args.get('page', 1, type=int)
start = (page - 1) * BOOKS_PER_SHELF
end = start + BOOKS_PER_SHELF
books = [book.format() for book in selection]
current_books = books[start:end]
return current_books
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__)
setup_db(app)
CORS(app)
# CORS Headers
#app.after_request
def after_request(response):
response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization,true")
response.headers.add("Access-Control-Allow-Methods", "GET,PUT,PATCH,POST,DELETE,OPTIONS")
return response
# #TODO: Write a route that retrivies all books, paginated.
# You can use the constant above to paginate by eight books.
# If you decide to change the number of books per page,
# update the frontend to handle additional books in the styling and pagination
# Response body keys: 'success', 'books' and 'total_books'
# TEST: When completed, the webpage will display books including title, author, and rating shown as stars
#app.route('/books', methods=['GET'])
def get_books():
selection = Book.query.order_by(Book.id).all()
current_books = paginate_books(request, selection)
if len(current_books) == 0:
abort(404)
return jsonify({
'success': True,
'books': current_books,
'total_books': len(Book.query.all())
})
# #TODO: Write a route that will update a single book's rating.
# It should only be able to update the rating, not the entire representation
# and should follow API design principles regarding method and route.
# Response body keys: 'success'
# TEST: When completed, you will be able to click on stars to update a book's rating and it will persist after refresh
#app.route('/books/<int:book_id>', methods=['GET', 'PATCH'])
def update_book_rating(book_id):
body = request.get_json()
try:
book = Book.query.filter_by(Book.id==book_id).one_or_none()
if book is None:
abort(404)
if 'rating' in body:
book.rating = int(body.get('rating'))
book.update() #Class book in models.py has an update method which executes a commit()
return jsonify({
'success': True,
'id': book.id
})
except Exception as e:
print(e)
abort(400)
# #TODO: Write a route that will delete a single book.
# Response body keys: 'success', 'deleted'(id of deleted book), 'books' and 'total_books'
# Response body keys: 'success', 'books' and 'total_books'
#app.route('/delete/<int:book_id>', methods=['DELETE'])
def delete_book(book_id):
try:
book = Book.query.filter_by(Book.id==book_id).one_or_none()
if book is None:
abort(404)
book.delete()
selection = Book.query.order_by(Book.id).all()
current_books = paginate_books(request, selection)
return jsonify({
'success': True,
'deleted': book_id,
'books': current_books,
'total_books': len(Book.query.all())
})
except:
abort(422)
# TEST: When completed, you will be able to delete a single book by clicking on the trashcan.
# #TODO: Write a route that create a new book.
# Response body keys: 'success', 'created'(id of created book), 'books' and 'total_books'
# TEST: When completed, you will be able to a new book using the form. Try doing so from the last page of books.
# Your new book should show up immediately after you submit it at the end of the page.
#app.route('/books', methods=['POST'])
def create_book():
body = request.get_json()
new_title = body.get('title', None)
new_author = body.get('author', None)
new_rating = body.get('rating', None)
try:
book = Book(title=new_title, author=new_author, rating=new_rating)
book.insert()
selection = Book.query.order_by(Book.id).all()
current_books = paginate_books(request, selection)
return jsonify({
'success': True,
'created': book.id,
'books': current_books,
'total_books': len(Book.query.all())
})
except:
abort(422)
#app.errorhandler(400)
def bad_request(error):
return jsonify({
'success': False,
'error': 400,
'message': 'Server cannot or will not process the request due to client error (for example, malformed request syntax, invalid request message framing, or deceptive request routing).'
}), 400
#app.errorhandler(404)
def not_found(error):
return jsonify({
'success': False,
'error': 404,
'message': 'resource not found'
}), 404
#app.errorhandler(405)
def not_found(error):
return jsonify({
'success': False,
'error': 405,
'message': 'method not allowed'
}), 405
#app.errorhandler(422)
def unprocessable(error):
return jsonify({
'success': False,
'error': 422,
'message': 'unprocessable'
}), 422
return app

Response is not valid 'application/json'

I have an issue with my post request. This is my code:
def add_review(request, dealer_id):
if request.method == "GET":
context = {
"cars": CarModel.objects.all().filter(dealerId = dealer_id),
"dealerId": dealer_id
}
return render(request, 'djangoapp/add_review.html', context)
if request.method == "POST":
if request.user.is_authenticated:
form = request.POST
review = {
"dealership": int(dealer_id),
"name": request.user.username,
"review": form["review"],
"purchase": form.get("purchasecheck") == 'on',
}
if form.get("purchasecheck"):
review["purchase_date"] = datetime.strptime(form.get("purchasedate"), "%m/%d/%Y").isoformat()
car = CarModel.objects.get(pk=form["car"])
review["car_make"] = car.make.name
review["car_model"] = car.name
review["car_year"]= int(car.year.strftime("%Y"))
json_payload = {"review": review}
url = "https://4fbfebf7.us-south.apigw.appdomain.cloud/api/review"
post_request(url=url, json_payload=json_payload, dealer_id=dealer_id)
return redirect("djangoapp:dealer_details", dealer_id=dealer_id)
else:
return redirect("/djangoapp/login")
And this:
def post_request(url, json_payload, **kwargs):
json_data = json.dumps(json_payload, indent=4)
print(f"{json_data}")
try:
# Call get method of requests library with URL and parameters
response = requests.post(url, params=kwargs, json=json_data)
except Exception as e:
# If any error occurs
print("Network exception occurred")
print(f"Exception: {e}")
print(f"With status {response.status_code}")
print(f"Response: {response.text}")
I am receiving Response is not valid 'application/json' error as you can see here.
Meanwhile, when I copy the exact same JSON to IBM Cloud to test my APIs, all is working fine and the record is created as you can see here.
I guess it's a very silly mistake, but where?
When you pass json to request.post it should be a serializable object (not already serialized)
def post_request(url, json_payload, **kwargs):
# json_data = json.dumps(json_payload, indent=4). << delete
print(f"{json_payload}")
try:
# Call get method of requests library with URL and parameters
response = requests.post(url, params=kwargs, json=json_payload)
except Exception as e:
# If any error occurs
print("Network exception occurred")
print(f"Exception: {e}")
print(f"With status {response.status_code}")
print(f"Response: {response.text}")

Why raise_for_status() did not catch the error?

Trying to check for none 200 Response in the current_track() function. What could be a problem? It throwing JSONDecodeError error. But if I understood raise_for_ status correctly it should have prevented the function from trying to load a JSON from a faulty web-page? If I run the script without this check and with uncommenting lines check_playback() it successfully catches JSONDecodeError.
The script is fetching data from Spotify and putting it to the status on vk.com.
import config
import webbrowser
import requests
import furl
import secrets
import string
import time
import os
import simplejson as json
URL_CODE_BASE_VK = 'https://oauth.vk.com/authorize'
URL_CODE_BASE_SP = 'https://accounts.spotify.com/authorize'
URL_TOKEN_VK = 'https://oauth.vk.com/access_token'
URL_TOKEN_SP = 'https://accounts.spotify.com/api/token'
URL_TRACK = 'https://api.spotify.com/v1/me/player/currently-playing'
URL_STATUS = 'https://api.vk.com/method/status.set'
EXP_IN_TOKEN_SP = 3400
EXP_IN_TOKEN_VK = 86400
FILE_TOKEN_VK = 'vk_token.json'
FILE_TOKEN_SP = 'sp_token.json'
def get_auth_code_vk():
url_code_params = {
'client_id': config.CLIENT_ID_VK,
'response_type': 'code',
'redirect_uri': 'https://oauth.vk.com/blank.html',
'v': 5.92,
'scope': 'status',
'state': gen_state(),
'display': 'page'
}
code = url_open(URL_CODE_BASE_VK, url_code_params)
return parse_code(code)
def get_auth_code_sp():
url_code_params = {
'client_id': config.CLIENT_ID_SP,
'response_type': 'code',
'redirect_uri': 'https://www.spotify.com/',
'scope': 'user-read-currently-playing',
'state': gen_state()
}
code = url_open(URL_CODE_BASE_SP, url_code_params)
return parse_code(code)
def gen_state():
symbols = string.ascii_lowercase + string.digits
return ''.join(secrets.choice(symbols) for _ in range(12))
def url_open(url_base, url_params):
url_code_full = furl.furl(url_base).add(url_params).url
webbrowser.open_new_tab(url_code_full)
input_url = input('Enter the whole URL, that you have been redirected on: ')
return input_url
def parse_code(url):
return (url.split("code=")[1]).split("&state=")[0]
def get_token_vk():
data = {
'grant_type': 'authorization_code',
'code': get_auth_code_vk(),
'redirect_uri': 'https://oauth.vk.com/blank.html',
'client_id': 6782333,
'client_secret': config.CLIENT_SECRET_VK
}
response = requests.post(url=URL_TOKEN_VK, data=data).json()
write_file(FILE_TOKEN_VK, response)
def get_token_sp():
data = {
'grant_type': 'authorization_code',
'code': get_auth_code_sp(),
'redirect_uri': 'https://www.spotify.com/',
'client_id': config.CLIENT_ID_SP,
'client_secret': config.CLIENT_SECRET_SP
}
response = requests.post(url=URL_TOKEN_SP, data=data).json()
write_file(FILE_TOKEN_SP, response)
def write_file(tkn_file, response):
dict = {}
dict['token'] = response["access_token"]
dict['time'] = time.time()
with open(tkn_file, 'w') as file:
file.write(json.dumps(dict))
def load_file(tkn_file):
with open(tkn_file) as file:
data = json.load(file)
return data
def set_status():
params = {
'v': 5.92,
'access_token': load_file(FILE_TOKEN_VK)['token'],
'text': current_track()
}
set_status = requests.get(url=URL_STATUS, params=params)
def track_data():
tkn_file = load_file(FILE_TOKEN_SP)['token']
headers = {
'Accept': 'application/json',
'Authorization': f'Bearer {tkn_file}'
}
return requests.get(url=URL_TRACK, headers=headers)
def current_track():
response = track_data()
print(response)
try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
return "Error: " + str(e)
# data = track_data().json()
data = response.json()
artist = data['item']['artists'][0]['name']
track = data['item']['name']
return(f'{artist} - {track}')
def check_playback():
set_status()
print(current_track())
# try:
# set_status()
# print(current_track())
# except json.decoder.JSONDecodeError:
# print('Not playing')
def token_missing(file):
return not os.path.isfile(file)
def token_expired(file, exp_in):
return time.time() - load_file(file)['time'] > exp_in
def token_not_valid(file, exp_in):
return token_missing(file) or token_expired(file, exp_in)
def run_script():
if token_not_valid(FILE_TOKEN_VK, EXP_IN_TOKEN_VK):
get_token_vk()
if token_not_valid(FILE_TOKEN_SP, EXP_IN_TOKEN_SP):
get_token_sp()
check_playback()
if __name__ == "__main__":
run_script()
Error screen
raise_for_status() will only raise an exception if the server reported an error to you (and even then, only if it actually followed the HTTP spec and returned a HTTP error code).
There is no way for the library to know that the response is incorrect. Even if it was correctly formatted JSON, it can't know what schema you expect it to follow (what fields should be present, and what types those fields should have). Even if it knew the schema and had verified it, there is no way for it to know that the data is actually correct and not made up on the spot.

Why my requests session expire?

I am using requests 2.12.1 library in Python 2.7 and Django 1.10 for consume web services. When I use a session for save cookies and use persistence, and pass 10 seconds ~ without use any web service, my view regenerates the object requests.Session()...
This makes web service doesn't serve me, because my view has changed the cookies.
This is my Views.py:
client_session = requests.Session()
#watch_login
def loginUI(request):
response = client_session.post(URL_API+'login/', data={'username': username, 'password': password,})
json_login = response.json()
#login_required(login_url="/login/")
def home(request):
response_statistics = client_session.get(URL_API+'statistics/')
log('ClientSession: '+str(client_session))
try:
json_statistics = response_statistics.json()
except ValueError:
log('ExcepcionClientSession: '+str(client_session))
return logoutUI(request)
return render(request, "UI/home.html", {
'phone_calls' : json_statistics['phone_calls'],
'mobile_calls' : json_statistics['mobile_calls'],
'other_calls' : json_statistics['other_calls'],
'top_called_phones' : json_statistics['top_called_phones'],
'call_ranges_week' : json_statistics['call_ranges_week'],
'call_ranges_weekend' : json_statistics['call_ranges_weekend'],
'access_data' : accessData(request.user.username),
})
def userFeaturesFormInit(clientRequest):
log('FeaturesClientSession: '+str(client_session))
response = client_session.get(URL_API+'features/')
try:
json_features = response.json()
except ValueError as e:
log('ExcepcionFeaturesClientSession: '+str(client_session))
raise e
Thank you.
I fixed it specifying cookies manually, and saving it in the request.
client_session = requests.Session()
response = client_session.post(URL_API+'login/', {'username': username, 'password': password,})
request.session['cookiecsrf'] = client_session.cookies['csrftoken']
request.session['cookiesession'] = client_session.cookies['sessionid']
And sending it in the gets/posts:
cookies = {'csrftoken' : request.session['cookiecsrf'], 'sessionid': request.session['cookiesession']}
response = requests.get(URL, cookies=cookies)

Django Unit Testing testing views

I am testing my views using Django Unit testing. I am making get and post requests with params to check what status i get back.
But the problem how to check for context variables which are retuned in the response?
For example, on the View Cities page, I make a get request, the context dict in the view has the variable cities. So I want to check for context.
resp = self.client.post(
path=reverse('upload_video'),
data={"video_url": video_url, "pro": 1}
)
self.assertEqual(resp.status_code, 200)
Condition is True both ways, if the form is invalid or valid it returns 200. If I can check for context, then I can check what has been retuned from the view in response.
What I tried
=> resp.__dict__
{'templates': [], '_handler_class': None, '_headers': {'vary': ('Vary', 'Cookie'), 'content-type': ('Content-Type', 'application/json')}, '_charset': 'utf-8', '_closable_objects': [], 'cookies': <SimpleCookie: >, 'client': <django.test.client.Client object at 0x112bace10>, '_base_content_is_iter': False, 'context': None, 'request': {u'CONTENT_LENGTH': 202, u'wsgi.input': <django.test.client.FakePayload object at 0x113667990>, u'REQUEST_METHOD': 'POST', u'PATH_INFO': '/upload/video/modal/', u'CONTENT_TYPE': u'multipart/form-data; boundary=BoUnDaRyStRiNg', u'QUERY_STRING': ''}, '_container': ['{"error": {"msg": "Pro: Select a valid choice. That choice is not one of the available choices.", "head": null}}']}
Check _container has that variable. The form is invalidated, and retuned an error in the context. but when I do the following i get None
=> resp.context
None
Test
import os
from django.contrib.auth import authenticate
from django.core.urlresolvers import reverse
from django.test import TestCase
def test_video_upload(self):
""" Test that video upload is successful """
self.create_and_login(username="su", password="su", is_superuser=True)
video_urls = [
u"https://www.youtube.com/watch?v=abc",
u"https://vimeo.com/32222",
u"http://www.dailymotion.com/video/rer"
]
for video_url in video_urls:
resp = self.client.post(
path=reverse('upload_video'),
data={"video_url": video_url, "pro": 1}
)
set_trace() #Breakpoint
a = resp.context[-1] # <=== Not getting it here.
self.assertEqual(resp.status_code, 200) #passes
videos = Video.objects.all()
self.assertEqual(len(videos), 3)
View
ctx = {}
if request.method == Enums.Request.POST:
video_form = UploadVideoEasyForm(data=request.POST)
if video_form.is_valid():
video, log = video_form.save(request=request)
msg = 'Successfully Uploaded, View: here'.format(video.get_absolute_url())
ctx[Enums.ResponseAlert.Success] = {'msg': msg}
else:
ctx[Enums.ResponseAlert.Error] = make_alert(msg=form_error_to_string(video_form))
return HttpResponse(json.dumps(ctx), content_type="application/json")
elif request.method == Enums.Request.GET:
ctx['upload_video'] = UploadVideoEasyForm()
if request.user.is_authenticated() and request.user.is_superuser:
return render_to_response('new/modals/upload_video.html', context_instance=RequestContext(request, ctx))
Cheers.
The resp (An instance of django.test.Response) should have an context attribute.
You can access context value using context[..]:
self.assertEqual(resp.context['cities'], ...)

Categories