POST document with Django RequestFactory instead of form data - python

I'd like to build a request for testing middleware, but I don't want POST requests to always assume I'm sending form data. Is there a way to set request.body on a request generated from django.test.RequestFactory?
I.e., I'd like to do something like:
from django.test import RequestFactory
import json
factory = RequestFactory(content_type='application/json')
data = {'message':'A test message'}
body = json.dumps(data)
request = factory.post('/a/test/path/', body)
# And have request.body be the encoded version of `body`
The code above will fail the test because my middleware needs the data to be passed as the document in request.body not as form data in request.POST. However, RequestFactory always sends the data as form data.
I can do this with django.test.Client:
from django.test import Client
import json
client = Client()
data = {'message':'A test message'}
body = json.dumps(data)
response = client.post('/a/test/path/', body, content_type='application/json')
I'd like to do the same thing with django.test.RequestFactory.

RequestFactory has built-in support for JSON payloads. You don't need to dump your data first. But you should be passing the content-type to post, not to the instantiation.
factory = RequestFactory()
data = {'message':'A test message'}
request = factory.post('/a/test/path/', data, content_type='application/json')

I've tried Jay's solution and didn't work, but after some reseach, this did (Django 2.1.2)
factory = RequestFactory()
request = factory.post('/post/url/')
request.data = {'id': 1}

Here's what worked for me in Django 4.1:
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase, RequestFactory
from customauth import views
class RegistrationViewTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
def test_post_request_creates_new_user(self):
data = {
'email': 'new_user#email.com',
'screen_name': 'new_user',
'password1': 'new_user_password',
'password2': 'new_user_password',
}
request = self.factory.post('/any/path/will/do/', data )
middleware = SessionMiddleware(request)
middleware.process_request(request)
request.session.save()
response = views.registration_view(request)
self.assertEqual(response.status_code, 302)
# ok
This test passes. The form was successfully processed in views.registration_view.
Note:
When I included content_type='application/json' in the call to self.factory.post (as the accepted answer suggests), request.POST had no content in the view. Without that, it worked. I don't know why but would be happy to learn.
I needed to manually added SessionMiddleware to request.

In later version of Django (tested on 4.0) this is no longer an issue. On the other hand, to pass data to request.POST might be.
In default, when passing content-type to a RequestFactory, data goes into request.body and when you don't, data goes into request.POST.
request_factory = RequestFactory()
# provide content-type
request = request_factory.post(f'url', data={'foo': 'bar'}, content_type="application/json")
print(request.body) # b'{"foo": "bar"}'
# don't provide content type
request = request_factory.post(f'url', data={'foo': 'bar'})
print(request.POST) # <QueryDict: {'foo': ['bar']}>

Related

Test Django with dictionary in dictionary data

