Ajax POST to flask to download a binary (cytoscape in use) - python

I'm using cytoscape.js to create a menu where I can download files quickly. I'm using an Ajax POST in order to pass the file name back to flask in order to download. For some reason, I can get all the information back but for whatever reason I cannot get the file to download. I've tried two methods so far.
AJAX Post:
{{
content: 'Download',
select: function(ele) {{
//this is to get the name of URI
var loc = window.location.pathname
var postData = {{
"element": ele.id(),
"source": loc
}}
$.ajax({{
url: '/get_file',
type: "POST",
contentType: 'application/json',
data: JSON.stringify(postData),
dataType: 'json',
success: function(response) {{
console.log("got it!")
}},
error: function(xhr) {{
console.log("Nope!")
}}
}})
Now for the flask back end, we have method one, and the commented section shows method 2 & 3 (in the if loop (if os.path.isfile(SAVE_PATH)):
#app.route('/get_file', methods=['POST'])
def get_file():
print("This is request data: {}".format(request.data))
requests = request.get_json()
element = requests['element']
source = requests['source']
#this is where loc is retrieved from ajax post, in format of /static/{filename}.html
for change in ['/static/', '.html']:
if change in source:
source = source.replace(change,"")
print("source: {}".format(source))
SAVE_PATH = os.path.curdir + "/results/" + source + "/" + element
SAVE_DIRECTORY = os.path.curdir + "/results/" + source + "/"
if os.path.isfile(SAVE_PATH):
downloaded_file = open("{}".format(SAVE_PATH), 'rb').read()
#res = send_from_directory(SAVE_PATH.replace("./", ""), element, attachment_filename=element, mimetype="application/octet-stream", as_attachment=True)
#res = send_file(SAVE_PATH, as_attachment=True, attachment_filename=element, mimetype='application/octet-stream')
#return res
return Response(
downloaded_file,
mimetype="application/octet-stream",
headers={"Content-disposition":
"attachment; filename={}".format(element)})
else:
print("failed")
return "failed"
Now I'm getting all the correct response, when I print out downloaded_file, I get the binary output, but for whatever reason, it just isn't downloading.

Looks like you could construct the download path on the frontend and request the file directly, something like this:
location = loc.split("/");
path = "results/" + location[location.length - 1].split(".")[0] + "/" + ele.id();
window.location.href = path;

Related

Sending image from flutter to flask

When I try to send image from flutter to flask, flask shows error 400.
I have no idea where is an error in my flutter code. Flutter function gets file(image in my case) as Uint8List. Then, I cast it as List, and trying to send with multipart.
Here is the code from flask and flutter.
Flask:
#auth.post('update/avatar')
#jwt_required()
def update_avatar():
current_user = get_jwt_identity()
save_folder = 'images/users/'
file = request.files.get('file', None)
file.filename = str(current_user) +".jpeg"
filename = secure_filename(file.filename)
file.save(os.path.join(save_folder, filename))
Flutter:
Future<String> uploadAvatar(Uint8List file, int userId) async {
var url = ApiConstants.baseUrlAuth + ApiConstants.updateAvatar + userId.toString();
String? access = await storage.storage.read(key: 'access');
if(access == null){
return '';
}
http.MultipartRequest request = http.MultipartRequest('POST', Uri.parse(url));
List<int> _selectedFile = file;
request.headers.addAll({'Authorization': access, "Content-type": "multipart/form-data"});
request.files.add(http.MultipartFile.fromBytes('file', _selectedFile, contentType: MediaType('file', 'jpeg'),));
http.StreamedResponse response = await request.send();
final responseStr = await response.stream.bytesToString();
Map data = json.decode(responseStr);
if (response.statusCode == 401 && data.containsKey("msg") && data['msg'] == "Token has expired!"){
String res = auths.refreshToken() as String;
if(res == "success"){
res = uploadImagePost(file, userId) as String;
}
return res;
} else if(response.statusCode == 201){
return data['photo_url'];
}
return '';
}
}
According to http Error 400 (Bad Request) the error is because request was somehow corrupted on the way.
check this Mozilla docs for more information about the main error.

How to get path of all uploaded files in Flask

I am making a rest API for multipart request API code is working fine but I am unable to get URL of multiple files any body know how to get this done code I done so far:
#app.route('/multiple-files-upload', methods=['POST'])
def upload_file():
randNum = random.randint(10, 900)
appendRand = "SWM-"+str(randNum)
date = str(datetime.date(datetime.now()))
# subject = request.form['subject']
# details = request.form['details']
# province = request.form['province']
# district = request.form['district']
# tehsil = request.form['tehsil']
# lat = request.form['lat']
# lon = request.form['lon']
# username = request.form['username']
# status = request.form['status']
files = request.files.getlist('files')
errors = {}
data = {}
success = False
for file in files:
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
appendDate = str(appendRand)+"-"+date+"-"+filename
file.save(os.path.join(app.config['UPLOAD_FOLDER'],appendDate))
success = True
else:
errors[file.filename] = 'File type is not allowed'
# if success and errors:
# errors['message'] = 'File(s) successfully uploaded'
# resp = jsonify(errors)
# resp.status_code = 500
# return resp
if success:
filename = secure_filename(file.filename)
url = appendPath +'/'+appendDate
data = {"URL":url}
resp = jsonify({"Error":"flase","Code":"00",'Message': 'Files successfully uploaded',"Data":data})
resp.status_code = 200
return resp
else:
resp = jsonify(errors)
resp.status_code = 500
return resp
Response I get:
{
"Error": "flase",
"Code": "00",
"Message": "Files successfully uploaded",
"Data": {
"URL": "/uploads/SWM-882-2022-02-17-API.PNG"
}
}
Desired response:
{
"Error": "flase",
"Code": "00",
"Message": "Files successfully uploaded",
"Data": {
"URL": "/uploads/SWM-882-2022-02-17-API.PNG"
"URL": "/uploads/SWM-882-2022-02-17-API.PNG"
"URL": "/uploads/SWM-882-2022-02-17-API.PNG"
"URL": "/uploads/SWM-882-2022-02-17-API.PNG"
}
}
E.g. when I upload multiple files I get multiple URLs now only get 1 URL
The example below follows your code and shows how to upload multiple files to the server using AJAX. As I am assuming you are trying.
Depending on whether an error occurs or not, a different response from the server is returned.
This contains a message and possible error messages, each of which is assigned to a file name. Furthermore, the resulting urls of the successfully uploaded files are listed.
Flask (app.py)
import os
from flask import Flask
from flask import (
jsonify,
render_template,
request,
url_for
)
from datetime import date
from random import randint
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = app.static_folder
#app.route('/')
def index():
return render_template('index.html')
def allowed_file(filename):
# Your validation code here!
return True
#app.route('/upload', methods=['POST'])
def upload():
errors = {}
data = []
prefix = f'SWM-{randint(10,900)}-{date.today()}'
files = request.files.getlist('files')
for file in files:
if file.filename != '' and allowed_file(file.filename):
filename = secure_filename(file.filename)
filename = f'{prefix}-{filename}'
file.save(os.path.join(
app.config['UPLOAD_FOLDER'],
filename
))
data.append({ 'url': filename })
else:
errors[file.filename] = 'File type is not allowed'
return jsonify({
'message': 'An error has occurred.' if errors else 'Files successfully uploaded',
'errors': errors,
'data': data
}), 400 if errors else 200
HTML (templates/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Index</title>
</head>
<body>
<form name="my-form" method="post">
<input type="file" name="files" multiple />
<input type="submit" />
</form>
<script type="text/javascript">
((uri) => {
const formElem = document.querySelector('form[name="my-form"]');
formElem.addEventListener('submit', evt => {
evt.preventDefault();
fetch(uri, {
method: 'post',
body: new FormData(evt.target)
}).then(resp => resp.json())
.then(data => {
console.log(data);
});
evt.target.reset(),
});
})({{ url_for('upload') | tojson }});
</script>
</body>
</html>

How to display user's image in Microsoft Graph using python

I tried using
GET https://graph.microsoft.com/v1.0/me/photo/$value to get the user's image/photo but it only returns an HTTP 200 status code. How can I get the binary data?
I've also tried using the content.property as suggested in a similar post but get a .format is not an attribute of the dict.
#app.route("/photo")
def get_photo():
token = _get_token_from_cache(app_config.SCOPE)
if not token:
return redirect(url_for("login"))
photo = requests.get(app_config.PHOTO_ENDPOINT,
headers={'Authorization': 'Bearer ' + token['access_token']})
print(photo.status_code)
return photo
Gets a profile photo, and optionally saves a local copy. Returns a tuple of the raw photo data, HTTP status code, content type, and saved filename. Refer to this sample.
def profile_photo(session, *, user_id='me', save_as=None):
"""Get profile photo, and optionally save a local copy.
session = requests.Session() instance with Graph access token
user_id = Graph id value for the user, or 'me' (default) for current user
save_as = optional filename to save the photo locally. Should not include an
extension - the extension is determined by photo's content type.
Returns a tuple of the photo (raw data), HTTP status code, content type, saved filename.
"""
endpoint = 'me/photo/$value' if user_id == 'me' else f'users/{user_id}/$value'
photo_response = session.get(api_endpoint(endpoint),
stream=True)
photo_status_code = photo_response.status_code
if photo_response.ok:
photo = photo_response.raw.read()
# note we remove /$value from endpoint to get metadata endpoint
metadata_response = session.get(api_endpoint(endpoint[:-7]))
content_type = metadata_response.json().get('#odata.mediaContentType', '')
else:
photo = ''
content_type = ''
if photo and save_as:
extension = content_type.split('/')[1]
filename = save_as + '.' + extension
with open(filename, 'wb') as fhandle:
fhandle.write(photo)
else:
filename = ''
return (photo, photo_status_code, content_type, filename)
Alternate approach, based on the original question's code, if you want to display the resulting image on a web page.
from base64 import b64encode
#app.route("/photo")
def get_photo():
token = _get_token_from_cache(app_config.SCOPE)
if not token:
return redirect(url_for("login"))
response = requests.get(
app_config.PHOTO_ENDPOINT,
headers={'Authorization': 'Bearer ' + token['access_token']}
)
content_type = response.raw.getheader('Content-Type')
return render_template('index.html',
photo_data=b64encode(response.content),
photo_content_type=content_type)
Then in the index.html template you can display the photo like so:
<html>
<body>
<img src="data:{{ photo_content_type }};base64,{{ photo_data }}" />
</body>
</html>
Call Api: -
Axios.get('https://graph.microsoft.com/v1.0/me/photo/$value', {
headers: { 'Authorization': 'Bearer '+AccessToken },
responseType: 'blob'
}).then(o => {
const url = window.URL || window.webkitURL;
const blobUrl = url.createObjectURL(o.data);
self.setState({ imageUrl: blobUrl });
})
JSX: -
<img alt="image" src={this.state.imageUrl} />
Here is what worked for me:
from base64 import b64encode
token = _get_token_from_cache(app_config.graphSCOPE)
aadPhotoURI = "https://graph.microsoft.com/v1.0/me/photo/$value"
response = requests.get(aadPhotoURI, headers={'Authorization': 'Bearer ' +
token['access_token']},)
content_type = response.raw.getheader('Content-Type')
return render_template(
'usersettings.html',
photo_data = b64encode(response.content).decode(),
photo_content_type = content_type)
then in the .html page added:
<img class="account-img" alt="" src="data:{{ photo_content_type }};base64,{{ photo_data }}"/>
CSS for account-img:
.account-img {
width: 40px;
float: right;
border-radius: 50%;
}

Auto download not working for Django FileResponse

I need to let the Django auto download the generated file.
Tried all different solutions online, none of them works.
Views.py
def validate(request):
if request.method == 'POST':
filename = request.POST.get('source_file')
file_path = os.path.join(settings.MEDIA_ROOT, 'SourceFiles', filename)
region = request.POST.get('region')
product_type = request.POST.get('product_type')
result = validateSource.delay(file_path, region, product_type)
output_filepath, log_filepath = result.get()
if os.path.exists(output_filepath) and os.path.exists(log_filepath):
zip_filename = zipFiles([output_filepath, log_filepath], filename)
zip_filepath = os.path.join(settings.MEDIA_ROOT, zip_filename)
response = FileResponse(open(zip_filepath, 'rb'), as_attachment=True)
return response
raise Http404
Template: code for the form POST.
$(document).on('submit', '#productForm', function(e){
e.preventDefault();
var inputFilePath = document.getElementById('sourceFileInput').files.item(0).name;
$.ajax({
method: 'POST',
url: 'validate/',
data: {
source_file: inputFilePath,
region: $("#Region-choice").val(),
product_type: $("#Product-type").val()}
})
.done(function(){
document.getElementById('lblStatus').innerHTML = "Result: <br/>"
document.getElementById('lblStatusContent').innerHTML = "Success!"
})
.fail(function(req, textStatus, errorThrown) {
document.getElementById('lblStatus').innerHTML = "Result: <br/>"
alert("Something went wrong!:" + textStatus + ' ' + errorThrown )
});
});
});
It's not possible to download files to your computer via an ajax (XHR) request. So you need to redirect the user actually (setting window.location) to a view that downloads the file. Or you can add as a result of the successful POST a button the current page so the user can download the file. In any case, you need to move the file download to a different view so a standard GET request can fetch it.
But your code to return the file in Django (using FileResponse) is correct.
There's also an explanation with an alternative way of doing it here
def validate(request):
if request.method == 'POST':
filename = request.POST.get('source_file')
file_path = os.path.join(settings.MEDIA_ROOT, 'SourceFiles', filename)
region = request.POST.get('region')
product_type = request.POST.get('product_type')
result = validateSource.delay(file_path, region, product_type)
output_filepath, log_filepath = result.get()
if os.path.exists(output_filepath) and os.path.exists(log_filepath):
zip_filename = zipFiles([output_filepath, log_filepath], filename)
zip_filepath = os.path.join(settings.MEDIA_ROOT, zip_filename)
with open(zip_filepath, 'rb') as fh:
response = HttpResponse(fh.read(), content_type="application/force-download")
response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(zip_filepath)
return response
raise Http404

python url not found - Accessing views.py from AJAX

New to Python and Django and I'm trying to make a simple ajax call from a button click to pass certain data to my views.py, however, when I try to make a url as seen on my ajax code below, the documentId.id does not append unless I directly append in without the "?id=".
{%for document in documents%}
{{document.filename}}
<input type="button" id="{{document.id}}" onclick="loadData(this)" name="load-data" value="Add"/>
{%endfor%}
<script type ="text/javascript">
function loadData(documentId){
$.ajax({
url:"upload-data/load" + "?id=" + documentId.id,
data: {'documentId': documentId},
type: 'GET',
success: function(){
window.location.href = "http://127.0.0.1:8000/url/locations";
}
});
}
</script>
This gives me then an error that says the url cannot be found. I have a line in my urls.py below:
url(r^"upload-data/load/([0-9]+)/$', views.loadFile, name="load-data"),
Other than this method, I am stumped as to how I am going to extract my data to my views.py.
def loadFile(request):
documentId = request.GET.get('id')
newLayer = Layer(get_object_or_404(Document, pk = documentId))
newLayer.save()
layers = Layer.objects.all()
return render(request, 'url/loaded.html', { 'layers': layers})
The persisting error in the console would be:
http://127.0.0.1:8000/upload-data/load/ [HTTP/1.0 404 Not Found]
Use something like this:
def loadFile(request):
documentId= request.GET.get('id', '').
newLayer = Layer(get_object_or_404(Document, pk = documentId))
newLayer.save()
layers = Layer.objects.all()
return render(request, 'url/loaded.html', { 'layers': layers})
And update your url as :
url(r^"upload-data/load/', views.loadFile, name="load-data")
And the script would be like :
<script type ="text/javascript">
function loadData(documentId){
$.ajax({
url:"upload-data/load/?id="+ documentId.id,
data: {'documentId': documentId},
type: 'GET',
success: function(){
window.location.href = "http://127.0.0.1:8000/url/locations";
}
});
}
</script>
Thanks.
In JavaScript you need
"upload-data/load/" + documentId.id
Django doesn't use ?id= in url definition r^"upload-data/load/([0-9]+)/$'. It expects ie. upload-data/load/123 instead of upload-data/load?id=123
EDIT: and you need id in def loadFile(request, id).
And then you don't have to use request.GET.get('id')
From the above answers and comments it seems like rather than passing id as a url param you want to pass the same as a get param. In that case make your urls like below.
url(r^"upload-data/load/', views.loadFile, name="load-data")
and in views, check for get params by replacing id with documentId. document id will be in your dict named as data passed to view. So look for request.GET.get('data','') and from data extract id as below
def loadFile(request):
data = request.GET.get('data', None)
if data:
documentId = data['documentId']
newLayer = Layer(get_object_or_404(Document, pk = documentId))
newLayer.save()
layers = Layer.objects.all()
return render(request, 'url/loaded.html', { 'layers': layers})
else:
return JsonResponse({'error': 'pass document id'}, status=400)
Since you are passing a get param from javascript named as documentId not id.
HTH

Categories

Resources