Passing File Objects with Django - python

I am attempting to retrieve a file and then upload it to Parse.com by submitting via POST. My HTML:
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<div class="container">
<form action="" id="fileupload" name="fileupload" enctype="multipart/form-data" method="post">
{% csrf_token %}
<fieldset>
<input type="file" name="fileselect" id="fileselect" /></input>
<input type="hidden" name="myFile" id="myFile" />
<input id="uploadbutton" type="submit" value="Upload to Parse" />
</fieldset>
</form>
</div>
And using the following function to retrieve the file:
<script type="text/javascript">
$(function() {
var file;
// Set an event listener on the Choose File field.
$('#fileselect').bind("change", function(e) {
var files = e.target.files || e.dataTransfer.files;
// Our file var now holds the selected file
file = files[0];
document.getElementById('myFile').value = file;
});
});
</script>
However, this "myFile" field is not posting the file as an object nor does it jive with the Parse API documentation, which appears to be looking for a file path name, which I don't think I can pull from an unknown machine.
import json,httplib
connection = httplib.HTTPSConnection('api.parse.com', 443)
connection.connect()
connection.request('POST', '/1/files/pic.jpg', open('myPicture.jpg','rb').read(), {
"X-Parse-Application-Id": "xxxxxxxxxxxxxxxxxxxxx",
"X-Parse-REST-API-Key": "xxxxxxxxxxxxxxxxxxxxxxx",
"Content-Type": "image/jpeg"
})
result = json.loads(connection.getresponse().read())
print result
This seems like it would be a common use case but the only documentation I found was for ajax, which I would prefer not to use because it exposes my API credentials https://www.parse.com/questions/uploading-files-to-parse-using-javascript-and-the-rest-api.
I am not sure what is the best way to handle the file... if there's a way to handle within the Django framework or if I need to convert to JSON. And, even when the file object is captured, I'm not clear on how to use the Parse.com API with the file object.

After some research, it turns out that the answer is pretty straightforward. Using the "Basic File Upload" documentation from the Django website: https://docs.djangoproject.com/en/1.8/topics/http/file-uploads/ and replacing
open('myPicture.jpg', 'rb').read()
in the Parse documentation (referenced in the question above) with
request.FILES['file']
I was able to successfully upload the file to Parse. No javascript necessary.

Related

I am getting a `KeyError` when I receive a request to my API

