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:
- Sin thumbnail (miniatura), donde es 100% django.
- 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!
Comenta