Flask Uploading + Folder Creation + FrontEnd Showing it - python

My problem is the following:
I'm creating and commercial proposal app, it should receive files in the input form, save them inside a folder named as the model.id and show them in the front end, inside the page of the proposal.
My code:
#comercial.route('/NovoOrçamento', methods=['GET', 'POST'])
def novo_orcamento():
form = OrcamentoForm()
if request.method == 'POST':
if form.validate_on_submit():
form.to_database()
else:
flash('Something went wrong.')
return redirect('./NovoOrçamento')
if 'arquivos' not in request.files:
flash('no files')
files = request.files.getlist('arquivos')
for file in files:
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
# My problem starts here at file.save()
return redirect('./Dashboard')
return render_template('novo_orcamento.html', form=form)
I don't have the rest of the code because idk how to actually do it.

I think you are looking for a library for handling and managing file uploads and downloads. I would suggest looking at Flask_uploads library. It allows you to configure multiple file models with different settings for allowed file types, allowed sizes, and where to save them (and many more).

Miguel Grinberg just wrote a superb blog post about exactly your problem this week:
https://blog.miguelgrinberg.com/post/handling-file-uploads-with-flask
If you want to try it on your on, you have to save the uploaded files first, then serve them. Have a look at Flask's send_from_directory function.

Related

How to access uploaded images via URL in Django?

I'm building a simple blog style web app for a friend. I am using CK editor and mostly stock Django 4.1. I'm stumped in how in what would be the best approach to let the creator to upload and serve images in their posts. CK editor allows you to add images via URL and I'd like for it to be possible to the creator to upload their files to the backend and would like to know if there are best practices when it comes to this.
Right now I have this:
def editor_upload(request):
if request.method == 'GET':
form = UploadFileForm
path = os.path.join(settings.STATIC_ROOT, 'files')
files = os.listdir(path)
return render(request, 'upload.html', {"form": form, "files": files})
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
file = request.FILES['file']
title = str(file)
if form.is_valid():
path = os.path.join(settings.STATIC_ROOT, 'files')
fs = FileSystemStorage(location=path)
fs.save(title, file)
return redirect('/editor/upload')
def open_file(request, file_name):
print(file_name)
return render(request, 'file.html', {"file": file_name})
in editor_upload, the user can see what files there are in the specified folder (but cannot access them yet) and there is the logic to upload new pictures to the folder. The problem comes now: I don't know how to properly access them.
In open_file, the request receives the name of the file and gives it to the HTML templating. This does not work because the template looks into the project folder and not into the root static folder. I must admit I'm a bit lost with how the static folders in Django works because I've been mostly utilizing just the project static folder. Also, I don't think this would work for CK editor to grab the image.
Any ideas on how I could solve this? I also tried doing it by uploading the image to a database but I had kind of the same problem. I need to somehow generate an URL to pass into CK editor. Many thanks!

RESTful API on Flask. How to upload a directory to a server

I try to write simple RESTful API on Flask. Which way is good to let user upload a directory by using only web request without using a web browser? For instance:
curl ... http://localhost:5000/api/v1/uploaddirectory...
Or uploading directory in this case is possible only how transferring as an archive file?
curl -F "directory=#/home/directory.zip" "http://localhost:5000/api/v1/uploaddirectory"
Getting flask to accept files is more code than I want to dump into a SO post. Since the problem is so general, I'm going to point you at resources which give you clear instructions on how to solve this problem.
Here is flask's boilerplate documentation on how to upload files: http://flask.pocoo.org/docs/0.12/patterns/fileuploads/
As it does for many problems, flask has its own module for file uploading. You could take a look at flask-uploads.
If you're really set on using curl to upload a bunch of files in a directory, see this SO post for recursively uploading everything in a directory: Uploading all of files in my local directory with curl
As per the solution provided by #melchoir55 I was able to build a flask api which is able to read a single file at a time. Here, for my requirement I need to upload all files existing in a directory on to a server. What is the way to do so.
#app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
# if user does not select file, browser also
# submit a empty part without filename
if file.filename == '':
flash('No selected file')
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('runDocumentManager',input_path=filename))
return '''
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<form method=post enctype=multipart/form-data>
<p><input type=file name=file>
<input type=submit value=Upload>
</form>
'''
This is the code for uploading file to the flask api server. Here I need to upload all files in a directory in a single go. How should I do it.

File upload not working after changing models format

