1

I'm trying to make a really simple web application that take images inputted by the client, then process it on server and then return the image. I want the image to be displayed on one of the div. I tried to use ajax but it doesn't work. I'm very new to flask and ajax, found the example that return text.

This is my main.py

import os
#import magic
import urllib.request
from app import app
from flask import Flask, flash, request, redirect, render_template, send_file
from werkzeug.utils import secure_filename
from fungsi import plot

ALLOWED_EXTENSIONS = set(['txt', 'tif'])

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/')
def upload_form():
    return render_template('upload_.html')

@app.route('/', methods=['POST'])
def upload_file():
    if request.method == 'POST':
        # check if the post request has the files part
        if 'files[]' not in request.files:
            flash('No file part')
            return redirect(request.url)
        files = request.files.getlist('files[]')
        for file in files:
            if file and allowed_file(file.filename):
                filename = secure_filename(file.filename)
                file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
        flash('Files successfully uploaded')
        return redirect('/')

@app.route('/foo', methods=['GET'])
def foo():
    for root, dirs, files in os.walk(app.config['UPLOAD_FOLDER']):
        for file in files:
            if file.endswith('B4.TIF'):
                fname4 = os.path.join(app.config['UPLOAD_FOLDER'], file)
            if file.endswith('B5.TIF'):
                fname5 = os.path.join(app.config['UPLOAD_FOLDER'], file)

    bytes_obj = plot(fname4,fname5)

    return send_file(bytes_obj,
                     attachment_filename='plot.png',
                     mimetype='image/png')

if __name__ == "__main__":
    app.run()

html code

<!doctype html>
<html>
<title>Python Flask Multiple Files Upload Example</title>

<head><script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> </head>


<body>

    <div class="row">
        <div class="col-sm-6">
            <h4>Plot</h4>
            <button id='myButton' type="button">Click Me!</button>
            <div id="img"></div>
        <div class="col-sm-6">
            <h2>Select file(s) to upload</h2>
            <p>
                {% with messages = get_flashed_messages() %}
                  {% if messages %}
                    <ul class=flashes>
                    {% for message in messages %}
                      <li>{{ message }}</li>
                    {% endfor %}
                    </ul>
                  {% endif %}
                {% endwith %}
            </p>
            <form method="post" action="/" enctype="multipart/form-data">
                <dl>
                    <p>
                        <input type="file" name="files[]" multiple="true" autocomplete="off" required>
                    </p>
                </dl>
                <p>
                    <input type="submit" value="Submit">
                </p>
            </form>
        </div>
    </div>

</body>
<script>
                $(document).ready(function() {
                $('#myButton').click(function() {
                    $.ajax({
                          type:"get",
                          url: "/foo",
                          dataType:"image/png"
                          success: function(response){
                           $("#img").html(response.html);
                          }
                  });
                });
                });
            </script>
</html>
Nick
  • 4,820
  • 18
  • 31
  • 47
ISTI Vita
  • 51
  • 7
  • I think the easiest way would be to save the image as a file with a specific (dynamically generated) name, pass the name to HTML and then render it like ``. – Ardweaden Dec 17 '19 at 09:17
  • 1
    @Ardweaden actually before i try to use ajax, i use this code ``` ``` , but when i first load the html page there is an image logo there, then after i submit the data input the processed image appear there. what makes me confused is i think the logo is kinda broken image that appear because the processing code couldnt find the input data as the client havent upload it yet, doesnt it mean that actually the processing code already run since the page accessed ? i want the processing function to run after i upload the image and then click on processing button. – ISTI Vita Dec 17 '19 at 09:41
  • Well of course, the image wasn't defined before you saved it so it showed the logo of a broken image. You could do `{% if processed_image_name %}{% endif %}` for as the image element not to show the image if its name is not defined. – Ardweaden Dec 17 '19 at 09:54
  • Btw, I suggest you use unique name for each image, otherwise other users will override each other. You can pass the name to the template like so: https://stackoverflow.com/questions/17057191/redirect-while-passing-arguments – Ardweaden Dec 17 '19 at 10:00
  • @Ardweaden can i apply such thing if i choose to not save the image and use the send file instead ? – ISTI Vita Dec 17 '19 at 10:18
  • You could do something like this: https://stackoverflow.com/questions/31358578/display-image-stored-as-binary-blob-in-template – Ardweaden Dec 17 '19 at 10:28

1 Answers1

1

You first need to run to be able to make a flask form

pip install flask-wtf

once you do that, make a form file like this:

from flask_wtf import FlaskForm
from flask_wtf.file import SubmitField

class foo(FlaskForm):
    image=FileField('Images')
    submit=SubmitField('Submit')

then in your routes file. I haven't found a way to get around using an if statement to make sure the list is populated yet, but honestly I haven't really tried and it works. It will return a list of objects and by making sure the first one's filename isn't an empty string (representing no images were actually selected) then it should work. The python file should look like this.

from formFile import foo

@app.route('/whatever',methods=['POST'])
def bar():
    form=foo()
    images=[]
    if form.image.data[0].filename!='':
        images.append(image)
    return render_template('yourHTML.html',form=form,images=images)

I just added the extra import and didn't add all the imports relevant. Now to access each image you just need to run a simple for loop

for image in form.image.data:
    //for loop contents

with your html file's form part looking similar to this:

<form method="POST" action="" enctype="multipart/form-data">
    {{form.hidden_tag()}}
    <fieldset class="form">
        <legend class="legend">Upload Image</legend>
            <div class="scrolling-wrapper">
                <p class="form-control-label">Current Images</p>
                {%for image in images%}
                    <div class="card"><img src="/static/images/{{image}}" alt="Pic"/></div>
                {%endfor%}
            </div>
        <div class="form-group">
            {{ form.image.label() }}
            {{ form.image(class="form-control-file") }}
            {% if form.image.errors %}
                {% for error in form.image.errors %}
                    <span class="text-danger">{{ error }}</span></br>
                {% endfor %}
            {% endif %}
        </div>
        <div class="form-group">
            {{form.submit(class="btn btn-outline-info")}}
        </div>
    </fieldset>
</form>

where you don't need the error section. The classes here used are part of Bootstrap just to style so obviously you can replace them with your own styling if you'd prefer. The "scrolling-wrapper" class is a custom one just to have all the pictures in one line in a scrollable box. I can add that code of you'd like. That should be everything you need, and if I'm missing anything it might be because I used my own code that implements a saving function that saves the picture to the server and the specific image can be accessed by an SQL Database query. If I had to guess you could just use: src={{image}} instead of src="/static/images/products/{{image}}", but don't hold me to it as I haven't tested that. Also I didn't add any validators, but you know how to implement them anyways.

Tux1965
  • 15
  • 3