Django Rest Framework Serializer POST data not receiving arrays - django

I'm using the DRF test api for testing my serializers, for instance I made my data like this:
image_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../media/', 'product_images/', 'myimage.png')
with open(image_path) as image:
encoded_image = base64.b64decode(image.read())
data = {
u'small_name': u'Product Test Case Small Name',
u'large_name': u'Product Test Case Large Name',
u'description': u'Product Test Case Description',
u'images': [
{u'image': encoded_image}
],
u'variants': [
{u'value': u'123456789'}
]
}
response = self.client.post(url, data, 'multipart')
However when I receive the data on the respective serializer the variants array and the images array are empty.
What could be the issue here?

Thanks to Linovia and Rahul Gupta I was able to resolve the image and arrays problem. I changed my serializer to receive an Base64ImageField using the one provided by django-extra-fields. Rahul also pointed a silly error on my code.
I'll leave some code to help someone with the same problem.
serializers.py file
from drf_extra_fields.fields import Base64ImageField
class ProductImageSerializer(serializers.ModelSerializer):
source = Base64ImageField(source='image', required=False, )
test.py file
image_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../media/', 'product_images/', 'myimage.png')
with open(image_path) as image:
encoded_image = base64.b64encode(image.read())
data = {
u'small_name': u'Product Test Case Small Name',
u'large_name': u'Product Test Case Large Name',
u'description': u'Product Test Case Description',
u'images': [
{u'source': encoded_image}
],
u'variants': [
{u'value': u'123456789'}
]
}
response = self.client.post(url, data, format='json')