I have a model class similar to following -
class Document(models.Model):
docfile = models.FileField(upload_to='documents/%Y/%M/%D')
Everything is working fine and files are uploaded successfully based on directory structure.
Now I don't want to upload files in this format but simply all files in one folder so I changed the logic ..
class Document(models.Model):
docfile = models.FileField(upload_to='documents')
Now It is not uploading the files and throwing error. Maybe I need to run some command but I do not know what ??
Please suggest something
Edit1:
Ok .. I found that the actual problem lies somewhere else.
I have a view like this - (please ignore the bad spacing but that is fine in actual code)
def lists(request):
// Problematic Code Start
path = settings.MEDIA_URL + 'upload/location.txt'
f = open(path, 'w')
myfile = File(f)
myfile.write('Hello World')
myfile.closed
f.closed
// Problematic Code ends
# Handle file upload
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
filename = Document(docfile = request.FILES['docfile'])
filename.save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('sdm:lists'))
#return render_to_response(reverse('sdm:lists'))
else:
form = DocumentForm() # A empty, unbound form
# Load documents for the list page
documents = Document.objects.all()
# Render list page with the documents and the form
return render_to_response(
'sdm/lists.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
When I remove the problematic code , everything works fine. (ignore the purpose of this weird code, actual interest is something bigger)
MEDIA_URL=/media/
Here is the error:
IOError at /sdm/lists
[Errno 2] No such file or directory: '/media/upload/location.txt'
Although File Exists and all permissions are www-data:www-data with 755
"problematic" code indeed - whoever wrote this should find another job. This code is wrong in more than one way (using MEDIA_URL instead of MEDIA_ROOT - which is the cause of the IOError you get - and also badly misusing Python's dead simple file objects) and totally useless, and looks like a leftover of someone programming by accident. To make a long story short : just remove it and you'll be fine.

How to sanitize user-provided paths in Python and Flask?

I'm using user-provided names as path components in my Python/Flask web app: for example, the user creates a project named "hello", prompting my web app to make a folder called "data/hello/" to store files that the user will upload. I am wondering how to sanitize the user provided name so that, e.g., the user can't type in "../hello" and have the folder be created in a different directory. The best pathname-based solution I can come up with is something like this:
import os.path
rpath = os.path.relpath(input_path)
safepath = rpath[rpath.index('../'):]
Is there a better way to make sure my web app doesn't access files outside of the data directory? Thanks!
Werkzeug provides secure_filename to help with this, an example is provided in the Flask docs
#app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files['file']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename) #Sanitised here
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file',
filename=filename))

Django: Serving a Download in a Generic View

So I want to serve a couple of mp3s from a folder in /home/username/music. I didn't think this would be such a big deal but I am a bit confused on how to do it using generic views and my own url.
urls.py
url(r'^song/(?P<song_id>\d+)/download/$', song_download, name='song_download'),
The example I am following is found in the generic view section of the Django documentations:
http://docs.djangoproject.com/en/dev/topics/generic-views/ (It's all the way at the bottom)
I am not 100% sure on how to tailor this to my needs. Here is my views.py
def song_download(request, song_id):
song = Song.objects.get(id=song_id)
response = object_detail(
request,
object_id = song_id,
mimetype = "audio/mpeg",
)
response['Content-Disposition'= "attachment; filename=%s - %s.mp3" % (song.artist, song.title)
return response
I am actually at a loss of how to convey that I want it to spit out my mp3 instead of what it does now which is to output a .mp3 with all of the current pages html contained. Should my template be my mp3? Do I need to setup apache to serve the files or is Django able to retrieve the mp3 from the filesystem(proper permissions of course) and serve that? If it do need to configure Apache how do I tell Django that?
Thanks in advance. These files are all on the HD so I don't need to "generate" anything on the spot and I'd like to prevent revealing the location of these files if at all possible. A simple /song/1234/download would be fantastic.
Why do you want to do this with a generic view? It's very easy to do this without generic views:
from django.http import HttpResponse
def song_download(request, song_id):
song = Song.objects.get(id=song_id)
fsock = open('/path/to/file.mp3', 'rb')
response = HttpResponse(fsock, content_type='audio/mpeg')
response['Content-Disposition'] = "attachment; filename=%s - %s.mp3" % \
(song.artist, song.title)
return response
I'm not sure if it's possible to make this work somehow with a generic view. But either way, using one is redundant here. With no template to render, the context that is automatically provided by the generic view is useless.
To wrap my comment to Tomasz Zielinski into a real answer:
For several reasons it is indeed better to let apache/nginx/etc do the work of sending files.
Most servers have mechanisms to help in that usecase: Apache and lighttpd have xsendfile, nginx has X-Accel-Redirect.
The idea is that you can use all the features of django like nice urls, authentification methods, etc, but let the server do the work of serving files. What your django view has to do, is to return a response with a special header. The server will then replace the response with the actual file.
Example for apache:
def song_download(request):
path = '/path/to/file.mp3'
response = HttpResponse()
response['X-Sendfile'] = smart_str(path)
response['Content-Type'] = "audio/mpeg"
response['Content-Length'] = os.stat(path).st_size
return response
install mode_xsendfile
add XSendFileOn on and (depending on the version) XSendFileAllowAbove on or XSendFilePath the/path/to/serve/from to your apache configuration.
This way you don't reveale the file location, and keep all the url management in django.
Serving static files with Django is a bad idea, use Apache, nginx etc.
https://docs.djangoproject.com/en/dev/howto/static-files/deployment/
To answer the original question how to use a generic view, you could do the following:
from django.views.generic import DetailView
from django.http.response import FileResponse
class DownloadSong(DetailView):
model = Song
def get(self, request, *args, **kwargs):
super().get(request, *args, **kwargs)
song = self.object
return FileResponse(open(song, 'rb'),
as_attachment=True,
filename=f'{song.artist} - {song.title}.mp3')
Docs:
Detailview: https://docs.djangoproject.com/en/3.2/ref/class-based-views/generic-display/#detailview
FileResponse: https://docs.djangoproject.com/en/3.2/ref/request-response/#fileresponse-objects
If your Django version does not have the FileResponse object, use the HttpResponse as shown in the other answers.

Categories