Alex Franco

Blog de Alex Franco, aquí podrás encontrar temas de tecnología, desarrollo web y móvil, Python, Django, Mongo, DBs y… otros temas más
Home  /  Codigo  /  Servir o publicar imágenes con Django y MongoDB (GridFS)

Servir o publicar imágenes con Django y MongoDB (GridFS)

Alex Franco Django, GridFS, MongoDB, mongoengine, pymongo, Python Leave a Comment

Mezclar Django y MongoDB es una tarea interesante, con los retos que implica ya que Django por default no tiene una integración con MongoDB como Base de Datos, sin embargo se han hecho esfuerzos por unirlos y hacerlos trabajar sacando las ventajas de ambos.

Uno de los retos con los que me topé es usar GridFS que podemos entenderlo así:

MongoDB puede ser utilizado con un sistema de archivos, tomando la ventaja de la capacidad que tiene MongoDB para el balanceo de carga y la replicación de datos utilizando múltiples servidores para el almacenamiento de archivos. Esta función (que es llamada GridFS ) está incluida en los drivers de MongoDB y disponible para los lenguajes de programación que soporta MongoDB. Esta base de datos expone funciones para la manipulación de archivos y contenido a los desarrolladores. En un sistema con múltiple servidores, los archivos pueden ser distribuidos y copiados entre los mismos varias veces y de una forma transparente, de esta forma se crea un sistema eficiente que maneja fallos y balanceo de carga.

Bien, en resumen, es un sistema de archivos de MongoDB.

Mi problema era publicar las imágenes dado que no las almacena en una carpeta pública por así decirlo, así que había que buscar la solución vía programación. Antes de seguir, los paquetes que estoy usando son los siguientes:

  • Django
  • MongoDB
  • mongoengine
  • django-mongodbforms
  • PIL

Para el caso usé un modelo bastante sencillo, digamos que mi aplicación es un Blog y mi modelo es de un Post

from mongoengine import *
from mongodbforms import DocumentForm


class Post(Document):
    title = StringField(max_length=160, required=True)
    body = StringField(required=True)
    image = ImageField(size=(800, 600, True), thumbnail_size=(150, 112, True))


class PostForm(DocumentForm):
    class Meta:
        document = Post

Hay 2 maneras de realizar la tarea:

  1. Sin thumbnail (miniatura), donde es 100% django.
  2. Con thumbnail, donde tendremos que recurrir a pymongo + django

La primera es muy sencilla, si tu aplicación no requiere de miniaturas, esta es la mejor opción, simplemente requieres conocer el ID de tu imagen:

''' blog/views.py '''
def view_post(request, id):
    post = get_document_or_404(Post, pk=id)
    # img_id temporal para mandar llamar la imagen
    # Si no contiene imagen el post, no muestra nada
    try:
        post.img_id = post.image._id
    except AttributeError:
        post.img_id = False
    return render_to_response('post.html', {'post': post})


def display_image(request, id):
    image = get_document_or_404(Post, image=ObjectId(id)).image
    return HttpResponse(image.read(), content_type=image.contentType)


''' blog/urls.py '''
# ...
                       url(r'^post/(?P<id>\w+)/$', 'view_post'),
                       url(r'^media/(?P<id>\w+)$', 'display_image'),
# ...


''' blog/templates/post.html '''
<p><a href="/blog/">View all posts</a></p>

<p><h2>{{ post.title }}</h2></p>
<p>{{ post.body }}</p>
{% if post.img_id %}
<p style="text-align: center"><img src="/blog/media/{{ post.img_id }}"></p>
{% endif %}

Si por alguna razón requieres el uso de las miniaturas, esta es la receta de cocina para obtener la imagen regular o la miniatura, esto es debido a que directamente con el ORM de Django con mongoengine no hay manera de hacer referencia a la miniatura:

''' blog/views.py '''
def view_post_th(request, id):
    post = get_document_or_404(Post, pk=id)
    # img_id temporal para mandar llamar la imagen
    # Si no contiene imagen el post, no muestra nada
    try:
        post.img_id = post.image._id
    except AttributeError:
        post.img_id = False
    try:
        post.thumbnail_id = post.image.thumbnail_id
    except AttributeError:
        post.thumbnail_id = False
    return render_to_response('post2.html', {'post': post})


def display_image_m2(request, id):
    # Conexión adicional al sistema de archivos de MongoDB
    mongo_con = Connection()
    # Si tu campo en el modelo es ImageField, la collección es 'images'
    grid_fs = gridfs.GridFS(mongo_con.servemedia, collection='images')
    if not grid_fs.exists(ObjectId(id)):
        raise Exception("Mongo file does not exist! {0}".format(ObjectId(id)))
    image = grid_fs.get(ObjectId(id))
    try:
        # Con esta validación sabemos si es miniatura o imagen original,
        # el thumbnail por default se guarda sin el contentType
        content_type = image.contentType
    except AttributeError:
        # Con esto buscamos la imagen original para obtener el contentType del thumbnail
        for grid_out in grid_fs.find({'thumbnail_id': ObjectId(id)}).limit(1):
            content_type = grid_out.contentType
    return HttpResponse(image.read(), content_type=content_type)


''' blog/urls.py '''
# ...
                       url(r'^post2/(?P<id>\w+)/$', 'view_post_th'),
                       url(r'^media2/(?P<id>\w+)$', 'display_image_m2'),
# ...


''' blog/templates/post2.html '''
<p><a href="/blog/">View all posts</a></p>

<p><h2>{{ post.title }}</h2></p>
<p>{{ post.body }}</p>
{% if post.thumbnail_id %}
<p style="text-align: center"><img src="/blog/media2/{{ post.thumbnail_id }}"></p>
{% endif %}
{% if post.img_id %}
<p style="text-align: center"><img src="/blog/media2/{{ post.img_id }}"></p>
{% endif %}

El código completo puedes encontrarlo en mi GitHub  https://github.com/jafrancov/Serve-Media-Django-MongoDB-GridFS espero les sirva y sea de su agrado, cualquier duda, deja tu comentario.

Enjoy!

Relacionado

About Author

Alex Franco

 IT

Previous Article Combinación de Correspondencia con Google Drive (Docs + Hoja de Cálculo + GMail)
Next Article Agregar número consecutivo y enviar correo al contestar Formulario de Google Drive

Related Posts

  • Enviar correo personalizado automáticamente cuando respondan un Formulario de Google

    Enviar correo personalizado automáticamente cuando respondan un Formulario de Google

  • Django + AllAuth + Google OAuth 2.0

    Django + AllAuth + Google OAuth 2.0

  • Agregar número consecutivo y enviar correo al contestar Formulario de Google Drive

Comenta Cancelar respuesta

Posts populares

  • Geocode Simple: Buscar direcciones (coordenadas a partir de direcciones) con GMaps API v3
  • Buscar y trazar rutas con GMaps API v3
  • Recuperar o ver las claves de las redes inalámbricas en tu Mac (o cualquier App)
  • Enviar correo después de contestar Formulario de Google Drive automáticamente

Suscríbete

Suscríbirme!

Quizá te interese

  • Info Window en GMaps API v3
  • Aldea Digital – Día TRES
  • Reseña 2° Destilando Web Guanajuato
  • Combinar Correspondencia con Google Drive y GMail
Alex Franco 2009 - 2023