Search code examples
pythonhtmlcssflaskjinja2

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


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


Solution

  • 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: "×";
    }