Jacek Kaczmarek
Jacek Kaczmarek

Reputation: 103

Unable to update image in PUT endpoint

i have basic flask app in python.

I have put method to update data. I want to update assistant in database but image is not updating.

On html page there is 'inputfile' parameter which should download uploaded file and then return it to function.

But i don't know how to solve it. I want image from 'inputfile' to be in updated_assistant.data so i can do something with it later.

Here is my program:

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'

db = SQLAlchemy(app)
migrate = Migrate(app, db)

class Job(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(80), nullable=False)

class Picture(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    data = db.Column(db.LargeBinary)

class Assistant(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    job_id = db.Column(db.Integer, db.ForeignKey('job.id'), nullable=False)
    first_name = db.Column(db.String(80), nullable=False)
    last_name = db.Column(db.String(80), nullable=False)
    email_address = db.Column(db.String(80), nullable=False)
    pic_name = db.Column(db.String(300))
    data = db.Column(db.LargeBinary)

@app.route('/')
def index():
    return render_template('home.html')

def pic_2_thumbnail(image):
    im = Image.open(image)
    return im

def pic_to_bytes(im):
    imgByteArr = io.BytesIO()
    im.save(imgByteArr, format='BMP')
    imgByteArr = imgByteArr.getvalue()
    return imgByteArr

@app.route('/assistants/update/<int:id>', methods=['GET'])
def display_update(id):
        # updated_assistant = Assistant.query.get_or_404(id)

        assistant_job = db.session.query(Assistant, Job).filter((Assistant.job_id==Job.id) & (Assistant.id==id)).all()
        print(assistant_job)

        jobs = Job.query.all()
        return render_template('update_assistant.html', 
        assistant_job=assistant_job,
        jobs=jobs
        )

@app.route('/assistants/<int:id>', methods=['PUT'])
def update_assistant(id):
        updated_assistant = Assistant.query.get_or_404(id)
        data = request.json
        print(data)
        print(request)
        updated_assistant.job_id = data['job_id']
        updated_assistant.first_name = data['first_name']
        updated_assistant.last_name = data['last_name']
        updated_assistant.email_address = data['email_address']
        if data['inputfile'] != '':
            image = data['inputfile']
            size = 80, 80
            im = pic_2_thumbnail(image)
            im.thumbnail(size)
            x= pic_to_bytes(im)
            updated_assistant.data = x
        try:
            db.session.commit()
            return jsonify({'ok': 'ok'})
        except Exception as e:
            print(str(e))
            return 'There was an issue updating your assistant'

Here is html page:

{% extends 'base.html' %}

{% block head %}
<head>
    <h3>Assistant Registration Update Forumlar</h3>
</head>

{% endblock %}

{% block body %}
<script>
    function updateAssistant() {

        id = '{{ assistant_job.0.0.id }}';
        console.log(id);
        url = window.location.origin + '/assistants/' + id;
        fetch(url, {
            method: 'PUT',
            body: JSON.stringify({
                'first_name' : document.getElementById('first_name').value,
                'last_name' : document.getElementById('last_name').value,
                'email_address' : document.getElementById('email_address').value,
                'job_id' : document.getElementById('job_id').value,
                'inputfile' : document.getElementById('inputfile').value,
            }),
            headers: {
            'Content-Type': 'application/json'
            },
    })
    .then(res => res.json())
    .then(data => location.replace(window.location.origin + '/assistants'));

    return false;
    };
</script>

<form onsubmit="return updateAssistant()">

       {% for assistant, job in assistant_job %}    
      <div class="form-group">
            <label for="exampleFormControlINput1">First name:</label>
            <input type="text" name="first_name" id="first_name" class="form-control" id="exampleFormControlInput1"
             value="{{assistant.first_name}}" required>
            <br>
            <label>Lastname:</label>
                <input type="text" name="last_name" id="last_name" class="form-control" id="exampleFormControlInput1"
                value="{{assistant.last_name}}" required>
            <br>
            <label>Email:</label>
                <input type="email" name="email_address" id="email_address" class="form-control" id="exampleFormControlInput1"
                value="{{assistant.email_address}}" required>
            <br>
            <label for="exampleFormControlSelect1">Job name:</label> 
            <select name='job_id' class="form-control" id="job_id" id="exampleFormControlSelect1"
            value="{{assistant.job_id}}" required>
                    <option value="{{ job.id}} ">
                        {{job.title}}
                    </option>
                {% for job in jobs %}
                    <option value="{{ job.id }}">
                {{ job.title }}
                    </option>
                {% endfor %}
            </select> 
            <br>  
            <label for="exampleFormControlFile1">Upload picture:</label>
                <input type = "file" name = "inputfile" class="form-control-file" id="exampleFormControlFile1">
            <br>
        </div>
        {% endfor %}    
        <input type="submit" name="Submit" value="Submit"/>  
    </form>
</div>
{% endblock %}

Upvotes: 1

Views: 67

Answers (1)

J&#243;zef Podlecki
J&#243;zef Podlecki

Reputation: 11283

'inputfile' : document.getElementById('inputfile').value,

This won't work as it is input type="file"

HTMLInputElement of this type has a files property which is a collection of files.

You could take first element of collection and convert it to base64

const toBase64 = file => new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result);
    reader.onerror = error => reject(error);
});

So the updateAssistant with the applied changes would look like.

function updateAssistant() {

        id = '{{ assistant_job.0.0.id }}';
        console.log(id);
        url = window.location.origin + '/assistants/' + id;

        const [file]= document.getElementById('inputfile').files

        return toBase64(file).then(base64 => {
          return fetch(url, {
            method: 'PUT',
            body: JSON.stringify({
                'first_name' : document.getElementById('first_name').value,
                'last_name' : document.getElementById('last_name').value,
                'email_address' : document.getElementById('email_address').value,
                'job_id' : document.getElementById('job_id').value,
                'inputfile' : base64,
            }),
            headers: {
            'Content-Type': 'application/json'
            },
         })
       })
}

As for the server side you have convert back base64 string

from PIL import Image
from base64 import decodestring

image = Image.fromstring('RGB',(width,height),decodestring(imagestr))
image.save("foo.png")

Or if you don't like this approach at all you can go back to good old form data

Upvotes: 1

Related Questions