Home > database >  What is the correct way to serve/access media files in Flask
What is the correct way to serve/access media files in Flask

Time:10-30

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.

enter image description here

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
    )
  • Related