I am trying to upload an image from Ionic 2 app to Django-powered website through Django Rest API.
The API is working and tested through Postman but I always get HTTP 400 BAD Request error in Ionic.
Here is my code in Ionic:
openCamera(){
var options = {
sourceType: Camera.PictureSourceType.CAMERA,
destinationType: Camera.DestinationType.DATA_URL
};
Camera.getPicture(options).then((imageData) => {
this.imageName = imageData;
this.imageURL = 'data:image/jpeg;base64,' + imageData;
}, (err) => {
this.showAlert(err);
});
}
Upload file (I am serving my Django project on my local PC with IP address 192.168.22.4):
transferData(auth){
let headers = new Headers();
headers.append('Authorization', auth);
let formData = new FormData();
formData.append('image', this.imageURL, this.imageName);
this.http.post("http://192.168.22.4/api-imageUpload", formData, {headers: headers}).subscribe(res => {
let status = res['status'];
if(status == 200){
this.showAlert( "The image was successfully uploaded!");
}else{
this.showAlert("upload error");
}
}, (err) => {
var message = "Error in uploading file " + err
this.showAlert(message);
});
}
On Django, here is my serializer:
class ImageDetailsSerializer(serializers.ModelSerializer):
image = serializers.ImageField(max_length=None, use_url=True)
class Meta:
model = ImageDetails
fields= ('image','status','category', 'user') ####status, category has default value
and views.py:
class ImageDetailsViewSet(generics.ListCreateAPIView):
queryset = ImageDetails.objects.all()
serializer_class = ImageDetailsSerializer
I am not sure if my code in uploading file is correct. I am trying to pass the data through Form data since the form works well in my API. Is this method correct? Are there any other methods to get this work?
Note: I have tried to use Transfer Cordova plugin but it is not working.
I finally solved the problem. The HTTP 400 indicates that there is a syntax error somewhere in the code and that is the encoding used in the uploaded photo. Mobile data uses base64 encoding. When sending requests, the file will then be converted to a Unicode string.
On the other hand, Django-Rest uses normal encoding for images, thus by default, it cannot support base64 image. But luckily, this plugin is already available at GitHub.
You just need to install the plugin and import it on your serializers.py:
from drf_extra_fields.fields import Base64ImageField
class ImageDetailsSerializer(serializers.ModelSerializer):
image = Base64ImageField()
class Meta:
model = ImageDetails
fields= ('image','status','category', 'user')
On Ionic side, you have to submit the actual image not the imageURL. In my case I just have to tweak my code to:
transferData(auth){
let headers = new Headers();
headers.append('Authorization', auth);
let formData = new FormData();
formData.append('category', 1);
formData.append('status', 'Y')
formData.append('image', this.imageName);
this.http.post("http://192.168.22.4/api-imageUpload", formData, {headers: headers}).subscribe(res => {
let status = res['status'];
if(status == 201){
var message = "The image was successfully uploaded!";
this.showAlert(message);
}else{
var message = "upload error";
this.showAlert(message);
}
}, (err) => {
var message = "Error in uploading file " + err;
this.showAlert(message);
});
In order to inspect what's ongoing with the request:
from rest_framework.exceptions import ValidationError
class ImageDetailsViewSet(generics.ListCreateAPIView):
queryset = ImageDetails.objects.all()
serializer_class = ImageDetailsSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid():
print(serializer.errors) # or better use logging if it's configured
raise ValidationError(serialize.errors)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Even without Base64 it is possible using ionic native components for file-transfer and image-picker see here: https://gist.github.com/AndreasDickow/9d5fcd2c608b4726d16dda37cc880a7b
Related
I am working on a project using django 3.1 for backend and vue 3 for frontend.
I am pretty new to Django so I am still learning the ropes and I do not know if what I am trying is totally wrong.
I created a model that holds a user email and and an image field as follows:
class UsedBike(models.Model):
sellerEmail = models.CharField(max_length=255)
image = models.ImageField(upload_to='uploads/', blank=True, null=True)
class Meta:
ordering = ('sellerEmail', )
def __str__(self):
return self.sellerEmail
def get_image(self):
if self.image:
return 'http://127.0.0.1:8000' + self.image.url
return ''
I created a serializer for my model as follows:
class UsedBikeSerializer(serializers.ModelSerializer):
class Meta:
model = UsedBike
fields = (
"id",
"sellerEmail",
"get_image",
)
and in the views file, I created a function for saving the data in the database:
#api_view(['POST'])
def sellBike(request):
serializer = UsedBikeSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
I also registered the url in the urls file.
As for vue part, I used axios to send my post request as follows:
submitData() {
const formData = {
sellerEmail: this.sellerEmail,
image: this.productImage
}
axios
.post("/api/v1/sell-bike/", formData)
.then(response => {
console.log(response)
}).catch(error => {
console.log(error)
})
},
where in the template I am getting the inputs like this:
<input type="text" v-model="sellerEmail">
<input type="file" accept="image/png, image/jpeg" id="imageInput" v-on:change="onFileChange()">
<button #click="submitData">Upload</button>
and the method onFileChange is:
onFileChange(e) {
let imageInput = document.getElementById("imageInput")
this.productImage = imageInput.files[0]
},
When I send the request, I get "POST /api/v1/sell-bike/ HTTP/1.1" 200 55 in the django terminal. However, If I print request.data I get the following:
{'sellerEmail': 'test#email.com', 'image': {}}
As you can see, image is empty and when I checked the database, the email part is filled correctly but image is empty.
How can I solve this problem?
Any help is appreciated.
IN your axios call, you need to include the below header as well:
submitData() {
const formData = new FormData();
formData.append('sellerEmail', this.sellerEmail);
formData.append('image', this.productImage);
const headers = {headers: { 'Content-Type': 'multipart/form-data' }}
axios
.post("/api/v1/sell-bike/", formData, headers)
.then(response => {
console.log(response)
}).catch(error => {
console.log(error)
})
}
Also, to save image to database, you also need to include the "image" in fields inside UsedBikeSerializer
I am trying to update a User Profile by making a multipart/form-data put request (from my vue frontend using axios) containing a png blob image file. I receive an error message: File extension “” is not allowed.
This is the File Field on the Userprofile Model:
profile_picture = models.FileField(
_("Profile Pictures"),
upload_to="profile_picture",
max_length=100,
blank=True,
null=True,
)
These are the signals I use in the Userprofile model to save and update the Model.
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
I think it may be because of these or something other specific to the Userprofile model that creates the error because on another model the file upload works as intended, although there I am making a post and not a put request. The Userprofile model is connected to the User by a One to One Field.
Ther is nothing special in my serializer or views that could be causing the bug.
I have no Idea what I could do to fix this. Thanks for all advice. If you need any other information feel free to ask.
The image is the formdata sent with the put request... maybe somethings wrong there:
The axios code that makes the request:
import axios from 'axios'
const apiClient = axios.create({
baseURL: `http://127.0.0.1:8000/`,
withCredentials: false,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data'
}
})
export default {
updateUser(pk, params) {
return apiClient.put('/users/' + pk + '/', params)
}
}
This is the part where I crop a picture and it is a dataURI which I then convert to a blob to send to the backend server:
methods: {
crop() {
const { coordinates, canvas } = this.$refs.cropper.getResult()
this.coordinates = coordinates
this.file = canvas.toDataURL()
},
uploadImage(event) {
const input = event.target
if (input.files && input.files[0]) {
// create a new FileReader to read this image and convert to base64 format
const reader = new FileReader()
// Define a callback function to run, when FileReader finishes its job
reader.onload = (e) => {
// Read image as base64 and set to imageData
this.file = e.target.result
}
// Start the reader job - read file as a data url (base64 format)
reader.readAsDataURL(input.files[0])
}
},
dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string
const byteString = atob(dataURI.split(',')[1])
// separate out the mime component
const mimeString = dataURI
.split(',')[0]
.split(':')[1]
.split(';')[0]
// write the bytes of the string to an ArrayBuffer
const ab = new ArrayBuffer(byteString.length)
const ia = new Uint8Array(ab)
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i)
}
return new Blob([ab], { type: mimeString })
},
async updateUser() {
this.crop()
delete this.user.password2
const formData = new FormData()
if (this.file)
formData.append('profile_picture', this.dataURItoBlob(this.file))
if (this.user.username) formData.append('username', this.user.username)
else formData.append('username', this.$auth.user.username)
if (this.user.email) formData.append('email', this.user.email)
if (this.user.bio) formData.append('bio', this.user.bio)
if (this.user.password) formData.append('password', this.user.password)
else formData.append('password', this.$auth.user.password)
await UserFormService.updateUser(this.$auth.user.pk, formData)
await this.$store.dispatch('users/updateUser', this.user)
this.$auth.setUser(this.$store.state.users.user)
this.$router.push('/users/me')
Since your endpoint requires a specific file name extension you will have to set it manually because blobs do not have file names and the default used for a file upload is blob. There is a third optional parameter in FormData.append to set a file name
formData.append('profile_picture', this.dataURItoBlob(this.file), 'some_filename.valid_extension')
I want to save file from the client to the django project server's database from a script. I've tried to do this using a model and a view in the django project, and post request in the other python script, but it keeps return 403 error and not save the file and the data to the database.
models.py:
class ScreenRecord(models.Model):
record = models.FileField(default='output.avi', upload_to='records')
writeTime = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
views.py:
def getscreenrecords(request):
user = User.objects.filter(username=request.POST.get('user')).first()
k = ScreenRecord(record=request.FILES.get('record'), user=user)
k.save()
return HttpResponse('success ' + request.GET.__getitem__('user'))
url.py:
from . import views
urlpatterns = [
path('screenrecords/', views.getscreenrecords, name='getscreenrecords'),
]
python script to send the file:
url = 'http://127.0.0.1:8000/send/screenrecords/'
files = {'record': open('output.avi','rb')}
values = {'user': 'newUser'}
r = requests.post(url, files=files, data=values)
print(r)
what's wrong in my code or is there a way to do this better?
Django needs a CSRF token in POST requests by default.
Check this for more info on how to use it on your requests.
You need to pass csrf_token along with the data passed in your js, if you are doing it within the Django project, here is a sample code to do it.
<script>
var token = '{{csrf_token}}';
$("#id_username").change(function () {
console.log($(this).val());
var form = $(this).closest("form");
$.ajax({
headers: { "X-CSRFToken": token },
url: form.attr("data-validate-username-url"),
data: form.serialize(),
dataType: 'json',
success: function (data) {
if (data.is_taken) {
alert(data.error_message);
}
}
});
});
</script>
I'have rest api devlopped with djago and application front devlopped with agular7 and i try to upload image to my rest api when i try to send it with form data the form data is empty in the api.
for angular i try to send form data with file.
Angular:
getPredictionImage(file): Observable<any> {
const HttpUploadOptions = {
headers: new HttpHeaders({ 'Content-Type': 'multipart/form-data'})
}
const f = new FormData();
f.append('image', file, file.name);
console.log(f);
return this.http.post(this.urlapiimage, file, HttpUploadOptions);
}
Django:
def post(self, request, format=None):
print("heloooo")
print(request.data)
serializer = MammographySerializer(data=request.data)
print(serializer)
if serializer.is_valid():
result='hi'
serializer.save()
return Response(result,status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
the request.data is empty
it worked for me may be this help u to find your solution
Angular :
upload(file): Observable<any> {
let csrftoken = getCookie('csrftoken');
let headers = new HttpHeaders({
'Access-Control-Allow-Origin': '*',
'X-CSRFToken': csrftoken,
});
const formdata = new FormData();
formdata.append("image", file); //i did not use 3rd argument file.name
let options = {
headers: headers, withCredentials :true }
return this.http.post(this.urlapiimage , formdata, options )
}
Django:
def post(self, request, format=None):
if 'file' not in request.data:
raise ParseError("Empty content ")
photo = request.data["file"]
serializer = MammographySerializer(photo = photo) // use this instead data =request.data
if serializer.is_valid():
result='hi'
serializer.save()
return Response(result,status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
after trying this , than also u r getting error , read about DRF parser ( https://www.django-rest-framework.org/api-guide/parsers/)
"important point if u r using chrome and if r using http://127.0.0.1:8000/ for django server and localhost:4200/ for angular than because of CORS(different ports) chrome does not allow . use this 127.0.0.1:4200/ for angular server instead of localhost:4200/ and if u don't want to do this use firefox instead of chrome it allows CORS
Objective is to accept UI multiple parameters and give it to the model (127.0.0.1:5002)using flask API and then the scoring from the model post back to UI (127.0.0.1:5001)
I am getting error (which is posted below at the end) when a model accept the value from the UI.
So i am posting values to 127.0.0.1:5002 where model takes that as 1 json object but i am getting error.
So i post 1 json object from this code (let me know if there is an issue in the code- i am a newbie)
<script>
$(function() {$('#analysis').bind('click', function() {
$.post('http://127.0.0.1:5002/',{
'CK': $('CK').val(),
'OCE': $('OCE').val(),
'range_04': $('range_04').val(),
},
function(data) {
var parsed = JSON.parse(data);
$("#xyz").text(parsed['abc']);
});
return false;
});
});
</script>
Now this code generates the json (and that json object feeds the model)
app = Flask(__name__)
api = Api(app)
parser = reqparse.RequestParser()
parser.add_argument('args.xyz')
class getPredProb(Resource):
def post(self):
args = parser.parse_args()
clf = joblib.load('AO.pkl')
frameToScore = pandas.read_json('args.xyz')
prediction = clf.predict(frameToScore)
probability = clf.predict_proba(frameToScore)
return json.dumps({'Prediction': prediction},{'Probability':probability}), 201, {'Access-Control-Allow-Origin': 'http://127.0.0.1:5001'}
api.add_resource(getPredProb, '/')
if __name__ == '__main__':
#http_server = WSGIServer(('', 5002), app)
#http_server.serve_forever()
app.run(debug=True,port=5002)
Image of ERROR
You're only passing a string, 'args.xyz', to read_json, you should use args['xyz'] (assuming this is the json data, as I don't see anything with an xyz key being passed to the backend).