Hi I am trying to make a test case to test my upload image API. But I think I am not returning something when I pass the files for request.FILES
#models.py
class Image(models.Model):
name = models.CharField(max_length=200)
imagefile = models.ImageField(
null=True,
blank=True,
max_length=500,
upload_to='temp/images/')
def __str__(self):
return self.name
#views.py
class ImagesView(APIView):
def post(self, request):
print("DATA!!!", request.data)
print("FILE!!!", request.FILES)
params = Image(
imagefile=request.FILES['image'])
params.save()
print(params)
return Response({"status": "ok"})
#test.py
class CanalImagesApiTests(TestCase):
fixtures = []
def test_post_image(self):
c = Client()
response = c.post('/admin/login/', {'username': 'admin', 'password': 'passwrd'})
filename = 'data/sample_image.jpg'
name = 'sample_image.jpg'
data = {"data": "passthis"}
print(to_upload)
with open(filename, 'rb') as f:
c.post('/images/', data=data, files={"name": name, "image": f}, format='multipart')
response = c.get('/images/')
results = response.json()
My request.FILES is empty: <MultiValueDict: {}>
and my test gets an error: django.utils.datastructures.MultiValueDictKeyError: 'image'
You can pass a file object inside the data dictionary.
files parameter is not needed. format parameter means output format, not input format.
You can use APITestCase instead of TestCase if you are testing Django REST Framework API. This allows you can test some method like PUT.
from rest_framework import status
from rest_framework.test import APITestCase
class CanalImagesApiTests(APITestCase):
def test_post_image(self):
with open('data/sample_image.png', 'rb') as f:
data = {
"data": "passthis",
"image": f,
}
response = self.client.post('/images/', data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json(), {"status": "ok"})
Test result (which is passing):
DATA!!! <QueryDict: {'data': ['passthis'], 'image': [<InMemoryUploadedFile: sample_image.png (image/png)>]}>
FILE!!! <MultiValueDict: {'image': [<InMemoryUploadedFile: sample_image.png (image/png)>]}>
Related
I am sending files from the frontend and I want to save file location in database I am using DRF.
Currently, I am trying to save only one file but i am getting error
"'dict' object has no attribute '_committed'"
Can Anyone please give me some suggestions on what should I do?
Request Body I am getting for on file:-
{'id': "Please provide Site Contact person's Name", 'service': {'id': 3, 'service_name': 'Site
Survey', 'cost_per_kw': 1, 'min_size': 101, 'upto_size': 10000, 'tat': 5, 'creation_date':
None}, 'question': 3, 'creation_date': None, 'real_estate': 0, 'answer': 'VIkrant Patil',
'attachement': [{'name': 'Vikrant Patil - Offer Letter (1).pdf', 'size': 172518, 'url':
'blob:http://localhost:8000/cad9de5d-a12b-41a2-90f7-38deff67fd0f', '_file': {}}], 'project':
189}
My view.py logic for saving only one file:-
class ServiceProjectAnswerViewSet(viewsets.ModelViewSet):
model = ServiceProjectAnswer
serializer_class = ServiceProjectAnswerSerializer
# parser_classes = (MultiPartParser, FormParser)
def create(self, request, *args, **kwargs):
print(request.data)
instance = None
answers = request.data.copy()
data = {}
attachements = answers['attachement']
print(attachements[0])
data['answer'] = answers['answer']
data['project'] = answers['project']
data['question'] = answers['question']
serializer = self.get_serializer(data=data)
if serializer.is_valid(raise_exception=True):
try:
instance = serializer.save()
# for attachement in attachements:
AnswerAttachments.objects.create(
answer = instance,
# attachement = attachement
attachement = attachements[0]
)
except Exception as e:
print(e)
return Response(str(e), status=400)
return Response(serializer.data, status=201)
My view.py logic for saving only multiple files :-
In my responce when I a sending multiple files I am sending list of Objects every object has values which i want that is why I have use for loop to iterate.
def create(self, request, *args, **kwargs):
instance = None
print(request.data)
answers = request.data.copy()
for answer in answers:
data = {}
attachements = answer['attachement']
print(attachements,"attachement")
data['answer'] = answer['answer']
data['project'] = answer['project']
data['question'] = answer['question']
print(data,"data")
serializer = self.get_serializer(data=data)
if serializer.is_valid(raise_exception=True):
try:
instance=serializer.save()
for attachement in attachements:
print(attachement)
# attach = {}
# attach['name'] = attachement['name']
# attach['size'] = attachement['size']
# attach['url'] = attachement['url']
print(type(attachement))
AnswerAttachments.objects.create(
answer=instance,
attachement=attachement
)
except Exception as e:
print(e)
return Response(str(e), status=400)
else:
return Response({'message': ('Failed to save answers')}, status=400)
return Response('Answers are saved successfully!!', status=200)
Try something like this,
const formData = new FormData();
formData.append('file', file);
const requestOptions = {
method: "POST",
body:formData
};
Means,try to send body of request in multiform instead of application/json.
I have a model in Django like:
from django.db import models
from django.contrib.auth.models import User
from datetime import datetime
# Create your models here.
class UserDetails(models.Model):
def getFileName(self, filename):
return 'profile_pics/'+str(self.user.id)+'/'+filename
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
profile_picture = models.ImageField(upload_to=getFileName, blank = True)
country = models.CharField(max_length = 50, default='UK')
gender = models.CharField(max_length=10, default='NA')
birthday = models.DateField(default=datetime.now())
phone = models.CharField(max_length=15)
verified = models.BooleanField(default=False)
def __str__(self):
try:
return self.user.username
except:
return 'Deleted User - '+str(self.phone)
Then, I created a REST API that accepts Multipart requests to create a new user as well as save the user_details for the user. I am making the multipart POST request from my app in Flutter with the Image for the profile picture. The image is coming in the body of the request as a String instead of coming in as a file, where I could have read it with request.FILES['profile_picture']. The body of the request that I am getting by doing a print(request.data) is as follows:
Data: <QueryDict: {
'username': ['jonsnow'],
'first_name': ['Jon'],
'last_name': ['Snow'],
'email': ['jonsnow#got.com'],
'password': ['jonsnow'],
'country': ['UK'],
'gender': ['Male'],
'birthday': ['2020-4-28'],
'phone': ['5198189849'],
'profile_picture': ['����\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00��\x00�\x00\t\x06\x07\x08\x07\x06\t\x08\x07\x08\n\n\t\x0b\r\x16\x0f\r\x0c\x0c\r\x1b\x14\x15\x10\x16 \x1d"" \x1d\x1f\x1f$(4,$&1\'\x1f\x1f-=-157:::#+?D?8C49:7\x01\n\n\n\r\x0c\r\x1a\x0f\x0f\x1a7%\x1f%77777777777777777777777777777777777777777777777777��\x00\x11\x08\x019\x01�\x03\x01"\x00\x02\x11\x01\x03\x11\x01��\x00\x1c\x00\x00\x02\x03\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x04\x02\x05\x06\x01\x07\x00\x08��\x00Q\x10\x00\x02\x01\x03\x02\x03\x05\x04\x06\x06\x07\x07\x03\x03\x01\t\x01\x02\x03\x00\x04\x11\x12!\x051A\x06\x13"Qa2q��\x14B����\x07#3Rr�\x154Sbs��\x16$C����5��%c�DtEFd����\x17��\x00\x1a\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06��\x00(\x11\x01\x01\x00\x02\x01\x04\x02\x02\x02\x02\x03\x01\x00\x00\x00\x00\x00\x01\x02\x11\x03\x12!1A\x04\x13\x14Q\x05"aqBR�\x15��\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00�Cl�0ɵX��¸�iB�k; ...\uebe8?��']
}>
And print(request.FILES) is coming as: Files: <MultiValueDict: {}>
So, I made the Multipart REST API to handle this request like:
class SignUp(APIView):
parser_classes = [MultiPartParser,]
authentication_classes = [CsrfExemptSessionAuthentication]
permission_classes = [AllowAny]
def post(self, request, format=None):
data = request.data
print('\n\nFiles: '+str(request.FILES)+'\n\n');
print('\n\nData: '+str(data)+'\n\n');
print('\n\nData: '+str(request.META)+'\n\n');
## Creating a basic user
user = User.objects.create_user(username=data['username'], first_name = data['first_name'], last_name = data['last_name'], email = data['email'], password = data['password'])
user.save()
user_details = UserDetails()
user_details.user = user
rec_img_str = data['profile_picture']
rec_img_bytes = rec_img_str.encode('utf-8')
rec_img_writer = BytesIO(rec_img_bytes)
uploaded_img = InMemoryUploadedFile(rec_img_writer, None, 'sample.jpg', 'image/jpeg', None, 'utf-8')
user_details.profile_picture = uploaded_img
user_details.country = data['country']
user_details.gender = data['gender']
user_details.birthday = datetime.strptime(data['birthday'], '%Y-%m-%d').date()
user_details.phone = data['phone']
user_details.verified = False
user_details.save()
return Response({'Message': 'Profile Created Successfully'})
Here, I read the JPG image that comes as a string in the field profile_picture, converted it to Byte form, put it into BytesIO(), and then stored it in user_details.profile_picture as an InMemoryUploadedFile. It saves as sample.jpg in my MEDIA directory, but when I try to open it, it comes as a blank image.
So, how do I save the JPEG image that is coming in as a string to a ImageField in Django?
Here's my Flutter code, if it is needed for reference:
void submit() async {
MultipartRequest request = MultipartRequest("POST",
Uri.parse("http://10.0.2.2:8000/services/authentication/signup/"));
request.fields['username'] = this.username.text;
request.fields['first_name'] = this.first_name.text;
request.fields['last_name'] = this.last_name.text;
request.fields['email'] = this.email.text;
request.fields['password'] = this.password.text;
request.fields['country'] = "UK";
request.fields['gender'] = this.gender;
String birthdayStr = "${birthday.year}-${birthday.month}-${birthday.day}";
request.fields['birthday'] = birthdayStr;
request.fields['phone'] = this.phone_no.text;
if (this.profilePicture != null) {
print('Profile picture available');
print(this.profilePicture.path);
request.files.add(
await MultipartFile.fromBytes(
'profile_picture',
await this.profilePicture.readAsBytes(), //this.profilePicture is a File
contentType: MediaType('image', 'jpeg'),
),
);
}
StreamedResponse resp = await request.send();
if (resp.statusCode < 200 || resp.statusCode >= 400) {
print('There was an error in making the SignUp Request');
print('Status code: ${resp.statusCode}');
} else {
Navigator.pop(context);
}
}
Unable to store all the data into Django database. Its printing the correct list of data from an open API but not storing.
But if i give direct values of data1 in ( client.py ) for eg:
data1 = {
'employee_name':'Name',
'employee_salary':'20000',
'employee_age':'22',
'profile_image':' '
}
Then it store the above dictionary values into Django database.But the data i am getting using requests is a list.It prints in cmd but doesn't store in DB.
client.py file
def get_alldata():
url1 = "http://dummy.restapiexample.com/api/v1/employees"
url = "http://127.0.0.1:8000/employee/"
my_response = requests.get(url1)
token = get_token()
header = {'Authorization' : f'Token {get_token()}'}
data1 = [ my_response.json() ]
for d in range(len(data1)):
payload = data1[d]
res = requests.post(url, data=data1[d] , headers=header )
print(data1[d])
get_alldata()
This is the Api.py file in which includes the get and post method using serializers in django rest framework.
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from employee.serializers import *
class EmployeeAuthentication(ObtainAuthToken):
def post(self,request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, context={'request':request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token , created = Token.objects.get_or_create(user=user)
return Response(token.key)
class EmployeeView(APIView):
def get(self, request):
model = Employee.objects.all()
serializer = EmployeeSerializer(model, many=True)
return Response(serializer.data)
def post(self, request):
serializer = EmployeeSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
I got the solution.There is no problem in code yet.Problem was the data i was getting from an open API contains a Null value in a field which was unable to store into Django database.If anyone having same issue, don't GET any field having null value.
I'm pretty new to Django restframework, what i'm trying now is to return object with foreignkey.
class User(models.Model):
name = models.CharField(max_length=255,blank=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modiefied = models.DateTimeField(auto_now=True)
area = models.CharField(max_length=255,blank=True)
uuid = models.CharField(max_length=255)
home = models.CharField(max_length=255,blank=True)
work = models.CharField(max_length=255,blank=True)
mobileNo = models.CharField(max_length=255,blank=True)
email = models.CharField(max_length=255,blank=True)
appVersionCode = models.CharField(max_length=255,blank=True)
photoUrl = models.CharField(max_length=255,blank=True)
serverTime = models.CharField(max_length=255,blank=True)
fcmTokenId = models.CharField(max_length=255,blank=True)
def __str__(self):
return self.name
class LocationData(models.Model):
user = models.ForeignKey(
User, related_name='user', on_delete=models.DO_NOTHING)
source_id = models.CharField(max_length=255)
latitude = models.CharField(max_length=255)
longitude = models.CharField(max_length=255)
speed = models.CharField(max_length=255)
kms = models.CharField(max_length=255)
date_created = models.DateTimeField(auto_now=True)
date_modiefied = models.DateTimeField(auto
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class LocationDataSerializer(serializers.ModelSerializer):
class Meta:
model = LocationData
fields = '__all__'
depth = 1
I'm using def get_queryset(self):
class SyncIndexLastDataViewSet(viewsets.ModelViewSet):
serializer_class = LocationDataSerializer
def get_queryset(self):
userid = self.request.query_params.get('user_id', None)
userExist = User.objects.filter(id=userid)
if userExist.exists():
# call the original 'list' to get the original response
queryset = LocationData.objects.values('source_id').filter(user__id=userid).order_by('-source_id')[:1]
lastSourceId = queryset[0]['source_id']
response = {"collection": {"data": lastSourceId,"statusCode": status.HTTP_200_OK,"version":"1.0"}}
json = JSONRenderer().render(response)
# customize the response data
if response is not None:
return json
else:
# return response with this custom representation
response = {"collection": {"data": "","statusCode":status.HTTP_404_NOT_FOUND,"version":"1.0","error":"Not found"}}
return response
Right now the result is inside the response is below and immediately it throws this error
But i want that queryset to return as below one, Hence i can read those key-pair values in android
{ "collection": {
"data": {
"id": 31,
"source_id": "55",
"latitude": "24654",
"longitude": "454654",
"date_created": "2019-02-08T17:10:09.318644Z",
"date_modiefied": "2019-02-08T17:10:09.318714Z",
"area": "54546",
"user": {
"id": 1,
"name": "Dormy",
"date_created": "1992-01-18T03:29:53.388000Z",
"date_modiefied": "2018-02-19T05:17:00.164000Z",
"serverTime": "",
"fcmTokenId": ""
}
},
"statusCode": 200,
"version": "1.0"
}
Now the error throws
AttributeError: Got AttributeError when attempting to get a value for field source_id on serializer LocationDataSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the int instance.
Original exception text was: 'int' object has no attribute 'source_id'.
Thanks!
The answer to this depends on what type of view you are using but the bottom line is you don't do this in get_queryset you do this in the method for the type of reguest.
For example if you are using a RetrieveAPIView you should override the retrieve method from the RetrieveModelMixin like so:
class MyAPIView(RetrieveAPIView):
queryset = MyModel.objects.all()
serializer_class = MySerializer
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
data = {
"collection": {
"data": serializer.data
},
"statusCode": 200,
"version": "1.0"
}
return Response(data)
If you are using something else like a ListAPIView then you want to see what is used by that in the relevant method and override that to wrap your data.
The main thing to realise here is that it has nothing to do with getting the queryset - which is just about getting data from the database. This is about transforming the data into the correct format when sending back a response. As a result the work should be done at the point the response is made.
There are couple of solution possible for this problem. NDevox already mention how we can overwrite our retrive function and get our expected response. But If we want this will be done with every response for every api end-point and if we go this way we need to overwrite every function then its quite burden and its DRY we should avoid this as possible. One of the possible way to introduce a middleware or overwrite Response so that we can get our generic response for every-api end-point without explicitly overwrite every functionality.
Possible Solution One
As we are using DRF here we can add our own return responses with various media types, say for application/json.
First We need to add in our settings.py
REST_FRAMEWORK = {
...
'DEFAULT_RENDERER_CLASSES': (
'app_name.renderers.ApiRenderer', # our own render middleware
),
...
}
And in our custom render middleware
from rest_framework.renderers import BaseRenderer
from rest_framework.utils import json
class ApiRenderer(BaseRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
our_response_dict = {
'version': '1.0'
'data': {},
'message': '',
}
if data.get('data'):
our_response_dict['data'] = data.get('data')
if data.get('status'):
our_response_dict['statusCode'] = data.get('status')
if data.get('message'):
our_response_dict['message'] = data.get('message')
data = our_response_dict
return json.dumps(data)
Reference Link
Possible Solution Two
If we are using ModelViewset then there is another way we can achievement that. Say Our Views.py are like following
class A(serializer.ModelSerializer):
........
class B(serializer.ModelSerializer):
........
class C(serializer.ModelSerializer):
........
Our goal is to overwrite ModelViewset's to_representation function and return our custom result. This will like as following
from collections import OrderedDict
class OurParentViewset(serializer.ModelSerializer):
......
def to_representation(self, instance):
data = super(serializers.ModelSerializer, self).to_representation(instance)
result = OrderedDict()
result['data'] = data
result['version'] = '1.0'
result['statusCode'] = '2xx' # i am not fully sure how to customize this
return result
class A(OurParentViewset):
........
class B(OurParentViewset):
........
class C(OurParentViewset):
........
Implementing a custom renderer here seems to be a ways to go.
You can have requests from your android client include in the Accept header a way to identify the client to the renderer. 1 e.g.
Accept: application/json; android=true
Then compose a renderer using the JSONRenderer class to provide the format for your Android client.
# ./formatters/android_format.py
from rest_framework.renderers import JSONRenderer, BaseRenderer
from django.http.multipartparser import parse_header
class AndroidV1FormatRenderer(BaseRenderer):
media_type = 'application/json'
format = 'json'
json_renderer = JSONRenderer()
def android(self, accepted_media_type):
base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
return 'android' in params
def render(self, data, accepted_media_type=None, renderer_context=None):
response = renderer_context['response']
android = self.android(accepted_media_type)
if android:
data = {
"collection": {"data": data},
"statusCode": response.status_code,
"version": "1.0"
}
return json_renderer.render(
wrapped_data, accepted_media_type, renderer_context)
This can then be used where you require response formatted that way using renderer_classes attribute of your APIView. 2
Since get_queryset won't allow you to customize the response data. I decide to take the query value that's important to me.
http://localhost/api/users/?user_id=1 --> changed into ...api/users/1
def retrieve(self, request, *args, **kwargs):
""" userid = self.request.query_params.get('user_id', None) """
userid = kwargs.get('pk')
userExist = User.objects.filter(id=userid)
if userExist.exists():
# call the original 'list' to get the original response
queryset = LocationData.objects.values('source_id').filter(user__id=userid).order_by('-source_id')[:1]
lastSourceId = queryset[0]['source_id']
response = {"collection": {"data": lastSourceId,"statusCode": status.HTTP_200_OK,"version":"1.0"}}
# customize the response data
if response is not None:
return Response(response)
else:
# return response with this custom representation
response = {"collection": {"data": "","statusCode":status.HTTP_404_NOT_FOUND,"version":"1.0","error":"Not found"}}
return response
I would like to write a test for my DRF app that posts both json and a file using multipart.
This is what I have tried so far but collection_items (in the create method) is blank. Do I need to modify my view to make this work correctly, or am I doing something incorrectly within my test case below?
My Test:
image = Image.new('RGB', (100, 100))
tmp_file = tempfile.NamedTemporaryFile(suffix='.jpg')
image.save(tmp_file)
files = {"collection_items": [{"image": tmp_file}]}
payload = json.dumps({
"title": "Test Collection",
})
self.api_factory.credentials(Authorization='Bearer ' + self.token)
response = self.api_factory.post(url, data=payload, files=files, format='multipart')
This is the model:
class Collection(models.Model):
title = models.CharField(max_length=60)
collection_items = models.ManyToManyField('collection.Item')
class Item(models.Model):
image = models.ImageField(upload_to="/",null=True, blank=True)
Serializers:
class ItemCollectionDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('id', 'image')
read_only_fields = ('image',)
class CollectionListSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='col_detail')
collection_items = ItemCollectionDetailSerializer(many=True, required=True)
class Meta:
model = Collection
fields = ('url', 'id', 'collection_items')
def create(self, validated_data):
item_data = validated_data.pop('collection_items')
print(item_data) # <----- **EMPTY HERE???**
etc ....edited for brevity
So print(item_data) is empty [], why? How to I resolve this?
This is my entire view: below, do I need to do something here?
class CollectionListView(generics.ListCreateAPIView):
queryset = Collection.objects.all()
serializer_class = CollectionListSerializer
I am using Django Rest Framework 3.x, Django 1.8.x and Python 3.4.x.
Update
I have tried below but still no joy! collection_items is empty in my create. This either has to do with the fact it's a nested object or something has to happen in my view.
stream = BytesIO()
image = Image.new('RGB', (100, 100))
image.save(stream, format='jpeg')
uploaded_file = SimpleUploadedFile("temp.jpeg", stream.getvalue())
payload = {
"title": "Test Collection",
"collection_items": [{"image": uploaded_file}],
}
self.api_factory.credentials(Authorization='Bearer ' + self.test_access.token)
response = self.api_factory.post(url, data=payload, format='multipart')
Update 2
If I change my payload to use json.dumps it seems to now see the file but of course this cannot work!
payload = json.dumps({
"title": "Test Collection",
"collection_items": [{"image": uploaded_file}],
})
Error
<SimpleUploadedFile: temp.jpeg (text/plain)> is not JSON serializable
PS
I know the file is being uploaded because if I do the following in my serializer...
print(self.context.get("request").data['collection_items'])
I get
{'image': <SimpleUploadedFile: temp.jpeg (text/plain)>}
Using the multipart parser you can just pass the file handler in the post arguments (see this). In your code you are submitting a json-encoded part as the data payload and the file part in a files argument, and I don't think it can work that way.
Try this code:
from PIL import Image
from io import BytesIO
from django.core.files.uploadedfile import SimpleUploadedFile
stream = BytesIO()
image = Image.new('RGB', (100, 100))
image.save(stream, format='jpeg')
uploaded_file = SimpleUploadedFile("file.jpg", stream.getvalue(), content_type="image/jpg")
payload = {
"title": "Test collection",
"collection_items": [{"image": uf}],
}
self.api_factory.credentials(Authorization='Bearer ' + self.token)
self.api_factory.post(url, data=payload, format='multipart')
...
I'm not entirely sure the nested serialization works, but at least the file upload should work.