Flask - How can I query a BLOB image data and display it on HTML / Jinja2?

Viewed 2649

So, I have a form where a user can posts a title, body and upload an image. I took that image from the form and saved it as a "Blob" to my Postgres database.

But I have a problem, I am confused on how to query for that image blob data and decode and show it to the user.

These are my tables:

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), unique=True, nullable=False)
    username = db.Column(db.String(30), unique=True, nullable=False)
    password = db.Column(db.String(120), nullable=False)
    date_joined = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    posts = db.relationship('Post', backref='author', lazy=True, passive_deletes=True)


class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    body = db.Column(db.Text, nullable=False)
    link = db.Column(db.LargeBinary)
    date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey( 'user.id', ondelete='CASCADE'), nullable=False)

This is the route that saves the POST and the IMAGE to my database and static folder directory:

@app.route('/create/post', methods=['GET', 'POST'])
def create_post():
    # Image upload and validation
    if request.method == 'POST':
        if request.files:

            if allowed_image_size(request.cookies.get('filesize')):
                flash('Image exceeds the maximum size limit of 5 MB!', 'danger')
                return redirect(request.url)

            image = request.files['uploadImg']

            if image.filename == '':
                flash('No image detected or file name is empty!', 'danger')
                return redirect(request.url)

            if not allowed_images(image.filename):
                flash('Invalid image extension!', 'danger')
                return redirect(request.url)
            else:
                filename = secure_filename(image.filename)
                image.save(os.path.join(app.config['IMAGE_UPLOADS'], image.filename))

    # Regular Posts
    form = PostForm()
    if form.validate_on_submit():
        post = Post(title=form.title.data,
                    body=form.body.data, link=image.read(), user_id=current_user.id)
        db.session.add(post)
        db.session.commit()
        flash('Post submitted', 'success')
        return redirect(url_for('home'))
    return render_template('create_post.html', title='Create Post', form=form)

Also, when I query the post to see if I can retrieve the data, using something like posts = Post.query.all() and use print(posts.link.read()). I get an error saying AttributeError: 'list' object has no attribute 'link'

@app.route('/')
@app.route('/home')
def home():
    posts = Post.query.all()
    print(posts.link.read())
    return render_template('home.html', title='Home', posts=posts)
1 Answers

I believe you should store the image as a path name into your posts table, not a blob.

change this in your database to :

 link = db.Column(db.String(30))

Then when adding to post to the database pass the string of the image filename path into the Post

Also its good practice to rename the file with a random string, because many users might upload myCatPicture.jpg which would cause it to overwrite the file.

Something like this would do

def get_random_string(length):
    # Random string with the combination of lower and upper case
    letters = string.ascii_letters
    return ''.join(random.choice(letters) for i in range(length))

Then when saving the image save the new file name

    if not allowed_images(image.filename):
        flash('Invalid image extension!', 'danger')
        return redirect(request.url)
    else:
        ext = os.path.splitext(file_name)[1]  # get the file extension
        new_filename = get_random_string(20)  # create a random string

        image.save(os.path.join(app.config['IMAGE_UPLOADS'], new_filename+ext))  # save with the new path

And in the post creation use the new string.

post = Post(title=form.title.data,
                    body=form.body.data, link=os.path.join(app.config['IMAGE_UPLOADS'], new_filename+ext) , user_id=current_user.id)   #here we are substituting the old blod with the new stored image path string

Note:

It doesn't matter the filename as long as you have the path stored in the database. You can always look it up to get the image/path.

...

Then in your template (as you already have ALL the posts) you can start a for loop

{% for post in range(len(posts)) %}
    <h1> {{ post.title }} </h1>
    <img src= {{post.link }} >
    <p> {{ post.body }} </p>
{% endfor %}

This should iterate through every post title, image and content etc.

Again i'm a little fuzzy on this. But think that pretty much covers it.

Related