I am building a dictionary app with Flask where users can add new words, I am trying to request the word from the word input , I am having issues with the POST request, the error I am receiving on my terminal is this:
line 50, in add_word
word = req['word']
keyError:'word'
and this is how I wrote the code in my app.py file:
#app.route('/word', methods= ['POST'])
def add_word():
req = request.get_json()
word = req['word']
meaning = req['meaning']
conn = mysql.get_db()
cur = conn.cursor()
cur.execute('insert into word(word, meaning) VALUES (%s, %s)',(word, meaning))
conn.commit()
cur.close()
return json.dumps("success")
here is the json in my JavaScript file, I am posting to my flask app:
$('#word-form').submit(function() {
let word = $('word').val();
let meaning = $('meaning').val();
$.ajax({
url: '/word',
type: 'POST',
dataType: 'json',
data : JSON.stringify({
'word': word,
'meaning': meaning
}),
contentType: 'application/json, charset = UTF-8',
success: function(data) {
location.reload();
},
error: function(err) {
console.log(err);
}
})
here is the Html page:
<div class="div col-md-2 sidenav">
All words
Add New
<div>
<form action="javascript:0" id="word-form">
<div class="form-group">
<label for="word">Word:</label>
<input type="text"
class="form-control"
name="word"
id="word"
placeholder="Type in the word here:"
required>
</div>
<div class="form-group">
<label for="Meaning">Meaning:</label>
<textarea class="form-control" id="meaning"
placeholder="enter the meaning here: " required></textarea>
</div>
<button type="submit" class="btn btn-primary btn-block btn-lg" id="submit">Submit</button>
<button type="button" class="btn btn-warning btn-block btn-lg" id="cancel">Cancel</button>
</form>
</div>
</div>
<div class="div col-md-10 main">
<table style="border: 2px;">
<thead>
<tr>
<th>SN</th>
<th>Word</th>
<th>Meaning</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{% for word in words %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ word['word'] }}</td>
<td>{{ word['meaning'] }}</td>
<td><button class="btn btn-sm btn-success btn-block edit" id="{{word['id']}}">Edit</button></td>
<td><button class="btn btn-sm btn-danger btn-block delete" id="{{word['id']}}">Delete</button></td>
</tr>
{% else %}
<tr>
<td colspan="3">The dictionary has no words at the moment, please come bay later</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
Things seem to be in a confused state in the client code, and potentially the application architecture in general.
There are two general approaches to designing web apps that impacts how you create routes and build requests. One approach is the AJAX-based single page app architecture that loads one HTML skeleton, then uses JS (jQuery here) to make AJAX requests to a JSON API and injects the response data into the page using JS instead of page refreshes and navigations. Since you don't have a client-side router, this doesn't qualify as a SPA, but it's worth understanding to provide context on your design.
On the other hand, you can use HTML form submissions (<form action="/path/to/resource" method="POST">) and render_template to display new pages with a browser refresh for all form submissions.
The code here is somewhere in the middle, which is potentially fine (you can use AJAX for certain events and form submissions but mostly rely on full-navigation templates for routes). But it's important to be clear on the request-response workflow you're adopting so the design makes sense and can be debugged.
Here are few oddities/inconsistencies in your current design:
return json.dumps("success") is not really JSON as it seems like you want it to be--use jsonify and a dictionary or list, e.g. jsonify({"status": "success"}; it's customary for JSON routes to return JSON responses if they aren't rendering templates or redirecting.
The client ignores the JSON response and calls location.reload. If you're just planning on reloading and you have no special client processing to do, there's not much point in using AJAX or JSON here--just submit the form to the backend and redirect to the template or static HTML page you want to show next. No client-side JS involved. Redirect to an error page or render a template with errors shown on the form on error.
Links with href="#" are poor practice. Better to use buttons if you're adding JS to these handlers and you don't want them to trigger navigation. This is semantically more appropriate and doesn't hijack the URL.
<form action="javascript:0" id="word-form"> looks like it's trying to prevent the form submission, but all this does is replace the page content with the character "0". I can't imagine how this is useful or desirable. Submitting a form to a JSON route can produce the error you're seeing--another sign of confusion about which architecture you're following. Use event.preventDefault() (add the event parameter to the callback to .submit()) to prevent the form submission from refreshing the page.
After you've prevented the page refresh, you can debug the AJAX request.
When a route is complaining about missing keys, consider that objects with keys pointing to undefined disappear when serialized as JSON (undefined is not a thing in JSON):
const word = undefined;
const foo = 42;
const bar = "baz";
console.log({word, foo, bar}); /* =>
{
"word": undefined,
"foo": 42,
"bar": "baz"
}
*/
console.log(JSON.stringify({
word,
foo,
bar,
})); // => {"foo":42,"bar":"baz"}
If you add a console.log to see if your values are there (or print the JSON on the backend route before indexing into it), these values aren't defined:
let word = $('word').val();
let meaning = $('meaning').val();
console.log(word, meaning); // <-- undefined, undefined
Why? The reason is that these selectors are missing the # symbol prefix to denote an id. Without it, jQuery looks for <word></word> and <meaning></meaning> HTML elements that don't exist.
Change these lines to:
const word = $('#word').val();
const meaning = $('#meaning').val();
and now your request body should be ready to send.
Next problem: $.ajax's dataType key specifies the response type, not the request type. Use dataType: "json" to specify the appropriate request header to trigger the Flask handler to JSON parse the request body.
After these changes, things should work, with the caveat that it might be time for a rethink of your overall design, or at least a redesign of this route workflow.
A word of advice: work slowly and test all of your assuptions at each step. The code here shows many errors that are hard to debug because they're stacked on top of each other. Isolate and validate each behavior in your app. For example, when adding the jQuery submit handler and collecting the form values, print them to make sure they're actually there as you expected.
In case you're stuck, here's minimal, complete, runnable code you can reference.
app.py:
from flask import (
Flask, jsonify, render_template, request, url_for
)
app = Flask(__name__)
#app.route("/")
def index():
return render_template("index.html")
#app.post("/words/")
def words():
payload = request.get_json()
word = payload.get("word")
meaning = payload.get("meaning")
if word is None or meaning is None:
return (jsonify({
"error": "missing required keys `word` or `meaning`"
}), 400)
# handle db operation and report failure as above
return jsonify({"status": "success"})
if __name__ == "__main__":
app.run(debug=True)
templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<form id="word-form">
<div>
<label for="word">Word:</label>
<input
name="word"
id="word"
placeholder="Type in the word here:"
required
>
</div>
<div>
<label for="meaning">Meaning:</label>
<textarea
name="meaning"
id="meaning"
placeholder="enter the meaning here: "
required
></textarea>
</div>
<button type="submit">Submit</button>
</form>
<script>
$('#word-form').submit(function (e) {
e.preventDefault();
const word = $('#word').val();
const meaning = $('#meaning').val();
console.log(word, meaning);
$.ajax({
url: "{{url_for('words')}}",
type: "POST",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({word, meaning}),
success: function (data) {
console.log(data);
},
error: function (err) {
console.error(err);
}
});
});
</script>
</body>
</html>
See also: How to get POSTed JSON in Flask?
Here are a few additional notes that are somewhat tangential to the main issue but have to be mentioned.
You have <label for="Meaning"> but no name="meaning" element to shift focus to when clicked.
It's another antipattern to put ids on everything promiscuously. Only add ids to elements when they must have one because you're using it for something specific. Prefer classes, especially for styling.
On the backend, the code here is unsafe:
req = request.get_json()
word = req['word']
meaning = req['meaning']
If your client gives a bad request with missing values, you should detect that and return a 400/422 response (or similar) rather than crashing.
For example (from the above code snippet):
req = request.get_json()
word = req.get("word")
meaning = req.get("meaning")
if word is None or meaning is None:
return (jsonify({
"error": "missing required keys `word` or `meaning`"
}), 400)
Similarly, don't assume the database operation will succeed. Always check for errors and return an appropriate response to the client.
Resources are usually plural, not singular: words, users, posts.

How to get full path of selected file on change of <input type=‘file’> using django

compress.html
<form action="/compress" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<label for="pdf">Select pdf :</label>
<input type="file" name="pdf1" id ="pdf1" accept="pdf/*" />
<br>
<button type="submit" class="btn btn-dark mt-2" value="click">
submit
</button>
<script type="text/javascript">
function getFilePath(){
$('input[type=file]').change(function () {
var filePath=$('#fileUpload').val();
});
}
</script>
views.py
def mergepdf(request):
from pylovepdf.ilovepdf import ILovePdf
ilovepdf = ILovePdf('my_secrete_key', verify_ssl=True)
task = ilovepdf.new_task('compress')
task.request.FILES['full_path']# here i want full path of selected file
task.set_output_folder('/Downloads/download_pdffile')
task.execute()
task.download()
task.delete_current_task()
The filePath var contains the only name of selected file, not the full path.
I searched it on the net, but it seems that for security reasons browsers (FF, chrome) just give the name of the file.
Is there any other way to get the full path of the selected file?

Remove paragraph tags from the content entered in the textarea in Froala Editor

I am using Froala editor in my Flask app. When I get the content of textarea in Python, the content is wrapped in <p></p> tags. Can anyone help as what needs to be done so that the content is not wrapped in <p></p> tags?
I tried using htmlDoNotWrapTags option as per the documentation during initializing of text area, but it didn't help.
https://www.froala.com/wysiwyg-editor/docs/options#htmlDoNotWrapTags
My code:
HTML:
<form action="/submit_form" method="POST">
<fieldset>
<label for="activities_completed">Activities completed</label>
<textarea id="activities_completed" name="activities_completed"></textarea>
</fieldset>
<fieldset>
<input type="submit" value="Submit">
</fieldset>
</form>
Initializing Froala editor:
<script>
$(function() {
$('textarea#activities_completed').froalaEditor();
htmlDoNotWrapTags: ['p'] // it did not help
});
</script>
Getting value of textarea in Python:
if request.method == 'POST':
activities_completed = request.form['activities_completed']
print(activities_completed)
Figured it out.
Following code removed <p></p> tags.
<script>
$(function() {
var config = {
enter: $.FroalaEditor.ENTER_BR
};
$('textarea#activities_completed').froalaEditor(config);
});
</script>

Uploading file with AngularJS fails

Below are the snippets of my code regarding file upload.
Here is my HTML code where I will choose and upload the file:
<form ng-click="addImportFile()" enctype="multipart/form-data">
<label for="importfile">Import Time Events File:</label><br><br>
<label for="select_import_file">SELECT FILE:</label><br>
<input id="import_file" type="file" class="file btn btn-default" ng-disabled="CutOffListTemp.id== Null" data-show-preview="false">
<input class="btn btn-primary" type="submit" name="submit" value="Upload" ng-disabled="CutOffListTemp.id== Null"/><br/><br/>
</form>
This is my controller that will link both html and my python file:
angular.module('hrisWebappApp').controller('ImportPayrollCtrl', function ($scope, $state, $stateParams, $http, ngTableParams, $modal, $filter) {
$scope.addImportFile = function() {
$http.post('http://127.0.0.1:5000/api/v1.0/upload_file/' + $scope.CutOffListTemp.id, {})
.success(function(data, status, headers, config) {
console.log(data);
if (data.success) {
console.log('import success!');
} else {
console.log('importing of file failed' );
}
})
.error(function(data, status, headers, config) {});
};
This is my python file:
#api.route('/upload_file/<int:id>', methods=['GET','POST'])
#cross_origin(headers=['Content-Type'])
def upload_file(id):
print "hello"
try:
os.stat('UPLOAD_FOLDER')
except:
os.mkdir('UPLOAD_FOLDER')
file = request.files['file']
print 'filename: ' + file.filename
if file and allowed_file(file.filename):
print 'allowing file'
filename = secure_filename(file.filename)
path=(os.path.join(current_app.config['UPLOAD_FOLDER'], filename))
file.save(path) #The end of the line which save the file you uploaded.
return redirect(url_for('uploaded_file',
filename=filename))
return '''
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<p>opsss it seems you uploaded an invalid filename please use .csv only</p>
<form action="" method=post enctype=multipart/form-data>
<p><input type=file name=file>
<input type=submit value=Upload>
</form>
'''
And the result in the console gave me this even if I select the correct format of file:
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<p>opsss it seems you uploaded an invalid filename please use .csv only</p>
<form action="" method=post enctype=multipart/form-data>
<p><input type=file name=file>
<input type=submit value=Upload>
</form>
This is not returning to my HTML and I cannot upload the file.
Hi I can finally upload the file, I change the angular part, I change it by this:
$scope.addImportFile = function() {
var f = document.getElementById('file').files[0]; console.log(f);
var formData = new FormData();
formData.append('file', f);
$http({method: 'POST', url: 'http://127.0.0.1:5000/api/v1.0/upload_file/' +$scope.CutOffListTemp.id,
data: formData,
headers: {'Content-Type': undefined},
transformRequest: angular.identity})
.success(function(data, status, headers, config) {console.log(data);
if (data.success) {
console.log('import success!');
}
})
.error(function(data, status, headers, config) {
});
// }
};
The first thing is about the post request. Without ng-click="addImportFile()", the browser will usually take care of serializing form data and sending it to the server. So if you try:
<form method="put" enctype="multipart/form-data" action="http://127.0.0.1:5000/api/v1.0/upload_file">
<label for="importfile">Import Time Events File:</label><br><br>
<label for="select_import_file">SELECT FILE:</label><br>
<input id="import_file" type="file" name="file" class="file btn btn-default" ng-disabled="CutOffListTemp.id== Null" data-show-preview="false">
<input class="btn btn-primary" type="submit" name="submit" value="Upload" ng-disabled="CutOffListTemp.id== Null"/><br/><br/>
</form>
and then in python, make your request url independent of scope.CutOffListTemp.id:
#api.route('/upload_file', methods=['GET','POST'])
It probably will work.
Alternatively, if you want to use your custom function to send post request, the browser will not take care of the serialization stuff any more, you will need to do it yourself.
In angular, the API for $http.post is:
$http.post('/someUrl', data).success(successCallback);
If we use "{}" for the data parameter, which means empty, the server will not find the data named "file" (file = request.files['file']). Thus you will see Bad Request
To fix it, we need to use formData to make file upload which requires your browser supports HTML5:
$scope.addImportFile = function() {
var f = document.getElementById('file').files[0]
var fd = new FormData();
fd.append("file", f);
$http.post('http://127.0.0.1:5000/api/v1.0/upload_file/'+$scope.CutOffListTemp.id,
fd,
headers: {'Content-Type': undefined})
.success......
Other than using the native javascript code above, there are plenty great angular file upload libraries which can make file upload much easier for angular, you may probably want to have a look at one of them (reference: File Upload using AngularJS):
https://github.com/nervgh/angular-file-upload
https://github.com/leon/angular-upload
......

django, normal post form is not sending file data to server,

I'm wondering why this is happening:
I'm sending file data with form in POST, but all the time it says:
Key 'file' not found in <QueryDict:
{
u'datum': [u'aaa'],
u'csrfmiddlewaretoken': [u'USAbRrgU92yj7KFpZHuxf9bWufgnwC4N'],
u'anzeige': [u' aaaa'],
u'titel': [u' aaa']
}
This is my html:
<form id="myform" action="/anzeige_save/" method="post" enctype="multipart/form-data">
{% csrf_token %}
<textarea style="width: 450px" id="titel" name="titel"> </textarea>
<textarea name="anzeige" id="anzeige"
style="height: 180px; width: 450px"> </textarea>
<input type="text" id="datum" name="datum" >
<input type="file" id="file" name="file" size="40" maxlength="100000">
<input type="button" value="speichern"
onclick="javascript:submitform()" />
</form>
<script>
function submitform(){
document.forms["myform"].submit();
}
</script>
and part my view is this:
anzeige=Anzeige(titel=request.POST['titel'],
anzeige=end_anzeige,
date=datetime.datetime.now(),
datum=request.POST['datum'],
file=request.FILES['file'])
anzeige.save()
I'm actually submitting correctly, the form should also submit the file in QueryDict like other queries. Can someone help me to figure out what I'm missing here?
Thanks
Use a ModelForm instead of a form, unless you have a good reason not to.
Your HTML will display a blank form if the form doesn't validate, without mentioning the errors nor previously entered values, see customizing a form template for correct usage.
It is normal that request.POST['file'] does not exist, it's in request.FILES['file'], but it must be saved before it can be used in a model. If you use a ModelForm, it is automatic. Else, see handling uploaded files with a model.

Categories

Resources