Use python jinja to auto-increment Image id from SQL-Alchemy-DB served in html via flask

Question:

I am trying to create a gallery.html which changes picture behaviour by image id e.g id="lightbox-1", id="lightbox-2", id="lightbox-3" and the gallery behaviour alters according to css, this works well using file paths as sample code highlight below:

 <div id="gallery">
      <div><img src="images/12.jpg"/><a href="#lightbox-1">512</a></div>
      <div><img src="images/13.jpg"/><a href="#lightbox-2">513</a></div>
      ...

and

<div class="lightbox" id="lightbox-1">
      <div class="content"><img src="images/12b.jpg"/>
        <div class="title">No. <b>512</b> from Picsum</div><a class="close" href="#gallery"></a>
      </div>
    </div>

However I am getting my images from sql-alchemy database and i am using this method to get images from DB,
The images are getting pulled successfully but the arrangement is haphazard and i figured the id number is not increasing, hence I am trying to programatically increase the ID number. Here is my code:

<h5>Gallery</h5>
    {% set n = 1 %}
    {% for image in image_list %}
    <div id="gallery">
      <div>
        <img src="data:;base64,{{ image }}"/>
        <a href="#lightbox-{{n}}">{{n}}</a>
 
      </div>

    </div>

    <div class="lightbox" id="lightbox-{{n}}">
      <div class="content"><img src="data:;base64,{{ image }}"/>
      </div>
    </div>
    {% set n = n+1 %}
    {% endfor %}

In simple terms I want to mimic this behaviour and design using flask to serve the html and sql-alchemy for the images.
At the moment, I can serve, and get the images, but cant get replicate the css behaviour
Thanks in advance

Asked By: user15316630

||

Answers:

I think you’re iterating in the wrong place and getting a different structure that doesn’t match the style sheet.

To get the current index within the running iteration I recommend you to use loop.index. Creating and incrementing your own variable is therefore unnecessary.

Flask (./app.py)
from flask import (
    Flask, 
    current_app, 
    redirect, 
    render_template, 
    request, 
    url_for
)
from flask_sqlalchemy import SQLAlchemy
import base64

app = Flask(__name__)
app.config.from_mapping(
    SQLALCHEMY_DATABASE_URI='sqlite:///example.db',
    SQLALCHEMY_TRACK_MODIFICATIONS=False,
    UPLOAD_EXTENSIONS=('jpg',)
)
db = SQLAlchemy(app)