Description
I'm using Django for my website and trying to write some unit tests. However, when using dict in dict data I met some errors. I found out that view received slightly different data.
From
{'class_id': '1', 'filter_options': {'full_name': 'user 1'}} in data of test file
to <QueryDict: {'class_id': ['1'], 'filter_options': ['full_name']}>. I means that value has changed to array, and value of nested dict wasn't detected.
Code
# test.py
def test_search_with_full_name(self):
data = {'class_id': '1', 'filter_options': {'full_name': 'user 1'}}
response = self.client.post('/api/students', data, format='json')
self.assertEqual(response.status_code, 200)
# view.py
class SearchStudentView(generics.GenericAPIView):
def post(self, request):
print(request.data)
if not 'class_id' in request.data:
return HttpResponse('class_id is required', status=400)
class_id = request.data['class_id']
students = Users.objects.filter(
details_student_attend_class__course__pk=class_id)
if 'filter_options' in request.data:
filter_options = request.data['filter_options']
if 'full_name' in filter_options:
students = students.filter(
full_name=filter_options['full_name'])
Thanks for any help!
Django's TestCase uses django.test.Client as the client and it does not set the content type etc. according to the format kwarg, instead it uses the content_type kwarg so you should either set that or use APITestCase from DRF which will use the APIClient class.
While using TestCase:
def test_search_with_full_name(self):
data = {'class_id': '1', 'filter_options': {'full_name': 'user 1'}}
response = self.client.post('/api/students', data, content_type='application/json')
self.assertEqual(response.status_code, 200)
While using APITestCase from DRF:
from rest_framework.test import APITestCase
class YourTest(APITestCase):
def test_search_with_full_name(self):
data = {'class_id': '1', 'filter_options': {'full_name': 'user 1'}}
response = self.client.post('/api/students', data, format='json')
self.assertEqual(response.status_code, 200)
Could you change format to content_type='application/json'
Django docs say this
If you provide content_type as application/json, the data is serialized using json.dumps() if it’s a dict, list, or tuple. Serialization is performed with DjangoJSONEncoder by default, and can be overridden by providing a json_encoder argument to Client. This serialization also happens for put(), patch(), and delete() requests.
There might be an issue with serializing the dictionaries as values for POST fields. So in case if above solution doesn't work another way could be a serialize filter options by your self using json.dumps(filter_options) before passing to the request. And then read them via json.loads(request.POST.get(filter_options)) in your view.

Django testing post request with multipart/form data and a dictionary

My client uses the requests library to make this call to my Django server
import requests
response = requests.post(url, files=dict({
'value': 'key',
}))
This will create a requests that inserts the dictionary into the field request.FILES as a <MultiValueDict: {}>
I am trying to recreate this with django.test.
I keep seeing to try something like
from django.test import TestCase, Client
client = Client()
response = client.post('/sign', dict(request_data))
but the request.FILES object is empty
edit ----
I have also tried with the same result ( request.FILES -> <MultiValueDict: {}>)
client.post('/sign', {'file': dict({
'key' : 'value'
})})
Edit 2---
A look at the midldleware where I am checking the value
class ApiAuthenticationMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request: HttpRequest):
print(request.FILES)
Solution
fake_file.name = 'data.json'
post_data = {
'data.json': fake_file,
}
return self.client.post('/sign', post_data, content_type=MULTIPART_CONTENT)

How to convert a Django HttpRequest object with data to a Django Rest Framework Request object?

I'm trying to turn a django.http.HttpRequest object that contains JSON POST data into a rest_framework.request.Request object, but the data ends up empty.
I was asked to create the HttpRequest using the Django Rest Framework's APIRequestFactory. So I create it like this:
from rest_framework.test import APIRequestFactory
factory = APIRequestFactory()
data = {'email': 'test#example.com'}
request = factory.post('/', data, content_type='application/json')
# also tried using json.dumps(data) instead of just data
And then I try to convert it to a Request object using:
from rest_framework.request import Request
from rest_framework.parsers import JSONParser
converted_request = Request(request, parsers=[JSONParser])
I would expect converted_request.data to contain the data from data, i.e. {'email': 'test#example.com'}. However, when in print it, I get <QueryDict: {}>:
>>> print(converted_request.data)
<QueryDict: {}>
The only way I can get the request to contain the data is by setting the _full_data attribute after creating the Request object:
>>> converted_request._full_data = data
>>> print(converted_request.data)
{'email': 'test#example.com'}
I'm looking to see if there is a way of populating the request's data without setting the attribute directly. I don't understand why it's not getting populating currently.
Below is the full snippet for easy copy-and-pasting:
from rest_framework.test import APIRequestFactory
factory = APIRequestFactory()
data = {'email': 'test#example.com'}
request = factory.post('/', data, content_type='application/json')
from rest_framework.request import Request
from rest_framework.parsers import JSONParser
converted_request = Request(request, parsers=[JSONParser])
print(converted_request.data)
# <QueryDict: {}>
converted_request._full_data = data
print(converted_request.data)
# {'email': 'test#example.com'}
Turns out the parsers need to be instances and not just classes and the data needs to be a JSON string:
import json
from rest_framework.test import APIRequestFactory
factory = APIRequestFactory()
data = {'email': 'test#example.com'}
request = factory.post('/', json.dumps(data), content_type='application/json')
from rest_framework.request import Request
from rest_framework.parsers import JSONParser
converted_request = Request(request, parsers=[JSONParser()])
print(converted_request.data)

