I am struggling with the following problem: what is the correct way to serve media files in Flask? I have build a model Posts
in a blog system, I'm using flask-file-upload
# models/blog.py
from app import db, file_upload
@file_upload.Model
class Posts(db.Model):
"""
Defines the attributes of posts table in the database
"""
...
image = file_upload.Column()
...
in my settings.py
file i have
import os
from pathlib import Path
PROJECT_ROOT = Path(__file__).parent.parent
UPLOAD_FOLDER = os.path.join(PROJECT_ROOT, 'media')
so, when I add a Post object it saves the file name in the database and the file in media folder with path media/posts/<post_id>/filename
So far it works perfectly. But I need serve these images, so I created the following route:
# routes.py
from app import app
@app.get('/media/<path:path>')
def send_media(path):
"""
:param path: a path like "posts/<int:post_id>/<filename>"
:return:
"""
path_list = path.split('/')
path_ = '/'.join(path_list[:-1]) '/'
file_name = path_list[-1]
return send_from_directory(
directory=app.config['UPLOAD_FOLDER'], path=path_, filename=file_name, as_attachment=True
)
but it is not working, when I browse http://127.0.0.1:5000/media/posts/15/python.jpg
my Flask app responds with 404 - Not Found, even the python.jpg file being there.
How can I correctly access the media files in Flask?
I need this because I have to create an API that sends the file_name
and the frontend will render the image, something like
<img src="http://localhost:8000/media/posts/<post_id>/file_name">
CodePudding user response:
In my opinion, the flask-file-upload extension basically uses an upload_folder, which is located in the static folder. The upload also works if a different location is specified, but the get_file_url function does not return the correct position. In addition, a separate download route is required, as you are trying to create here.
You would make your work easier if you specified a folder, as indicated in the documentation, which is inside the static folder.
Otherwise, here is my example, for a different location. It's not the best way to go, but it works.
import os
from flask import Flask
from flask import render_template, request, send_from_directory
from flask_sqlalchemy import SQLAlchemy
from flask_file_upload.file_upload import FileUpload
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = os.path.join(app.root_path, 'media')
app.config['ALLOWED_EXTENSIONS'] = ['jpg', 'png']
app.config['MAX_CONTENT_LENGTH'] = 1000 * 1024 * 1024 # 1000mb
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.sqlite'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
file_upload = FileUpload(app, db)
@file_upload.Model
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
image = file_upload.Column()
@property
def image_url(self):
return f'{self.__tablename__}/{self.id}/{self.image__file_name}'
with app.app_context():
db.drop_all()
db.create_all()
@app.route('/create', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
my_img = request.files['my-img']
post = Post()
file_upload.save_files(post, files={ 'image': my_img })
db.session.add(post)
db.session.commit()
posts = Post.query.all()
return render_template('create.html', **locals())
@app.route('/media/<path:filename>')
def media(filename):
return send_from_directory(
app.config['UPLOAD_FOLDER'],
filename,
as_attachment=True
)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<form method="post" enctype="multipart/form-data">
<input type="file" name="my-img" />
<input type="submit" />
</form>
<div>
{% for post in posts %}
<div><a href="{{ url_for('media', filename=post.image_url) }}">{{post.image__file_name}}</a></div>
{% endfor %}
</div>
</body>
</html>
CodePudding user response:
Solution: seeing the send_from_directory() docstring I got it! We don't need path_list
, path_
and file_name
variables. The correct way to serve media files with Flask is:
@app.get('/media/<path:path>')
def send_media(path):
"""
:param path: a path like "posts/<int:post_id>/<filename>"
"""
return send_from_directory(
directory=app.config['UPLOAD_FOLDER'], path=path
)