Upload file to Django with Javascript - javascript

I have simple fetch function and I want to upload an base64 image. The function is as follows:
function upload_to_server(canvasData){
console.log(canvasData); // that is data:image/png;base64,iVBORw0KGgoAAAANSUh.......
return fetch(api_url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: {photo: canvasData}
}).then(function (value) {
if(value.ok){
return value.json().then(function (response) {
debugger;
})
}
}).catch(function (reason) {
debugger;
})
}
And I have simple django view:
def upload_image(request):
print(request.POST)
pdb.set_trace()
It goes successful to that view when function upload_to_server gets called, but request.POST is empty. It shouldn't be empty, it should have key photo with that base64 value.
Any idea what I do wrong?

If someone else has the same problem. I solved it as follows.
I changed the body of fetch request to:
body: JSON.stringify({photo: canvasData})
and I changed django view, because data is in request.body and not in request.POST
import json
def upload_image(request):
body = json.loads(request.body)
photo = body['photo']
// Rest of the code
EXTRA
The photo is base64 encoded but I needed it as media file. I converted it to media file and saved it to database as follows:
def upload_image(request):
body = json.loads(request.body)
photo = body['photo']
img_format, img_str = photo.split(';base64,')
ext = img_format.split('/')[-1]
data = ContentFile(base64.b64decode(img_str), name='temp.' + ext) # You can save this as file instance.
try:
n_file = Files()
n_file.file = data
n_file.save()
return JsonResponse({'status': 'success'})
except Exception as err:
return JsonResponse({'status': 'failed'})

Related

AttributeError: 'NoneType' object has no attribute 'decode' when sending a form through JS

I'm trying to send some data through a form using JavaScript Fetch to a Django view, including an image. I keep getting this error message nine times as if no data was sent to the back end:
My view is as follows:
if "contributors" in data:
try:
Project.objects.get(title=data['title'])
return JsonResponse({'error': 'Project title already exists!'}, status=406)
except Project.DoesNotExist:
form = ProjectForm(request.POST, request.FILES)
project = Project.objects.create(
title=data['title'],
description=data['description'], logo=data['logo'])
return JsonResponse({"message": "Project successfully created!"}, status=201)
and my JavaScript:
const projectName = document.getElementById("project_name");
const contributors = document.getElementById("id_contributors");
const description = document.getElementById("id_description");
const logo = document.getElementById("id_logo");
const projectCsrf = document.getElementsByName("csrfmiddlewaretoken")[0];
document.getElementById("submitProjectForm").addEventListener("click", () => {
let formData = {
title: projectName.value,
contributors: contributors.value,
description: description.value,
logo: logo.files[0],
};
submitForm(projectCsrf, formData);
});
function submitForm(csrf, fields) {
const request = new Request(window.location.origin, {
headers: {
"X-CSRFToken": csrf.value,
"Content-Type": "multipart/form-data",
},
});
fetch(request, {
method: "POST",
body: JSON.stringify(fields),
})
.then((response) => response.json())
.then((result) => alert(result.message ? result.message : result.error))
.catch((err) => console.log(err));
}
is it maybe due to python's Json.loads method not being able to decode the JavaScript File object? Thanks in advance!
As you are including an image in your data and your data is a formdata why you are converting it to a string:
fetch(request, {
method: "POST",
body: JSON.stringify(fields),
})
I think you should add formdata itself to body of fetch api
fetch(request, {
method: "POST",
body: fields,
})
So after hours of debugging I managed to figure it out. Ali javanmardi was partially right in that i should not have been converting the data to JSON because I was sending files.
The main issue for this error was in my headers in my fetch function:
"Content-Type": "multipart/form-data",
This appeared to be causing the main issue, when I changed this to:
"X-Requested-With": "XMLHttpRequest"
it worked.
I also converted all of the data collected from the form into FormData, rather than creating my own object:
let formData = new FormData();
formData.append("logo", logo.files[0]);
formData.append("title", projectName.value);
formData.append("contributors", contributors.value);
formData.append("description", description.value);
On the back end in my view, I should have been saving the form instance instead of creating a new Project object:
new_form = ProjectForm(request.POST, request.FILES)
if new_form.is_valid():
new_form.save()
Now I can upload images fine.

Passing multipart form data from python to django server with both file and data

