I am trying to intergrate File Pond with my Flask server and I can not find the unique file id it sends with requests. With normal uploads it will show me request data on the server, but without the unique ID I am looking for. I want to use this unique ID to attach to a database in order to delete the file if the revert is sent later.
Going over the docs, it is suppsoed to send a unique ID with every upload, but I can't tell if it's a bad config in file pond or I suck at reading request.files. Just ignore all my prints where I try to read the contents of request.
Here is my FilePond Config:
FilePond.setOptions({
//allowImageEdit: true,
//allowImageCrop: true,
//allowMultiple: true,
//allowFileEncode: true,
allowImageExifOrientation: true,
credits: false,
server: {
process: {
url: './pondupload',
method: 'POST',
headers: {
'x-customheader': 'Hello World',
},
withCredentials: document.getElementById('csrf_token'),
onload: (response) => response.key,
onerror: (response) => response.data,
ondata: (formData) => {
formData.append('csrf_token', document.getElementById('csrf_token').value)
return formData;
},
},
revert: './pondupload',
restore: './restore/',
load: './load/',
fetch: './fetch/',
},
});
Here is my route in flask:
#bp.route('/pondupload', methods=['POST', 'DELETE'])
def pondupload():
print ('data: '+str(request.data))
print('form: '+str(request.form))
print('files: '+str(request.files))
print('args: '+str(request.args))
form=EmptyForm()
#TODO add csrf to revert
if request.method == "DELETE":
print('delete stuff')
if request.method == "POST" and form.validate_on_submit():
upload_dir = current_app.config['UPLOAD_FOLDER']
fn = ""
file_names = []
# get file object from request.files (see: http://flask.pocoo.org/docs/1.0/api/#flask.Request.files)
for key in request.files:
print('key: '+str(key))
file = request.files[key]
fn = secure_filename(file.filename)
if allowed_file(fn) == True:
file_names.append(fn)
try:
file.save(os.path.join(upload_dir, fn))
return jsonify({}), 200
except:
return jsonify(filename=file_names), 402
return jsonify({}), 400
Related
I am trying to make a METAR decoder as shown:
I am using fetch in Vanilla js and I plan to send the entered code to a Django view. From the Django view, the decoded data will be taken and displayed in the template.
views.py
def ToolsPageView(request):
if request.method == "POST":
jsonData = json.loads(request.body)
metarCode = jsonData.get('Metar')
return JsonResponse("Success", safe=False)
return render(request, 'app/tools.html')
urls.py
...
path("tools", views.ToolsPageView, name="tools")
tools.html
<div class="metar-code-decode">
<form method="POST" action="{% url 'tools' %}" id="metar-form">
{% csrf_token %}
<input type="text" placeholder="Enter METAR: " id="metar-value"> <br>
<input type="submit" id="metar-button">
</form>
</div>
tool.js
function getDecodedMetar() {
let formButton = document.querySelector("#metar-button");
formButton.onclick = function (e) {
let metarCode = document.querySelector("#metar-value").value;
sendMetar(metarCode);
//e.preventDefault();
//getMetar(metarCode);
};
}
function sendMetar(metarCode) {
fetch('/tools', {
method: "POST",
headers: {
"X-CSRFToken": getCookie("csrftoken"),
},
body: JSON.stringify({
Metar: metarCode,
}),
});
}
I have used the same code for POST using fetch where I had to let user update his/her profile. And that worked. But, this does not work and the error keeps on changing from time to time after restarting the server. At the first try, there was no error produced and the server also showed a POST request being made. And the latest error which I am getting is "In order to allow non-dict objects to be serialized set the safe parameter to False." I get the same thing again and again even after setting safe=False within the JsonResponse(). Worth to note, request when converted to request.json() gives an error.
Am I using fetch wrongly? If yes, what is the correct way?
I'm not sure you have the flow right. The idea is that the button, when clicked, will call a function (fetch) that will send data to the view, which will decode it and send it back to the JavaScript, so that it could be displayed without reloading the entire page.
I think this might help:
let formButton = document.querySelector("#metar-button");
// When the button is clicked,
formButton.onclick = function(e) {
// do NOT send the form the usual way
e.preventDefault();
let metarCode = document.querySelector("#metar-value").value;
// Run the function that will send the code to the ToolsPageView
sendMetar(metarCode);
}
async function sendMetar(metarCode) {
const response = await fetch('/tools', {
method: "POST",
headers: {
"X-CSRFToken": getCookie("csrftoken"),
},
body: JSON.stringify({
'Metar': metarCode,
}),
})
.then(response => response.json())
.then(data => {
console.log(data);
// extract the decoded value from the data sent back from the view
// display it by targeting the element in your html that you want
// to display it
});
}
And in your view,
def ToolsPageView(request):
if request.method == "POST":
jsonData = json.loads(request.body)
metarCode = jsonData.get('Metar')
# Remove the original JsonResponse
# return JsonResponse("Success", safe=False)
# and INSTEAD,
# Send the code back to the JavaScript
# I don't THINK you need safe=False here?
return JsonResponse({'MetarCode': metarCode})
return render(request, 'app/tools.html')
I am using Django 3.1.2 to build a website, and I want the user of my website to be able to download a .zip file created on the backend when they click a button on the webpage.
However, when testing, I found that the download would not start when the button is clicked. No error was thrown either, and I just could not work out where I went wrong.
JavaScript:
document.querySelector("#download").onclick = function() {
// #download is the button I am talking about; it is in reality a div element
var formData = new FormData();
for (let i = 2; i < formElementsNumber; i++) {
let element = formElements[i];
formData.append(element.name, element.value);
}
formData.append("txtFile", formElements[1].files[0]);
formData.append("token", token);
// Up to here, everything works fine
$.ajax({
url: "/download/",
type: 'post',
data: formData,
// Essential; otherwise will receive an "illegal invocation" error
contentType: false,
processData: false,
success: function(data) {
// What should I add here to make the downloading start?
}
})
}
urls.py:
urlpatterns = [
path('', views.index),
path('process/', views.process),
path('download/', views.download)
]
views.py:
def download(request):
// Creates an .zip file
property_list = ['imageWidth', 'imageHeight', 'fontFamily', 'fontSize', 'rotationDirection', 'rotationAngle', 'foregroundColor', 'backgroundColor', 'token']
image_info = {}
for item in property_list:
image_info[item] = request.POST.get(item)
image_info['txtFile'] = request.FILES.get('txtFile').read().decode('gbk').split('\r\n')
// zip_name is the absolute path to the newly created .zip file
zip_name = w2p.make_zip(image_info, image_info['token'])
// Everything above works fine; the .zip file is successfully created
// What modification should I make here to make the downloading start properly
with open(zip_name, 'rb') as f:
response = HttpResponse(f.read())
response['content_type'] = "application/octet-stream"
response['Content-Disposition'] = 'attachment; filename=word2pic.zip'
return response
A thousand thanks in advance for your time.
import os
from django.conf import settings
from django.http import HttpResponse, Http404
def download(request, path):
file_path = os.path.join(settings.MEDIA_ROOT, path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(fh.read(), content_type="application/filename")
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
return response
raise Http404
I am working on a django website and have come across an error which I am not able to solve and Its been a couple of days but I wasnt able to solve the error. I am trying to integrate a payment gateway to my site. I have a payment button which on click is supposed to post the data to the database as well as go to the payment site. But it is storing the data but not going to payment site.
Here is the javascript for my button:
document.getElementById('payment-info').addEventListener('click', function (e) {
submitFormData()
})
function submitFormData() {
console.log('Payment Button Clicked')
var userFormData = {
'name': null,
}
var shippingInfo = {
'address': null,
}
shippingInfo.address = form.address.value
userFormData.name=form.name.value
var url = "/process_order/"
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken,
},
body:JSON.stringify({'form': userFormData, 'shipping': shippingInfo }),
})
.then((response) => response.json())
.then((data) => {
console.log('Success:', data);
alert('Transaction Completed')
window.location.href = "{% url 'index' %}"
})
}
</script>
This is my views.py:
def processOrder(request):
transaction_id = datetime.datetime.now().timestamp()
data = json.loads(request.body)
if request.user.is_authenticated:
customer=request.user.customer
order, created=Order.objects.get_or_create(customer=customer, complete=False)
total=float(data['form']['total'])
order.transaction_id=transaction_id
if total == order.get_cart_total:
order.complete = True
order.save()
ShippingAddress.objects.create(
customer=customer,
order=order,
address=data['shipping']['address'],
name=data['form']['name'],
)
//Code for integration below
param_dict = {
'MID': 'DIY12386817555501617',
'ORDER_ID': str(order.id),
'TXN_AMOUNT': '4',
'CUST_ID': 'j',
'INDUSTRY_TYPE_ID': 'Retail',
'WEBSITE': 'WEBSTAGING',
'CHANNEL_ID': 'WEB',
'CALLBACK_URL':'http://127.0.0.1:8000/handlerequest/',
}
param_dict['CHECKSUMHASH'] = Checksum.generate_checksum(param_dict, MERCHANT_KEY)
return render(request, 'paytm.html', {'param_dict': param_dict})
return JsonResponse('Done',safe=False)
#csrf_exempt
def handlerequest(request):
# paytm will send you post request here
form = request.POST
response_dict = {}
for i in form.keys():
response_dict[i] = form[i]
if i == 'CHECKSUMHASH':
checksum = form[i]
verify = Checksum.verify_checksum(response_dict, MERCHANT_KEY, checksum)
if verify:
if response_dict['RESPCODE'] == '01':
print('order successful')
else:
print('order was not successful because' + response_dict['RESPMSG'])
return HttpResponse('doneee')
And this is my urls.py:
path("process_order/",views.processOrder,name="process_order"),
path("handlerequest/",views.handlerequest,name="handlerequest"),
This code is not working . After clicking the payment button I am getting this error:
Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
This error is referring to the submitformdata() in my javascript code.
I want to workout a way so that once a person clicks on the payment button and his transaction is completed then his data is submitted to the database otherwise not for which I have written the code in handlerequest.
Please help me out.
I haven't been able to figure out how to get my JavaScript to send a request in a format that Rails will accept when I try to edit a Game with a File parameter and an array parameter in the same payload.
The Rails controller looks like this (simplified, obviously):
class GamesController < ApplicationController
def update
#game = Game.find(params[:id])
authorize #game
respond_to do |format|
if #game.update(game_params)
format.html { render html: #game, success: "#{#game.name} was successfully updated." }
format.json { render json: #game, status: :success, location: #game }
else
format.html do
flash.now[:error] = "Unable to update game."
render :edit
end
format.json { render json: #game.errors, status: :unprocessable_entity }
end
end
end
private
def game_params
params.require(:game).permit(
:name,
:cover,
genre_ids: [],
engine_ids: []
)
end
end
So I have JavaScript like so:
// this.game.genres and this.game.engines come from
// elsewhere, they're both arrays of objects. These two
// lines turn them into an array of integers representing
// their IDs.
let genre_ids = Array.from(this.game.genres, genre => genre.id);
let engine_ids = Array.from(this.game.engines, engine => engine.id);
let submittableData = new FormData();
submittableData.append('game[name]', this.game.name);
submittableData.append('game[genre_ids]', genre_ids);
submittableData.append('game[engine_ids]', engine_ids);
if (this.game.cover) {
// this.game.cover is a File object
submittableData.append('game[cover]', this.game.cover, this.game.cover.name);
}
fetch("/games/4", {
method: 'PUT',
body: submittableData,
headers: {
'X-CSRF-Token': Rails.csrfToken()
},
credentials: 'same-origin'
}).then(
// success/error handling here
)
The JavaScript runs when I hit the submit button in a form, and is supposed to convert the data into a format Rails' backend will accept. Unfortunately, I'm having trouble getting it to work.
I'm able to use JSON.stringify() instead of FormData for submitting the data in the case where there's no image file to submit, like so:
fetch("/games/4", {
method: 'PUT',
body: JSON.stringify({ game: {
name: this.game.name,
genre_ids: genre_ids,
engine_ids: engine_ids
}}),
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': Rails.csrfToken()
},
credentials: 'same-origin'
})
This works fine. But I haven't been able to figure out how to use JSON.stringify when submitting a File object. Alternatively, I can use a FormData object, which works for simple values, e.g. name, as well as File objects, but not for array values like an array of IDs.
A successful form submit with just the ID arrays (using JSON.stringify) looks like this in the Rails console:
Parameters: {"game"=>{"name"=>"Pokémon Ruby", "engine_ids"=>[], "genre_ids"=>[13]}, "id"=>"4"}
However, my current code ends up with something more like this:
Parameters: {"game"=>{"name"=>"Pokémon Ruby", "genre_ids"=>"18,2,15", "engine_ids"=>"4,2,10"}, "id"=>"4"}
Unpermitted parameters: :genre_ids, :engine_ids
Or, if you also upload a file in the process:
Parameters: {"game"=>{"name"=>"Pokémon Ruby", "genre_ids"=>"13,3", "engine_ids"=>"5", "cover"=>#<ActionDispatch::Http::UploadedFile:0x00007f9a45d11f78 #tempfile=#<Tempfile:/var/folders/2n/6l8d3x457wq9m5fpry0dltb40000gn/T/RackMultipart20190217-31684-1qmtpx2.png>, #original_filename="Screen Shot 2019-01-27 at 5.26.23 PM.png", #content_type="image/png", #headers="Content-Disposition: form-data; name=\"game[cover]\"; filename=\"Screen Shot 2019-01-27 at 5.26.23 PM.png\"\r\nContent-Type: image/png\r\n">}, "id"=>"4"}
Unpermitted parameters: :genre_ids, :engine_ids
TL;DR: My question is, how can I send this payload (a name string, an array of IDs, as well as a game cover image) to Rails using JavaScript? What format will actually be accepted and how do I make that happen?
The Rails app is open source if that'd help at all, you can see the repo here. The specific files mentioned are app/controllers/games_controller.rb and app/javascript/src/components/game-form.vue, though I've simplified both significantly for this question.
I figured out that I can do this using ActiveStorage's Direct Upload feature.
In my JavaScript:
// Import DirectUpload from ActiveStorage somewhere above here.
onChange(file) {
this.uploadFile(file);
},
uploadFile(file) {
const url = "/rails/active_storage/direct_uploads";
const upload = new DirectUpload(file, url);
upload.create((error, blob) => {
if (error) {
// TODO: Handle this error.
console.log(error);
} else {
this.game.coverBlob = blob.signed_id;
}
})
},
onSubmit() {
let genre_ids = Array.from(this.game.genres, genre => genre.id);
let engine_ids = Array.from(this.game.engines, engine => engine.id);
let submittableData = { game: {
name: this.game.name,
genre_ids: genre_ids,
engine_ids: engine_ids
}};
if (this.game.coverBlob) {
submittableData['game']['cover'] = this.game.coverBlob;
}
fetch(this.submitPath, {
method: this.create ? 'POST' : 'PUT',
body: JSON.stringify(submittableData),
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': Rails.csrfToken()
},
credentials: 'same-origin'
})
}
I then figured out that, with the way DirectUpload works, I can just send the coverBlob variable to the Rails application, so it'll just be a string. Super easy.
You can convert the File object to a data URL and include that string in JSON, see processFiles function at Upload multiple image using AJAX, PHP and jQuery or use JSON.stringify() on the JavaScript Array and set that as value of FormData object, instead of passing the Array as value to FormData.
submittableData.append('game[name]', JSON.stringify(this.game.name));
submittableData.append('game[genre_ids]', JSON.stringify(genre_ids));
submittableData.append('game[engine_ids]', JSON.stringify(engine_ids));
I wanted to have a way so users can choose images to send with a review.
so I use dropzone.js . It seems like having problems sending multiple images in one request. I thought that req.files would contain an array of files but that didn't happen. right now I see a problem because dropzone adds [] with the indexes inside for the name param.
when adding to images to dropzone I see something like this in request payload:
------WebKitFormBoundaryJOpGX6kWaoknKhIN
Content-Disposition: form-data; name="images[0]"; filename="data.png"
Content-Type: image/png
------WebKitFormBoundaryJOpGX6kWaoknKhIN
Content-Disposition: form-data; name="images[1]"; filename="loginButton.png"
Content-Type: image/png
------WebKitFormBoundaryJOpGX6kWaoknKhIN--
I feel like [0] and [1] is causing me problems
server:
app.post("/files" , upload.array("images"), (req, res) =>{
console.log("hit here")
console.log("req.file :", req.file)
// console.log(req.body)
console.log(req.files)
res.send("ok")
})
simple multer: I had more complicated one before
var upload = multer({ dest: "./uploads" })
frontend:
<script src = "/dropzone.js"></script>
<script>
$(function(){
Dropzone.autoDiscover = false;
var myDropZone = new Dropzone(".dropzone", {
url : "/files",
// uploadMultiple : true,
autoProcessQueue : false,
parallelUploads: 5,
paramName: "images",
uploadMultiple: true,
init : function(){
this.on("success", function(){
// alert("success")
})
this.on("sendingmultiple", function(){
console.log("SENDING MULTIPLE");
})
this.on("errormultiple", function(files, response){
console.log("ERROR");
console.log(response);
console.log(files);
})
}
})
$("#skip, #login").on("click", function(e){
myDropZone.processQueue();
})
})
</script>
<div class = "dropzone"></div>
<div id = "skip"> skip </div>
<div id = "login">login</div>
when I get rid of uploadMultiple: true, I get the images in the FS but it looks like multiple post request were made there is no req.files array with multiple images. I thought there would be
You should be able to force the name by specifying a function for paramName instead of a string:
paramName: function() { return 'images'; },
which will prevent any suffixes from being added to the form fields.