You can not have both the image upload and nested data.
Either you want to upload the image and use multipart encoding (HTML forms) which means your arrays will not work (unless it's a list of foreign key or similar). This could work with JSon content type but then you'll loose the ability to upload image. Your option there might be to encode with base64 the image and upload it but you'll have to customize a few things on the serializer.

Try to encode the image using base64.b64encode() instead of the base64.b64decode() method.
encoded_image = base64.b64encode(image.read())
Also, send json encoded data with content type set to application/json.
self.client.post(url, json.dumps(data), content_type='application/json')

You have to set the format of clientto JSON:
response = self.client.post(url, data, format='json')

Related

Input for field is missing even after i type the correct format on the API

I have coded the following:
models.py
class Job(models.Model):
datetime = models.DateTimeField(default=timezone.now)
combinedparameters = models.CharField(max_length = 1000)
serializers.py
class JobSerializers(serializers.ModelSerializer):
class Meta:
model = Job
fields = ['combinedparameters']
views.py
#api_view(['POST'])
def create_job(request):
job = Job()
jobserializer = JobSerializers(job, data = request.data)
if jobserializer.is_valid():
jobserializer.save()
return Response(jobserializer.data, status=status.HTTP_201_CREATED)
return Response(jobserializer.errors, status=status.HTTP_400_BAD_REQUEST)
Page looks like this:
But if i copy
{'device': 177, 'configuration': {'port_range': 'TenGigabitEthernet1/0/1,TenGigabitEthernet1/0/2,TenGigabitEthernet1/0/3,TenGigabitEthernet1/0/4,TenGigabitEthernet1/0/5', 'port_mode': 'Access', 'port_status': 'Disabled', 'port_param1': 'Test\\n1\\n2\\n3', 'port_param2': 'Test\\n1\\n2\\n3'}}
And click post, got error saying the single quotes have to be double quotes. So i changed it to :
{"device": 177, "configuration": {"port_range": "TenGigabitEthernet1/0/1,TenGigabitEthernet1/0/5", "port_mode": "Access", "port_status": "Disabled", "port_param1": "1\\n2\\n3", "port_param2": "1\\n2\\n3"}}
I clicked post again and this time the following error comes out:
I dont understand why is it happening. The reason why I key in the long format because this is the format i want to save in my database and this format is created upon saving from my html that creates the job
Postman:
Updated Postman:
Your post data object does not contain the required key "combinedparameters". I'm guessing that big object you copy into content is the string you want saved into the CharField combinedparameters? If that's the case you should structure your post data like this:
{
"combinedparameters": "{'device': 177, 'configuration': {'port_range': 'TenGigabitEthernet1/0/1,TenGigabitEthernet1/0/2,TenGigabitEthernet1/0/3,TenGigabitEthernet1/0/4,TenGigabitEthernet1/0/5', 'port_mode': 'Access', 'port_status': 'Disabled', 'port_param1': 'Test\\n1\\n2\\n3', 'port_param2': 'Test\\n1\\n2\\n3'}}"
}

How to properly send data from APIClient in Django (rest_framework) for a POST request

I've encountered some strange behavior in a Django unittest. Specifically, I'm using the APIClient module from rest_framework.test to simulate GET/POST requests from a unittest.
The issue occurs when updating/creating a new object in the Django ORM via a POST request (see the code below):
def test_something(self):
data = {
"name": 'unit testing',
"data": {}
}
response = self.api_client.post(reverse('save_model'), data=data, format='json')
self.assertEqual(response.status_code, 200)
#api_view(['GET', 'POST'])
def save_model(request):
obj, created = MyModel.objects.update_or_create(
user_id=request.user,
**request.data
)
return JsonResponse({
'id': obj.id,
'name': obj.name,
'user_id': obj.user_id.id
})
The error i receive when running the test case:
Error binding parameter 1 - probably unsupported type
Based on other stack posts involving this error, i would assume i have a type issue for the second parameter (the data field). However when the same exact data is used to store an object in Django shell, it works every time. Additionally, when the request is made from the client (with the same data) the request succeeds every time.
If i print the data in the unittest request i get the following:
(, u'{}')
(, u'unit testing')
Model code is below:
class MyModel(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
user_id = models.ForeignKey(AUTH_USER_MODEL)
data = JSONField()
So i thought this might be a unicode issue. But once again, storing the object with unicode data in the shell works just fine. One subtle difference to note, the django unittest will create a new test db for the models, whereas running in the shell does not.
Im out of answers, so if someone can shed some light on what's occuring here, that would be amazing.

Django Rest Framework api test upload text file application/x-www-form-urlencoded

I'm trying to test my Django REST API for file uploading.
The catch is that my server tries to validate the file so that the file has a recognised file type. Currently only text-based filetypes are allowed, like: docs, pdf, txt.
I've tried using a temporary file and now I just tried to read a file from the disk then gave up.
Whenever I use a temporary file the server responds with:
{
"cover_letter": [
"The submitted data was not a file. Check the encoding type on the form."
],
"manuscript": [
"The submitted data was not a file. Check the encoding type on the form."
]
}
This is my test:
def test_user_can_submit_a_paper(self):
"""
Ensure that an user is able to upload a paper.
This test is not working.. yet
"""
tmp_file = open("__init__.py", "w")
data = {
"title": "paper",
"authors": "me",
"description": "ma detailed description",
"manuscript": base64.b64encode(tmp_file.read()).decode(),
"cover_letter": base64.b64encode(tmp_file.read()).decode()
}
tmp_file.close()
response = self.client.post(self.papers_submitted, data=urlencode(MultiValueDict(data)), content_type='application/x-www-form-urlencoded',
HTTP_AUTHORIZATION=self.authorization_header)
print(response.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
del data["cover_letter"]
response = self.client.post(self.papers_submitted, data=urlencode(MultiValueDict(data)), content_type='application/x-www-form-urlencoded',
HTTP_AUTHORIZATION=self.authorization_header)
print(response.data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
I have successfully tested this endpoint via postman but I don't know how to do it in Python.
If I understand what you are asking, you'd like to test your DRF endpoint with a file upload, using python. You should use RequestFactory, which simulates a request -- it will be more like using Postman.
This answer Django: simulate HTTP requests in shell has an example of using RequestFactory, and there are multiple answers here django RequestFactory file upload for specific solutions on how to include uploaded files with RequestFactory.
I've managed to come up with a solution thanks to Mark's answer.
Here's the code if anyone is interested:
def test_user_can_submit_a_paper(self):
from api.journal import PaperListSubmitted
from django.test.client import RequestFactory
from django.core.files import temp as tempfile
"""
Ensure that an user is able to upload a paper.
"""
request_factory = RequestFactory()
manuscript = tempfile.NamedTemporaryFile(suffix=".txt")
cover_letter = tempfile.NamedTemporaryFile(suffix=".txt")
manuscript.write(b"This is my stupid paper that required me to research writing this test for over 5h")
cover_letter.write(b"This is my stupid paper that required me to research writing this test for over 5h")
manuscript.seek(0)
cover_letter.seek(0)
post_data = {
"title": "My post title",
"description": "this is my paper description",
"authors": "no authors",
"manuscript": manuscript,
"cover_letter": cover_letter
}
request = request_factory.post(self.papers_submitted, HTTP_AUTHORIZATION=self.authorization_header,
data=post_data)
response = PaperListSubmitted.as_view()(request)
self.assertEqual(response.status_code, status.HTTP_200_OK)

Can't upload image file while testing Django

I am developing API using Django REST Framework.
I have a Django model that has models.ImageField and it works just fine.
But when I want to unittest creating model object, I get error:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte
My code:
class PlacesTest(APITestCase):
. . .
def test_create_place_full(self):
. . .
image = SimpleUploadedFile(name='test.jpg',
content=open('test.png', 'rb').read(),
content_type='image/jpeg')
request = self.factory.post(reverse('place-list'),
{'name': 'test_place_1',
'picture': image,
})
I have tried passing string with path to image, and I've tried methods from Django testing model with ImageField to do tests, but no success.
What type should I pass to Django REST framework when adding image: file object or string with path?
How can I add real file to my tests?
Found solution for my problem if someone is interested:
all I needed was specifying format='multipart' in request arguments:
request = self.factory.post(reverse('place-list'),
{'name': 'test_place_1',
'picture': self.image},
format='multipart')
in my project was:
REST_FRAMEWORK = {
...
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}
so no image could be added to POST request.

django lazy translation sometimes appears in response.data in unit tests

I'm writing unit tests for my django api written with django-rest-framework, and I'm encountering seemingly inconsistent response data from calls that generate 400_BAD_REQUEST.
When I make a request that fails because it references an invalid primary key, repr(response.data) is a string that I can check for the substring "Invalid pk". But when I make a request that fails because it's trying to create a non-unique object, repr(response.data) contains {'name': [<django.utils.functional.__proxy__ object at 0x7f3ccdcb26d0>]} instead of the expected {'name': ['This field must be unique.']}. When I make an actual POST call to the real server, I get the expected 400 response with {'name': ['This field must be unique.']}.
Here's a code sample:
class GroupViewTests(APITestCase):
def test_post_existing_group(self):
"""
Use POST to update a group at /groups
"""
# create a group
# self.group_data returns a valid group dict
data = self.group_data(1)
url = reverse('group-list')
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# create the same group again
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
# this fails
self.assertIn('must be unique', repr(response.data))
def test_create_group_with_nonexistent_user(self):
"""
Create a group with an invalid userid in it.
"""
url = reverse('group-list')
data = self.group_data(5)
data.update({'users': ['testnonexistentuserid']})
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
# this succeeds
self.assertIn('Invalid pk', repr(response.data))
I'm using only the default middleware.
From what I've found online, the __proxy__ object is a representation of a lazy translation, which can be rendered as a string by calling unicode(obj) or obj.__unicode__. And indeed, response.data['name'][0].__unicode__ does return the expected value, but calling unicode(response.data) instead of repr(response.data) still returns the object identified by memory address.
Can anyone explain what's going on here, and how to fix it? I can work around the issue for now, but I'd like to know the "real way" to fix the problem. Is it possibly a bug in django or django-rest-framework?
Use response.content instead of response.data.
response.content contains the json output just like your client will receive it. It's also useful if you have custom renderer that change the response because response.data is not rendered, but response.content yes.
E.g :
import json
# we have to parse the output since it no more a dict
data = json.loads(response.content)
# if `response.content` is a byte string, decode it
data = json.loads(response.content.decode('utf-8'))