I have a Django server and a react frontend application. I hava an endpoint that receives both data and file object. The javascript version of the api client is working fine and it looks something like this.
const address = `${baseAddress}/${endpoint}`;
const multipartOptions = {
headers: {
'Content-Type': 'multipart/form-data',
'Content-Disposition': `attachment; filename=${filename}`,
'X-CSRFToken': getCSRFToken(),
},
};
const formData = new FormData();
formData.append('file', file);
const json = JSON.stringify(metadata);
const blob = new Blob([json], {
type: 'application/json',
});
formData.append('metadata', blob);
return axios.post(address, formData, multipartOptions);
So as you can see I am using a blob to add metadata to my form data and passing it to the server.
Printing the request.data in the server gives me something like this.
<QueryDict: {'file': [<InMemoryUploadedFile: admin_12183.zip (application/zip)>], 'metadata': [<InMemoryUploadedFile: blob (application/json)>]}>
So I can access both request.data.get('file') and request.data.get('metadata') on my django server.
Now I have to do something similar in python. I tried using requests to get the stuff right, but I don't get two separate keys in the QueryDict. The python code looks like this.
with open("file.zip", "rb") as fp:
with open("metadata.json", "rb") as meta:
file_headers = {
**headers,
'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryjzAXwA7GGcenPlPk',
}
data = {
"file": "",
}
files = {
'file': ("file.zip", fp, "application/zip"),
'metadata': ("blob", meta, "application/json"),
}
response = requests.post(f"{BASE_URL}/api/endpoint", data=data, files=files, headers=file_headers)
print(response.status_code)
If I do not send in both files and data at the same time, I get nothing in request.data. And if I send both of them, I am getting both of the data in a single key, that corresponds to whatever key I have in the data variable.
The server has this code in it
def post(self, request, *args, **kwargs):
file_obj = request.data.get('file')
metadata = request.data.get('metadata')
# both are empty if either one of files or data is not sent from the client
# if both are sent, then request.data has only one key, with everything inside of it
# works fine with the javascript code
I think I am missing something very small and trivial.
Please help.
Turns out I was adding a content-type in my header and that is what was causing all the issues. The working code looks like this
file_headers = {
**headers,
# make sure there is no content type in the header
}
files = {
'file': ("file.zip", shape, "application/zip"),
'metadata': ("blob", meta, "application/json"),
}
response = requests.post(f"{BASE_URL}/api/shapefiles", files=files, headers=file_headers)
print(response.text, response.status_code)

How can I post a json object and a file to asp.net server with fetch

I'm a bit stuck here, I'm trying to post a json object from my indexedDb simultaneously with a IFormFile object to the server. The method that accepts it looks like this:
[HttpPost]
public async Task<IActionResult> Create(BatchModel model, IFormFile vinFile)
{
//logic goes here
}
This method worked earlier, before I had to store my batchModels as Json objects, and were just posted straight from the form with a POST. A lot has changed since then, and now the client will upload it as soon as he gets online again from an offline state, with the following (simplified) method:
if (infoChanged === true) {
fetchPromises.push(
fetch('/Batch/Create/', {
headers: new Headers({
'Content-Type': 'application/javascript' //Tried this with multi-part/form
}),
credentials: 'same-origin',
method: 'POST',
body: batch //, vinFile
})
);
}
return Promise.all(fetchPromises);
For testing I tried to use the method with only the model filled, and the only thing I had to change was that I had to change in the C# code was adding a [FromBody] tag, but now I need the vinFile filled as well.
I tried it already with a FormData object, appending the batch and the vinFile with the same name as in the Create function. But that would result in both variables as null.
The key feature you need to look at is your header. You can change your response to JSON if you do header: { Content-Type: "application/json" }
You want something like this: You need to configure the type yourself.
fetch(myRequest).then(function(response) {
var contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); });
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

Python 2.7 - Access Javascript file object in Django

