I am running a Flask app where the user uploads a file and must select the root folder path of where to upload the file on a network drive. This path is an IIS available network path and is also a network drive on all user's computers.
I want to dynamically show the available folders in HTML, even if new folders are created after the app starts.
I know this can't be done with pure HTML due to security but wanted to know if there was a way around this with Flask. The goal is to use Python to move the upload file to the choosen folder path.
I have tried:
<form><input type="file" name=dir webkitdirectory directory multiple/></form>
But this only works in Chrome. With the path choosen by the user I can pass this onto Python to copy the upload file to there.
Due to modern browser limitations I decided to use JSTree as a solution. And it is working very well. It features a tree structure browser. The structure is the result of outputting the folders as JSON. You can add a search bar as well so the user can just type in a folder name to search.
Please see JSTree https://www.jstree.com/
How to implement this with Flask
HTML/JS:
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css">
<div>
<input class="search-input form-control" placeholder="Search for folder"></input>
</div>
<script id="jstree1" name="jstree1">
/*Search and JS Folder Tree*/
$(function () {
$(".search-input").keyup(function () {
var searchString = $(this).val();
console.log(searchString);
$('#container').jstree('search', searchString);
});
$('#container').jstree({
'core': {
"themes": {
"name": "default"
, "dots": true
, "icons": true
}
, 'data': {
'url': "static/JSONData.json"
, 'type': 'GET'
, 'dataType': 'JSON'
}
}
, "search": {
"case_insensitive": true
, "show_only_matches": true
}
, "plugins": ["search"]
});
});
{ /* --- THIS IS FOLDER SELECTOR FOR ID "folderout" --- */
$("#container").on("select_node.jstree", function (evt, data) {
var number = data.node.text
document.getElementById("folderout").value = number;
});
In Flask/WTForms call on the id "folderout". This will return the path to WTForms when the user clicks the folder.
folderout = TextField('Folder:', validators=[validators.required()])
To Create the JSON JStree File using Python:
import os
# path : string to relative or absolute path to be queried
# subdirs: tuple or list containing all names of subfolders that need to be
# present in the directory
def all_dirs_with_subdirs(path, subdirs):
# make sure no relative paths are returned, can be omitted
path = os.path.abspath(path)
result = []
for root, dirs, files in os.walk(path):
if all(subdir in dirs for subdir in subdirs):
result.append(root)
return result
def get_directory_listing(path):
output = {}
output["text"] = path.decode('latin1')
output["type"] = "directory"
output["children"] = all_dirs_with_subdirs(path, ('Maps', 'Reports'))
return output
with open('test.json', 'w+') as f:
listing = get_directory_listing(".")
json.dump(listing, f)
Python runs on your server, therefoere it will not be possible to use that to move the files on the client side. If you think about it, let's assume you manage to somehow (magically) send python commands to the clients to move files, do you know if they even have python installed to be able to interpret your commands?
Javascript on the other hand is running on client side and was used to achieve this. However, like you said, due to security reasons modern browswers won't allow that. If they would allow it then any website could potentially see your whole File System.
Here is an article that explains a bit why. Look up the File Upload Control section of it. Hope this makes things a bit clearer.
EDIT: after seeing your comment you could achieve that using os.walk. Beware it could be slow.
for root, dirs, files in os.walk(rootPath): # for example "C:/Users/"
for file in files:
if file == (wantedFile):
print(os.path.join(root,file))
break
Related
I am working on a flask based UI and there I am downloading a text file using send_file function.
This is my directory setup:
/static
/design.css
/templates
/index.html
/upload.html
/engine.html
/output
/text_file.txt
/main.py
Below is the code:
#app.route('/download')
def download_file():
path = "output\\text_file.txt"
return send_file(path, as_attachment=True)
And below is related html button which is initiating the download:
<button>
<a href="{{ url_for('.download_file') }}" style="color: white; text-decoration: none;">Download Source Text
</a>
</button>
Now this function is directly downloading the file in downloads folder of my local C drive. But I want to get an option to select the location like below:
(image taken from google)
How can I achieve this?
The path to save the downloaded file is decided by a browser and server-side application cannot change this--and this is a feature, not a bug.
You think about it--say a server application can pick a location to save a file, what if my website saves an exe file to your C:\Windows folder? The consequence is disastrous...
Some modern browsers allow users to set a default download path. If you discover that your file is saved to a folder, such as Downloads, without asking you, most likely you have this browser feature enabled.
I'm trying to create a function to a serverscript to be able to automatically check for some dependencies that the script requires to work as intended, by first checking if the files exist on the filesystem and if not, get them from a webserver. But I cannot really figure out how to proceed. The function looks like this:
folder1 = "Folder1/"
folder2 = "Folder1/Folder2/"
def downloadTxt():
import os.path
import requests
file_repository = "https://ourserver.tld/txtfiles/"
folderlist = [folder1, folder2]
for folder in folderlist:
missing_folders = []
if not os.path.exists(folder):
missing_folders.append(folder)
for folder in missing_folders:
os.makedirs(folder, exist_ok=True)
filelist = [
folder1 + "example1.txt",
folder2 + "example2.txt",
folder2 + "example3.txt",
folder2 + "example4.txt",
] # Etc.. I have tons of these files but not really anything useful to show here
for file in filelist:
missing_files = []
if not os.path.exists(file):
missing_files.append(file)
for file in missing_files:
r = requests.get(file_repository + file)
open(file, "wb").write(r.content)
f_auto_download_txt()
This actually creates files named according to the filelist in the correct folders, but the content is just HTML error-messages:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body></html>
and if I check the webserverlogs I can see that it actually tries to get the files from /webfolder/webfolder/file, which makes sense because of the structure of the filelist.
[28/Oct/2021:05:18:30 +0200] "GET /Folder1/Folder1/example1.txt HTTP/1.1" 404 2804 "-" "python-requests/2.26.0"
I have tried to "just make it work" even if it's ugly with
missing_files_folder1_stripped = [s.replace(folder1, "") for s in missing_files]
missing_files_folder2_stripped = [s.replace(folder2, "") for s in missing_files]
missing_files_all_stripped = missing_files_folder1_stripped + missing_files_folder2_stripped
for file in missing_files_all_stripped:
r = requests.get(file_repository + file)
open(file, "wb").write(r.content)
but with no success, I managed to get one of the files, supposed to be in Folder1, downloaded to the rootdirectory, and when it reaches the first file in Folder2, I get "couldn't find Folder2\example2.txt. The server even gives me a HTTP code 200 so it looks like it should have worked, but no. So my other plan was to just get all the files downloaded to whatever directory and sort it afterwards, but now I'm stuck even getting the files downloaded properly at all.
Would appreciate any working solution to this, thanks on beforehand!
I ended up solving this after inspecting the accesslog of the webserver a little better, looking for what files that was actually asked for by the script, and I found out that I still had some errors in the filestructure. What I ended up doing was creating a new, "clean" list of the files with Folder1 stripped out of it, and changed the parameters of the open-function, so now it works as intended.
new_list_clean = [s.replace(folder1, "") for s in missing_files]
for file in new_list_clean:
r = requests.get(file_repository + file)
open(folder1 + file, "wb").write(r.content)
Also it may or may not give you a permission-error, in my case I just made sure to have write-access to that folder on my test-machine (which run's Windows), but it may be useful to set
os.makedirs(folder, exist_ok=True, mode=0760) # Example for giving fileowner full access and group read+write
right away to don't have this problem on *nix systems.
I currently have a python file that outputs data in a .json and saves it to a folder in my project which is located on my desktop:
let dirPath = "/Users/Admin/Desktop/DataExchangeExercise/DataExchangeExercise/"
This folder is where my Xcode project is located. However, I would like my program to just write to the project folder by itself without me directing to it manually so if I move my project it still works. I am calling my python file from swift like this:
func runPythonCode(){
let sys = Python.import("sys")
sys.path.append(dirPath)
let example = Python.import("Users")
example.writeToJSONFile()
}
I have tried changing dirPath to: let dirPath = Bundle.main.bundlePath but it is not putting the file where I want it in my XCode project, because then my read function does not work with it anymore:
func readLocalFile(forName name: String) -> Data? {
do {
if let bundlePath = Bundle.main.path(forResource: fileName, ofType: "json"),
let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) {
return jsonData
}
} catch {
print("error")
}
return nil
}
Any ideas on how to get the file where I want it to go?
This question already has answers here:
How to serve static files in Flask
(24 answers)
Closed 5 years ago.
I'm making an upload/download service just for fun but am struggling to serve files that are outside of static directory, which causes an issue because anyone could visit www.mysite.com/static and view the contents.
This is what I have so far. Forgive the ugly paths of my network storage :)
if not os.path.exists('\\ATTILA\\Development\\GIT\\MyCloud\\static\\'+ session['username']):
os.makedirs('\\\\ATTILA\\Development\\GIT\\MyCloud\\static\\'+ session['username'])
#Download links for all files
pathing = []
my_path = '\\\\ATTILA\\Development\\GIT\\MyCloud\\static\\'+ session['username'] + '\\'
os.chdir('\\\\ATTILA\\Development\\GIT\\MyCloud\\static\\'+ session['username'])
for myfile in glob.glob('*'):
downs = my_path + myfile
pathing.append(tuple([downs, myfile]))
In my template I have a simple for loop
{% for myfile, name in pathing %}
<a href='{{ myfile }}' download>{{ name }}</a><br>
{% endfor %}
So my view looks like this:
As it stands my files are downloadable, however if I change the file paths for the downloads to a folder outside of 'static' then instead of download links I get 404 errors that point to the URL + the file path like so www.mysite.com\ATTILLA\Development\some_other_folder any suggestions?
If you want place your app in production you need use solutions like a nginx for serving you static files.
Usually in development stage Flask work with regulary static files (css, js and another) self. It's normal.
If you want hide some private data or uploaded files you need use something like this:
from flask import Flask, make_response
...
#app.route('/file_downloads/<filename>')
def file_downloads(filename):
headers = {"Content-Disposition": "attachment; filename=%s" % filename}
with open('../tmp/you_private_file.zip', 'r') as f:
body = f.read()
return make_response((body, headers))
In my view callable, I want users to be able to create a new file called filename like so:
#view_config(route_name='home_page', renderer='templates/edit.pt')
def home_page(request):
if 'form.submitted' in request.params:
name= request.params['name']
input_file=request.POST['stl'].filename
vertices, normals = [],[]
for line in input_file:
parts = line.split()
if parts[0] == 'vertex':
vertices.append(map(float, parts[1:4]))
elif parts[0] == 'facet':
normals.append(map(float, parts[2:5]))
ordering=[]
N=len(normals)
...parsing data...
data=[vertices,ordering]
jsdata=json.dumps(data)
renderer_dict = dict(name=name,data=jsdata)
app_dir = request.registry.settings['upload_dir']
filename = "%s/%s" % ( app_dir , name )
html_string = render('tutorial:templates/view.pt', renderer_dict, request=request)
with open(filename,'w') as file:
file.write(new_comment)
return HTTPFound(location=request.static_url('tutorial:pages/%(pagename)s.html' % {'pagename': name}))
return {}
right now, when I attempt to upload a file, I am getting this error message: IOError: [Errno 2] No such file or directory: u'/path/pages/one' (one is the name variable) I believe this is because I am incorrectly defining the app_dir variable. I want filename to be the url of the new file that is being created with the name variable that is defined above (so that it can be accessed at www.domain.com/pages/name). Here is the file structure of my app:
env
tutorial
tutorial
templates
home.pt
static
pages
(name1)
(name2)
(name3)
....
views.py
__init__.py
In my init.py I have:
config.add_static_view(name='path/pages/', path=config.registry.settings['upload_dir'])
In my development.ini file I have
[app:main]
use = egg:tutorial
upload_dir = /path/pages
Edit: If anyone has an idea on why this question isn't getting much attention, I would love to hear it.
While I feel like you probably have a misunderstanding of how to serve up user-generated content, I will show you a way to do what you're asking. Generally user-generated content would not be uploaded into your source, you'll provide some configurable spot outside to place it, as I show below.
Make the path configurable via your INI file:
[app:main]
use = egg:tutorial
upload_dir = /path/to/writable/upload/directory
Add a static view that can serve up files under that directory.
config.add_static_view(name='/url/to/user_uploads', path=config.registry.settings['upload_dir'])
In your upload view you can get your app_dir via
app_dir = request.registry.settings['upload_dir']
Copy the data there, and from then on it'll be available at /url/to/user_uploads/filename.