class GalleryImage(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    mime = db.Column(db.String, nullable=False)
    data = db.Column(db.LargeBinary(), nullable=False)

    @property
    def b64encoded(self):
        return base64.b64encode(self.data).decode('ascii')

with app.app_context():
    db.create_all()

@app.route('/')
def index():
    images = GalleryImage.query.all()
    return render_template('index.html', **locals())

def allowed_file(filename):
    allowed_extensions = current_app.config.get('UPLOAD_EXTENSIONS', [])
    return '.' in filename and 
        filename.rsplit('.', 1)[1].lower() in allowed_extensions

@app.route('/upload', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        files = request.files.getlist('file[]')
        for file in files: 
            if file.filename != '' and allowed_file(file.filename):
                image = GalleryImage(
                    mime=file.mimetype, 
                    data=file.read()
                )
                db.session.add(image)
        try:
            db.session.commit()
        except: 
            db.session.rollback()
        return redirect(url_for('.index'))
    return render_template('upload.html')
HTML Template (./templates/upload.html)
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Upload</title>
</head>
<body>
    <form method="post" enctype="multipart/form-data">
        <input type="file" name="file[]" accept="image/jpeg" multiple />
        <button type="submit">Upload</button>
    </form>
</body>
</html>
HTML Template (./templates/index.html)
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Index</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/main.css') }}">
</head>
<body>

    <h5>Gallery</h5>
    
    <div id="gallery">
        {% for img in images -%}
        <div id="gallery-thumb-{{loop.index}}">
            <img src="data:;base64,{{ img.b64encoded }}" />
            <a href="#lightbox-{{ loop.index }}">{{ loop.index }}</a>
        </div>
        {% endfor -%}
    </div>

    {% for img in images -%}
    <div class="lightbox" id="lightbox-{{ loop.index }}">
        <div class="content">
            <img src="data:;base64,{{ img.b64encoded }}" />
            <a class="close" href="#gallery-thumb-{{loop.index}}"></a>
        </div>
    </div> 
    {% endfor -%}

</body>
</html>
CSS (./static/css/main.css)
* {
    box-sizing: border-box;
}
body {
    margin: 5px;
    position: relative;
}
#gallery {
    display: grid;
    /*  height: calc(100vh - 10px);*/ /* !!! */
    grid-template: repeat(6, 1fr) / repeat(6, 1fr);
    grid-gap: 0.5em;
}
@media (max-width: 800px) {
    #gallery {
        display: flex;
        align-items: flex-start;
        flex-wrap: wrap;
        justify-content: center;
    }
    #gallery > div {
        width: 48%;
        margin: 1%;
    }
}
@media (max-width: 800px) and (max-width: 350px) {
    #gallery > div {
        width: 98%;
    }
}
#gallery > div:nth-child(6n + 1) {
    grid-column: span 2;
    grid-row: span 2;
}
#gallery > div:nth-child(2) {
    grid-column: span 3;
    grid-row: span 3;
}
#gallery > div:nth-child(4) {
    grid-column: span 1;
    grid-row: span 2;
}
#gallery > div > a {
    opacity: 0;
    position: absolute;
    color: #000;
    background-color: #000;
    font: bold 4em "Helvetica";
    text-shadow: 0 -1px 5px #fff, -1px 0px 5px #fff, 0 1px 5px #fff, 1px 0px 5px #fff;
    padding: 2rem;
    mix-blend-mode: difference;
    width: 100%;
    height: 100%;
    transition: all ease 1s;
}
#gallery > div > img {
    width: 100%;
    min-height: 100%;
    transition: all ease 1s;
    object-fit: cover; /* !!! */
}
#gallery > div:hover img {
    filter: blur(4px);
}
#gallery > div:hover a {
    opacity: 1;
}
#gallery > div {
    overflow: hidden;
    position: relative;
    box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2), 0 3px 20px 0 rgba(0, 0, 0, 0.19);
}
#gallery div, #gallery a {
    display: flex;
    justify-content: center;
    align-items: center;
    text-decoration: none;
}
[id^="lightbox-"] {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    background-color: rgba(0, 0, 0, 0.5);
    display: flex;
    opacity: 0;
    transition: opacity 450ms ease-in-out;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}
[id^="lightbox-"]:target {
    opacity: 1;
    pointer-events: inherit;
}
[id^="lightbox-"]:target img {
    filter: blur(0);
}
[id^="lightbox-"] .content {
    max-width: 90%;
    position: relative;
    color: #fff;
}
[id^="lightbox-"] .content:hover > a.close {
    opacity: 1;
    transform: scale(1, 1);
}
[id^="lightbox-"] .content:hover > .title {
    opacity: 1;
    transform: translateY(-3px);
}
[id^="lightbox-"] .content:hover > .title::after {
    opacity: 1;
}
[id^="lightbox-"] .content > * {
    transition: all 450ms ease-in-out;
}
[id^="lightbox-"] .title {
    display: block;
    margin: 0;
    padding: 1em;
    position: absolute;
    bottom: 0;
    width: 100%;
    transform: translateY(50%);
    font-size: 1.5em;
    opacity: 0;
}
[id^="lightbox-"] .title::after {
    content: ' ';
    background-color: rgba(0, 0, 0, 0.4);
    bottom: 0;
    left: 0;
    height: 100%;
    width: 100%;
    position: absolute;
    transition: all 350ms ease-in-out 250ms;
    opacity: 0;
    transform-origin: bottom;
    mix-blend-mode: soft-light;
}
[id^="lightbox-"] img {
    max-height: 90vh;
    max-width: 100%;
    margin: 0;
    padding: 0;
    filter: blur(50px);
}
[id^="lightbox-"] a.close {
    width: 2em;
    height: 2em;
    position: absolute;
    right: 0;
    top: 0;
    background-color: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    transform: scale(0, 0);
    opacity: 0;
    transform-origin: right top;
    text-decoration: none;
    color: #fff;
}
[id^="lightbox-"] a.close::after {
    content: "×";
}
Answered By: Detlef
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.