2

I have several images stored within MongoDB and I would like to display them now in a web page. I'm using Flask for my application. The record has a binary field named "payload"

class BinaryFile(mongo.Document):
    created_at = mongo.DateTimeField(default=datetime.datetime.now, required=True)
    file_name = mongo.StringField(max_length=255, required=True)
    payload = mongo.ImageField(required=False)

I want to diplay payload. Any clues on how to do this? I've tried

<img src="{{ image.payload }}"/>

which simply gives me a broken link. The source of the page then reads

<img src="&lt;ImageGridFsProxy: None&gt;"/>

If I try

<img src="{{ image.payload.read() }}"/>

I get

UnicodeDecodeError: 'ascii' codec can't decode byte 0x89 in position 0: ordinal not in range(128)

Kinda stumped. Any ideas? Thanks!

Brian Dolan
  • 2,930
  • 2
  • 20
  • 33

2 Answers2

3

The image.payload.read() returns the raw data of the image and this is what we want, except we don't want to put it in the IMG tag's src attribute.

What we want, is to serve the raw image data as an image and put that image's URL to the src attribute.

Here is one example how it can be done with a temporary file. And this is most likely the solution you want.

from tempfile import NamedTemporaryFile
from shutil import copyfileobj

tempFileObj = NamedTemporaryFile(mode='w+b',suffix='jpg')
copyfileobj(image.payload,tempFileObj)
tempFileObj.seek(0,0)

And then serve the file in a view

from flask import send_file

@app.route('/path')
def view_method():
    response = send_file(tempFileObj, as_attachment=False, attachment_filename='myfile.jpg')
    return response

It might be possible to send the image data straight from the ImageGridFSProxy object and skip the temporary file, but I am not sure about it.

----------

Now that you got your code done, I'll post the way I would have done it. And the way I tried to explain. :)

Here's my app.py.

from flask import Flask, send_file, render_template
import mongoengine as mo

app = Flask(__name__)
c = mo.connection.connect('localhost')

@app.route('/')
def index():
    images = MyDoc.objects.all()
    return render_template('template.html', images=images)

# Separate view for the images
@app.route('/image/<img_name>')
def image(img_name):
    image = MyDoc.objects(file_name=img_name).first()
    # This is where the tempfile stuff would have been if it would
    # have been needed.
    if image:
        return send_file(image.payload, mimetype='image')
    else:
        return "404" # might want to return something real here too

class MyDoc(mo.Document):
    file_name = mo.StringField(max_length=255, required=True)
    payload = mo.ImageField(required=True)

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

And here's the template.

<html>
<body>
<div>
    This is a body!
    <div>
        {% if images %}
           {% for image in images %}
             {{ image.file_name }}
             <img src="/image/{{ image.file_name }}" />
             I'm an image!<br><br>
           {% endfor %}
        {% endif %}

    </div>
</div>
</body>
</html>

The whole tempfile thing was unnecessary as the payload could be sent directly with the send_file. The mimetype was needed to tell the browser that "this is an image" and the browser should just show it and not download it. It wasn't the as_attachment as I suspected earlier.

This way there is no need to save the files with the tempfile or into a directory where they would be served statically instead.

Community
  • 1
  • 1
Sevanteri
  • 2,964
  • 18
  • 25
  • Thanks, I'll give this a try. It feels inefficient to save a temp file. Also, I want to render the image and it looks like the send_file method attaches it as a download. Last, can you do multiple files this way? I appreciate your help! – Brian Dolan Feb 01 '15 at 18:46
  • Ah, might be that the `as_attachment=True` is the reason it starts to download it. Switch that to `False` and it should work. – Sevanteri Feb 02 '15 at 06:55
  • You can do multiple files with this by giving the images some IDs and refere to them in the view by routing something like this: `@app.route('/image/')` and then getting the right image with the ID. – Sevanteri Feb 02 '15 at 06:57
  • Mongo already gives IDs to the documents if I recall correctly. You could use those. – Sevanteri Feb 02 '15 at 06:59
  • I've been playing with this and will update this afternoon. I really do appreciate your help! – Brian Dolan Feb 03 '15 at 14:46
  • @BrianDolan I edited my post to include the way I would have done it. :) – Sevanteri Feb 04 '15 at 06:43
0

Many thanks to @sevanteri for getting me started. For posterity, this is what I ended up doing

The view:

@app.route('/index')
def index():
    images = BinaryFile.objects.all()
    for image in images:
        # This was the temp file idea, abandoned...
        # tmpImageFile = NamedTemporaryFile(mode="w+b", suffix="jpg", delete=False, dir="app/static/temporary_files")
        # copyfileobj(image.payload,tmpImageFile)
        # tmpImageFile.seek(0,0)
        with open(app.config['TEMPORARY_FILE_DIR'] + "/" + image.file_name, 'wb') as f:
            f.write(image.payload.read())
        # image.temporary_file_location = app.config['TEMPORARY_FILE_DIR'] + "/" + image.file_name
        image.temporary_file_location = "static/tmp/" + image.file_name


    return render_template('dashboard.html', images=images)

the html:

{% block content %}
<div>
    This is a body!
    <div>
        {% if images %}
           {% for image in images %}
             {{ image.file_name }}
             <img src="{{ image.temporary_file_location }}"/>
             I'm an image!<br><br>
           {% endfor %}
        {% endif %}

    </div>
</div>

{% endblock %}

the model:

import datetime
from app import mongo
from flask import url_for

class BinaryFile(mongo.Document):
    created_at = mongo.DateTimeField(default=datetime.datetime.now, required=True)
    file_name = mongo.StringField(max_length=255, required=True)
    payload = mongo.ImageField(required=False)

    def get_absolute_url(self):
        return url_for('post', kwargs={"binary_file": self.file_name})

    def __unicode__(self):
        return self.title

So this kinda sucks because I am using a copy command. It satisfied my POC of saving images in mongo then displaying them, and I'll let my engineers figure out the management of the actual files. They are smarter than I am.

Thanks SO!

Brian Dolan
  • 2,930
  • 2
  • 20
  • 33