I am building an app that sends multiple files as email attachments, using jQuery and Django 1.9.
I store the files in a buffer that the user can add and delete from, and sending them over a POST Ajax request as such:
//Build the message
message_buffer.forEach(function(entry){
body += '\n' + entry;
});
var files = $.merge(attachment_buffer.photos, attachment_buffer.documents);
var form = new FormData();
form.append("csrfmiddlewaretoken", csrf_token);
form.append("client_id", client_id);
form.append("subject", subject);
form.append("body", body);
form.append("files", files);
$.ajax({
url: window.location.origin + '/dashboard/ajax/send_message',
method: "post",
data: form,
processData: false,
contentType: false,
cache: false,
beforeSend: function(){
//Block UI
},
success: function(data){
if(data.status == 'success'){
console.log(data);
//Show success and clear all the data stores.
} else {
console.log(data.message);
}
},
error: function(err){
console.log(err.responseText);
}
});
Problem is when i get this buffer (a list of JS file objects) in my django view, they are gotten as unicode and i dont know how to parse them.
I need to be able to attach the files to the django EmailMessage instance like this:
for attachment in attachments:
mail.attach(attachment.name, attachment.read(), attachment.content_type)
The Django view code is:
if request.method == "POST":
client_id = request.POST['client_id']
subject = request.POST['subject']
body = request.POST['body']
attachments = []
if 'files' in request.POST.keys() and request.POST['files'] != '':
attachments = request.POST['files']
client = Client.get_client_by_id(request.user, client_id)
if client:
email_helper = EmailHelper()
email_sent = email_helper.send_email_with_attachments(request, client, subject, body, attachments)
And the email method:
def send_email_with_attachments(self, request, client, subject, message, attachments, from_email=settings.EMAIL_HOST_USER):
"""
Sends a simple text mail with attachments
:param request:
:param client:
:param subject:
:param message:
:param from_email:
:param attachments:
:return:
"""
# print type(encoding.smart_bytes(attachments))
# # for attachment in attachments:
# # print json.loads(attachment)
# #
# return False
try:
mail = EmailMessage(
self.clean_email_params(subject),
self.clean_email_params(message),
self.clean_email_params(from_email),
self.clean_email_params([client.email]),
reply_to=[from_email]
)
for attachment in attachments:
mail.attach(attachment.name, attachment.read(), attachment.content_type)
mail.send()
try:
# Log the sent email
email_log = SentEmailsLog()
email_log.user = request.user
email_log.client = client
email_log.subject = self.clean_email_params(subject)
email_log.content = self.clean_email_params(message)
email_log.to_email = str(self.clean_email_params([client.email]))
email_log.from_email = self.clean_email_params(from_email)
email_log.host_email = settings.EMAIL_HOST_USER
email_log.attachments = 'No'
email_log.save()
except Exception, e:
ErrorLogHelper.log_error(error_message=e, calling_function="EmailHelper.send_email_with_attachments")
return True
except Exception, e:
ErrorLogHelper.log_error(error_message=e, calling_function="EmailHelper.send_email_with_attachments")
return False
Please advice, thank you.
Try to explicitly encode your request body in view.py, like so:
body = request.POST['body'].encode('utf8')

Flask server cannot read file uploaded by POST request

I have my React client post a file with the fetch api to the '/dataset' endpoint.
import 'whatwg-fetch';
uploadData(csv) {
this.dataset = csv;
fetch('/dataset', {
method: 'POST',
body: this._fileToFormData(csv)
}).then(
(response) => {
console.log(response);
}
).catch( () => {} );
};
_fileToFormData(file) {
var formData = new FormData();
formData.append('file', file);
return formData
};
My Flask server is supposed to pick it up.
#app.route('/dataset', methods=['POST'])
def dataset():
print request.get_data()
csv_data = request.form['file']
print csv_data
return '{ "fake_json":100}', 200
However, the csv_data object is simply a unicode string, '[object File]'
The code
print "form:", request.form
print "files:", request.files
returns
ImmutableMultiDict([('file', u'[object File]')])
ImmutableMultiDict([])
How do I get the actual contents of the CSV file?
=== EDIT: Solved ===
the variable csv was actually a single file array, so I needed to extract the file out.
Uploaded files are available in request.files, not request.form. The values are file-like objects, so to get the data you need to read the file.
data = request.files['file'].read()
See the Flask docs for some examples on working with uploads.
You also need to upload the file correctly. GitHub's fetch polyfill has an example using FormData to format the body properly. You must pass a single file or input to each call to append.
var input = document.querySelector('input[type="file"]')
var data = new FormData()
data.append('file', input.files[0])
fetch('/dataset', {
method: 'POST',
body: data
})

Categories

Resources