Can't upload image file while testing Django - 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.

Related

Django channels image saving, TextField or ImageField

Chat app using django channels
I am using websockets to send base64 encoded string to the server, the base64 encoded string could be saved in TextField or saved in ImageField by decoding using base64 library, which method is preferred, why?
EDIT
I am interested in which method is preferred and why, but not how to implement
You can use this function for converting base64 data that you get from your request into django contentfile which can be added to image model field later
import base64
from django.core.files.base import ContentFile
def base64_file(data, name=None):
_format, _img_str = data.split(';base64,')
_name, ext = _format.split('/')
if not name:
name = _name.split(":")[-1]
return ContentFile(base64.b64decode(_img_str), name='{}.{}'.format(name, ext))
# Simple example
data = request.GET.get('base64data')
data = base64_file(data, name='profile_picture')
UserProfile.profile_picture = data
UserProfile.save()

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 Serializer POST data not receiving arrays

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')

Django REST Framework FileField Data in JSON

In Django REST Framework (DRF), how do I support de-Serializing base64 encoded binary data?
I have a model:
class MyModel(Model):
data = models.FileField(...)
and I want to be able to send this data as base64 encoded rather than having to multi-part form data or a "File Upload". Looking at the Parsers, only FileUploadParser and MultiPartParser seem to parse out the files.
I would like to be able to send this data in something like JSON (ie send the binary data in the data rather than the files:
{
'data':'...'
}
I solved it by creating a new Parser:
def get_B64_JSON_Parser(fields):
class Impl(parsers.JSONParser):
media_type = 'application/json+b64'
def parse(self, *args, **kwargs):
ret = super(Impl, self).parse(*args, **kwargs)
for field in fields:
ret[field] = SimpleUploadedFile(name=field, content=ret[field].decode('base64'))
return ret
return Impl
which I then use in the View:
class TestModelViewSet(viewsets.ModelViewSet):
parser_classes = [get_B64_JSON_Parser(('data_file',)),]
This is an old question, but for those looking for an up-to-date solution, there is a plugin for DRF (drf_base64) that handles this situation. It allows reading files encoded as base64 strings in the JSON request.
So given a model like:
class MyModel(Model):
data = models.FileField(...)
and an expected json like:
{
"data": "data:image/png;base64,iVBORw0KGgoA ....",
...
}
The (des) serialization can be handled just importing from drf_base modules instead of the drf itself.
from drf_base64.serializers import ModelSerializer
from .models import MyModel
class MyModel(ModelSerializer):
class Meta:
model = MyModel
Just remember that is posible to get a base64 encoded file in javascript with the FileReader API.
There's probably something clever you can do at the serialiser level but the first thing that comes to mind is to do it in the view.
Step 1: Write the file. Something like:
fh = open("/path/to/media/folder/fileToSave.ext", "wb")
fh.write(fileData.decode('base64'))
fh.close()
Step 2: Set the file on the model. Something like:
instance = self.get_object()
instance.file_field.name = 'folder/fileToSave.ext' # `file_field` was `data` in your example
instance.save()
Note the absolute path at Step 1 and the path relative to the media folder at Step 2.
This should at least get you going.
Ideally you'd specify this as a serialiser field and get validation and auto-assignment to the model instance for free. But that seems complicated at first glance.

Storing Images on App Engine using Django

I'm trying to upload and save a resized image in a db.BlobProperty field on Google App Engine using Django.
the relevant part of my view that process the request looks like this:
image = images.resize(request.POST.get('image'), 100, 100)
recipe.large_image = db.Blob(image)
recipe.put()
Which seems like it would be the logical django equivalent of the example in the docs:
from google.appengine.api import images
class Guestbook(webapp.RequestHandler):
def post(self):
greeting = Greeting()
if users.get_current_user():
greeting.author = users.get_current_user()
greeting.content = self.request.get("content")
avatar = images.resize(self.request.get("img"), 32, 32)
greeting.avatar = db.Blob(avatar)
greeting.put()
self.redirect('/')
(source: http://code.google.com/appengine/docs/python/images/usingimages.html#Transform)
But, I keep getting an error that says: NotImageError / Empty image data.
and refers to this line:
image = images.resize(request.POST.get('image'), 100, 100)
I'm having trouble getting to the image data. Seems like it's not being uploaded but I can't figure out why. My form has the enctype="multipart/form-data" and all that. I think something's wrong with how I'm referring to the image data. "request.POST.get('image')" but I can't figure out how else to reference it. Any ideas?
Thanks in advance.
After some guidance from "hcalves" I figured out the problem. First of all, the default version of Django that comes bundled with App Engine is version 0.96 and how the framework handles uploaded files has changed since then. However in order to maintain compatibility with older apps you have to explicitly tell App Engine to use Django 1.1 like this:
from google.appengine.dist import use_library
use_library('django', '1.1')
You can read more about that in the app engine docs.
Ok, so here's the solution:
from google.appengine.api import images
image = request.FILES['large_image'].read()
recipe.large_image = db.Blob(images.resize(image, 480))
recipe.put()
Then, to serve the dynamic images back again from the datastore, build a handler for images like this:
from django.http import HttpResponse, HttpResponseRedirect
def recipe_image(request,key_name):
recipe = Recipe.get_by_key_name(key_name)
if recipe.large_image:
image = recipe.large_image
else:
return HttpResponseRedirect("/static/image_not_found.png")
#build your response
response = HttpResponse(image)
# set the content type to png because that's what the Google images api
# stores modified images as by default
response['Content-Type'] = 'image/png'
# set some reasonable cache headers unless you want the image pulled on every request
response['Cache-Control'] = 'max-age=7200'
return response
You access uploaded data via request.FILES['field_name'].
http://docs.djangoproject.com/en/dev/topics/http/file-uploads/
Reading more about Google's Image API, seems to me you should be doing something like this:
from google.appengine.api import images
image = Image(request.FILES['image'].read())
image = image.resize(100, 100)
recipe.large_image = db.Blob(image)
recipe.put()
request.FILES['image'].read() should work because it's supposed to be a Django's UploadedFile instance.