How do test in Django

I'm trying to do my first tests on Django and I don't know do it or after reading the docs (where it explains a very easy test) I still don't know how do it.
I'm trying to do a test that goes to "login" url and makes the login, and after a succesfull login redirects to the authorized page.
from unittest import TestCase
from django.test.client import Client
class Test(TestCase):
def testLogin(self):
client = Client()
headers = {'X-OpenAM-Username': 'user', 'X-OpenAM-Password': 'password', 'Content-Type': 'application/json'}
data = {}
response = client.post('/login/', headers=headers, data=data, secure=False)
assert(response.status_code == 200)
And the test success, but I don't know if it's beacuse the 200 of loading "/login/" or because the test do the login and after redirect get the 200 code.
How can I check on the test that after the login the url redirected it's the correct? There is a plugin or something that helps with the test? Or where I can find a good tutorial to test my views and the model?
Thanks and regards.
To properly test redirects, use the follow parameter
If you set follow to True the client will follow any redirects and a
redirect_chain attribute will be set in the response object containing
tuples of the intermediate urls and status codes.
Then your code is as simple as
from django.test import TestCase
class Test(TestCase):
def test_login(self):
client = Client()
headers = {'X-OpenAM-Username': 'user', 'X-OpenAM-Password': 'password', 'Content-Type': 'application/json'}
data = {}
response = client.post('/login/', headers=headers, data=data, secure=False)
self.assertRedirects(response,'/destination/',302,200)
Note that it's self.assertRedirects rather than assert or assertRedirects
Also note that the above test will most likely fail because you are posting an empty dictionary as the form data. Django form views do not redirect when the form is invalid and an empty form will probably be invalid here.
Django have plenty of tools for testing. For this task, you should use test case class from Django, for example django.test.TestCase.
Then you can use method assertRedirects() and it will check where you've been redirected and with which code. You can find any info you need here.
I've tried to write the code for your task:
from django.test import TestCase
class Test(TestCase):
def test_login(self):
data = {'X-OpenAM-Username': 'user', 'X-OpenAM-Password': 'password'}
response = client.post('/login/', data=data, content_type='application/json', secure=false)
assertRedirects(response, '/expected_url/', 200)
Then you can use python3 manage.py test to run all tests.

how can I provide csrf protection in case of using requests module to post data to a django view

I have a modelForm as follows:
class UserProfileForm(forms.ModelForm):
class Meta:
model = FileUploads
fields = ['uploads']
and a view like so:
#csrf_exempt
def upper(request):
form = UserProfileForm(request.POST or None, request.FILES or None)
if form.is_valid():
form.save()
return HttpResponse(status=200)
return HttpResponse(status = 403)
And I have a simple script to send a multipart/encoded file to the view as such:
import requests
f = open('C:\\Users\\myname\\Desktop\\image.jpg', 'rb')
urls='http://localhost:8000/upper'
r=requests.post(urls, files= {'uploads':f})
print(r.status_code)
My question being: everything works fine as long as I have the csrrf_exempt decorator above the receiving view, that's fine for test environment. But what if I wanted the csrf protection in place? Considering the fact that I'm using requests module, how can I provide the csrf token?
You need to pass a cookie and a header with the same value:
import requests
f = open('C:\\Users\\myname\\Desktop\\image.jpg', 'rb')
urls='http://localhost:8000/upper'
cookies = {'csrftoken': 'token'}
headers = {'X-CSRF-TOKEN': 'token'}
r=requests.post(urls, files={'uploads':f}, cookies=cookies, headers=headers)
print(r.status_code)
The value of the token does not matter, you can take any literal, as long as they are the same.

Categories