Guía para usar el motor Django MongoDB con Python

En este tutorial, echaremos un vistazo al motor Django MongoDB heredado, cubriendo algunas de las características útiles de la biblioteca a través de ejemplos en Python.

Introducción

En este artículo, veremos cómo usar MongoDB, una base de datos no relacional, con Django, un marco web de Python.

Django se usa comúnmente con PostgreSQL, MariaDB o MySQL, todas las bases de datos relacionales, debido a su ORM bajo el capó. MongoDB, al ser bastante flexible, se combina comúnmente con marcos livianos como Flask para facilitar la creación de prototipos. Sin embargo, también se usa cada vez más en proyectos más grandes debido a la escalabilidad, las estructuras dinámicas y el soporte de consultas.

El motor Django MongoDB se utiliza para definir esquemas declarativamente.

Nota: Al momento de escribir este artículo, este motor no es compatible con Python 3.x. La última versión compatible es Python 2.7.

Bases de datos no relacionales vs relacionales

La diferencia clave de este motor en comparación con otros motores populares es que funciona con una base de datos no relacional, mientras que las aplicaciones de Django se desarrollan más comúnmente con bases de datos relacionales.

Elegir entre estos dos enfoques se reduce al proyecto en el que está trabajando, ya que cada tipo tiene ciertas ventajas y desventajas según la situación. Las bases de datos no relacionales suelen ser más flexibles (tanto a favor como en contra), mientras que las bases de datos relacionales son más conformadas (también, tanto a favor como en contra).

Las bases de datos no relacionales también son, generalmente, mejores para sistemas escalables que contienen muchos datos. Sin embargo, para los sistemas pequeños y medianos, a menudo prevalece la facilidad de mantener bases de datos relacionales.

Base de datos relacional {#base de datos relacional}

Una base de datos relacional almacena datos en tablas, que constan de columnas y filas.

  • Una fila representa una entidad (por ejemplo, una Película)
  • Una columna representa un atributo de la entidad (por ejemplo, ’nombre’ de la película, su ‘duración’, ‘año’ de lanzamiento, etc.)
  • Una fila representa una entrada en una base de datos (por ejemplo, {"The Matrix", 2h 16min, 1999.}).

Cada fila de la tabla debe tener una clave única (un ID), que representa únicamente esa fila.

Algunas de las bases de datos relacionales más famosas son: Oracle, PostgreSQL, MySQL y MariaDB.

Base de datos no relacional

Una base de datos no relacional no almacena datos en tablas, sino que depende del tipo de datos. Hay cuatro tipos diferentes de bases de datos no relacionales:

  • Base de datos orientada a documentos (o almacén de documentos)
    • Manages a set of named string fields usually in a form of JSON, XML or YAML documents. These formats can also have derivatives.
  • Tienda de Columna Ancha
    • Organizes data in columns, in a similar structure to relational databases
  • Tienda de gráficos
    • Stores relations between entities (most complex type of non-relational database)
    • Used when data is widely interconnected
  • Almacenamiento de clave-valor
    • Simple key-value pair collection

Algunas de las bases de datos no relacionales más famosas son: MongoDB, Cassandra, Redis.

bases de datos relacionales vs no relacionales

MongoDB es una base de datos no relacional basada en documentos, que guarda documentos en formato BSON (JSON binario), un derivado de JSON.

Instalación y configuración

Para implementar Django MongoDB Engine en un proyecto, querremos instalar tres cosas:

  1. Django-nonrel - Soporte para bases de datos no relacionales (Esto también instalará Django 1.5 y desinstalará cualquier versión instalada anteriormente).
  2. djangotoolbox - Herramientas para aplicaciones Django no relacionales.
  3. Motor Django MongoDB: el motor en sí.

Instalémoslos a través de pip, junto con Django:

1
2
3
4
$ pip install django
$ pip install git+https://github.com/django-nonrel/[correo electrónico protegido]
$ pip install git+https://github.com/django-nonrel/djangotoolbox
$ pip install git+https://github.com/django-nonrel/mongodb-engine

Inicialicemos un proyecto Django a través de la línea de comandos para obtener un punto de partida:

1
$ django-admin.py startproject djangomongodbengine

Ahora, con un proyecto esqueleto que contiene algunos archivos básicos, queremos que Django sepa qué motor nos gustaría usar. Para hacer eso, actualizaremos nuestro archivo settings.py, y más específicamente, la propiedad DATABASES:

1
2
3
4
5
6
DATABASES = {
   'default' : {
      'ENGINE' : 'django_mongodb_engine',
      'NAME' : 'example_database'
   }
}

Una vez finalizada la instalación y la configuración, echemos un vistazo a algunas de las cosas que podemos hacer con Django MongoDB Engine.

Modelos y campos

Cuando se trata de trabajar con Modelos, en una arquitectura estándar MVC (Model-View-Controller), el enfoque clásico es usar el módulo django.db.models. La clase Model tiene CharFields, TextFields, etc. que le permiten definir esencialmente el esquema de sus modelos y cómo serán mapeados a la base de datos por el ORM de Django.

Agreguemos un modelo Movie a nuestro models.py:

1
2
3
4
5
from django.db import models

class Movie(models.Model)
    name = models.CharField()
    length = models.IntegerField()

Aquí, tenemos un modelo de “Película” que tiene dos campos: “nombre” y “duración”. Cada uno de estos es una implementación de Field, que representa una columna de base de datos, con el tipo de datos dado.

Si bien hay un un poco de tipos de campo, el módulo models no tienen un gran soporte para campos que tienen múltiples valores.

Esto se debe principalmente a que el módulo modelos está destinado a ser utilizado principalmente con bases de datos relacionales. Cuando un objeto tiene un campo con múltiples valores, como una ‘Película’ que tiene muchos ‘Actores’, tendría una relación Uno-a-Muchos con otra tabla.

Con MongoDB, puede guardarlos como una lista dentro de ese documento, sin tener que hacer una referencia de base de datos a otra tabla o documento. Aquí es donde sentimos la falta de campos como ListField y DictField.

Campo de lista

ListField es un atributo de tipo lista, un atributo que puede contener múltiples valores. Pertenece al módulo djangotoolbox.fields y se puede usar para especificar campos que contienen valores similares a una lista, que luego se guardan en el documento BSON.

Modifiquemos nuestro modelo Película de antes:

1
2
3
4
5
6
7
8
from django.db import models
from djangotoolbox.fields import ListField

class Movie(models.Model):
    name = models.CharField()
    length = models.IntegerField()
    year = models.IntegerField()
    actors = ListField()

Tenga en cuenta que no especificamos el campo id. No es necesario, ya que MongoDB lo asignará implícitamente a la instancia del Modelo. Además, agregamos el campo actores, que es un ListField.

Ahora, al crear una instancia de “Película”, podemos asignar una lista al campo “actores” y guardarla en nuestra base de datos MongoDB tal cual, sin crear una tabla separada para contener instancias de “Actor” y hacer referencia a ellas en nuestra “Película”. ` documentos:

1
2
3
4
5
6
movie = Movie.objects.create(
    name = "The Matrix",
    length = 136,
    year = 1999,
    actors = ["Keanu Reeves", "Laurence Fishburne"]
)

Ejecutar este fragmento de código da como resultado un documento MongoDB:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "_id" : ObjectId("..."),
  "name" : "The Matrix",
  "length" : 136,
  "year" : 1999,
  "actors" : [
    "Keanu Reeves", 
    "Laurence Fishburne"
  ]
}

También podemos extender() el ListField y agregarle más valores:

1
movie.actors.extend(['Carrie-Ann Moss'])

Esto da como resultado un documento BSON actualizado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "_id" : ObjectId("..."),
  "name" : "The Matrix",
  "length" : 136,
  "year" : 1999,
  "actors" : [
    "Keanu Reeves", 
    "Laurence Fishburne",
    "Carrie-Ann Moss",
    "Carrie-Ann Moss"
  ]
}

Establecer campo

SetField es lo mismo que ListField excepto que se interpreta como un conjunto de Python, lo que significa que no se permiten duplicados.

Si añadimos dos veces al mismo actor:

1
movie.actors.extend(['Carrie-Ann Moss'])

Rápidamente nos damos cuenta de que la salida es un poco rara:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "_id" : ObjectId("..."),
  "name" : "The Matrix",
  "length" : 136,
  "year" : 1999,
  "actors" : [
    "Keanu Reeves", 
    "Laurence Fishburne",
    "Carrie-Ann Moss"
  ]
}

Ya que nos gustaría evitar entradas duplicadas, haciendo que cada individuo siga siendo un individuo real, tiene más sentido hacer de los actores un SetField en lugar de un ListField:

1
2
3
4
5
6
7
8
from django.db import models
from djangotoolbox.fields import ListField

class Movie(models.Model):
    name = models.CharField()
    length = models.IntegerField()
    year = models.IntegerField()
    actors = SetField()

Ahora, podemos agregar múltiples actores, algunos de los cuales son duplicados, y solo tendremos adiciones únicas:

1
2
3
4
5
6
movie = Movie.objects.create(
    name = "John Wick",
    length = 102,
    year = 2014,
    actors = ["Keanu Reeves", "Keanu Reeves", "Bridget Moynahan"]
)

Sin embargo, el documento resultante solo tendrá una entrada para "Keanu Reeves", la única:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "_id" : ObjectId("..."),
  "name" : "John Wick",
  "length" : 102,
  "year" : 2014,
  "actors" : [
    "Keanu Reeves", 
    "Bridget Moynahan"
  ]
}

campo de dictado

DictField almacena diccionarios de Python, como otro documento BSON, dentro de su propio documento. Estos son preferibles cuando no está seguro de cómo se vería el diccionario y no tiene una estructura predefinida para él.

Por otro lado, si la estructura es familiar, se recomienda utilizar Modelos integrados, como modelos dentro de modelos. Por ejemplo, un ‘Actor’ podría ser un modelo propio, y podríamos dejar que el modelo ‘Película’ tenga múltiples modelos de ‘Actor’ incrustados. Por otro lado, si se va a agregar un conjunto variable de valores, se pueden asignar como elementos clave-valor y guardar a través de un DictField.

Por ejemplo, agreguemos un campo revisiones, que puede tener 0..n revisiones. Si bien las revisiones tienen una estructura predecible (nombre, calificación, comentario), las implementaremos como un DictField, antes de crear un Modelo separado para actores y opiniones:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from django.db import models
from djangotoolbox.fields import SetField
from djangotoolbox.fields import DictField

class Movie(models.Model):
    name = models.CharField()
    length = models.IntegerField()
    year = models.IntegerField()
    actors = SetField()
    reviews = DictField()

Ahora, al crear películas, podemos agregar diccionarios de revisores y sus reseñas de las películas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
movie = Movie.objects.create(
    name = "Good Will Hunting",
    length = 126,
    year = 1997,
    actors = ["Matt Damon", "Stellan Skarsgard"],
    reviews = [
        {"Portland Oregonian" : "With its sweet soul and sharp mind..."},
        {"Newsweek" : "Gus Van Sant, working from the tangy, well-written script..."}
    ]
)

Ejecutar este código da como resultado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "_id" : ObjectId("..."),
  "name" : "Good Will Hunting",
  "length" : 126,
  "year" : 1997,
  "actors" : [
    "Matt Damon", 
    "Stellan Skarsgard"
  ],
  "reviews" : [
    {"Portland Oregonian" : "With its sweet soul and sharp mind..."},
    {"Newsweek": "Gus Van Sant, working from the tangy, well-written script..."}
  ]
}

Modelos integrados {#modelos integrados}

Ahora, el campo reseñas seguirá, posiblemente, el mismo tipo de estructura: nombre seguido de comentario. Los ‘actores’ son más que solo sus nombres: tienen un ‘apellido’, ‘fecha_de_nacimiento’ y otras características.

Para ambos de estos, podemos hacer modelos independientes, como lo haríamos con bases de datos relacionales. Sin embargo, con las bases de datos relacionales, las guardaríamos en sus propias tablas y vincularíamos a ellas desde la tabla Película.

Con MongoDB, podemos convertirlos en Modelos integrados: documentos completos, integrados en otro documento.

Cambiemos nuestra Película una vez más:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from django.db import models
from djangotoolbox.fields import ListField, EmbeddedModelField


class Movie(models.Model):
    name = models.CharField(max_length=100)
    length = models.IntegerField()
    year = models.IntegerField()
    actors = SetField(EmbeddedModelField("Actor"))
    reviews = SetField(EmbeddedModelField("Review"))

Aquí, hemos creado un SetField (que también podría haber sido algo así como un ListField) tanto para actores como para reseñas. Sin embargo, esta vez, los hemos convertido en SetFields de otros modelos, pasando EmbeddedModelField a los constructores de SetFields.

También hemos especificado qué modelos en el constructor de la clase EmbeddedModelField.

Ahora, definamos esos dos también, en el archivo models.py:

1
2
3
4
5
6
7
8
class Actor(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    date_of_birth = models.CharField(max_length=11)
    
class Review(models.Model):
    name = models.CharField(max_length=30)
    comment = models.CharField(max_length=300)

Ahora, al crear un objeto “Película” y guardarlo en la base de datos, también podemos agregarle nuevas instancias de “Actor” y “Revisión”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
movie = Movie.objects.create(
    name = "Focus",
    length = 105,
    year = 2015,
    actors = [
        Actor(
            first_name="Will",
            last_name="Smith", 
            date_of_birth="25.09.1968."
        )
    ],
    reviews = [
        Review(
            name = "Portland Oregonian",
            comment = "With its sweet soul and sharp mind..."
        ),
        Review(
            name = "Newsweek",
            comment = "Gus Van Sant, working from the tangy, well-written script..."
        )
    ]
)

Esto crea nuevos documentos BSON para cada ‘Actor’ y ‘Revisión’ en los conjuntos, y los guarda como objetos incrustados en nuestro documento de ‘película’:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "_id" : ObjectId("..."),
  "name" : "Focus",
  "length" : 105,
  "year" : 2015,
  "actors" : [
      {
          "name" : "Will",
          "last_name" : "Smith",
          "date_of_birth" : "25.09.1968"
        }   
    ],
    "reviews" : [
        {
          "name" : "Portland Oregonian",
          "comment" : "With its sweet soul and sharp mind..."
        },
        {
          "name" : "Newsweek",
          "comment" : "Gus Van Sant, working from the tangy, well-written script..."
        }
    ]
}

Cada entrada en la matriz BSON revisiones es una instancia individual de Revisión. Lo mismo ocurre con los ‘actores’.

Manejo de archivos {#manejo de archivos}

MongoDB tiene una especificación incorporada para almacenar/recuperar archivos en el sistema de archivos llamada GridFS, que también se usa en Django MongoDB Engine.

Nota: MongoDB almacena archivos separándolos en partes con un tamaño de 255 kB cada una. Cuando se accede al archivo, GridFS recopila las piezas y las fusiona.

Para importar el sistema GridFS, accederemos al módulo django_mongodb_engine_storage:

1
2
3
4
from django_mongodb_engine.storage import GridFSStorage

gridfs = GridFSStorage()
uploads_location = GridFSStorage(location = '/uploaded_files')

Otro campo que podemos usar es GridFSField(), que nos permite especificar campos que utilizan el sistema GridFS para almacenar datos:

1
2
3
4
5
6
7
class Movie(models.Model):
    name = models.CharField()
    length = models.IntegerField()
    year = models.IntegerField()
    actors = SetField(EmbeddedModelField("Actor"))
    reviews = SetField(EmbeddedModelField("Review"))
    poster = GridFSField()

Ahora esta imagen se guardará en fragmentos y lazy-loaded solo bajo demanda.

Conclusión

En resumen, Django MongoDB Engine es un motor bastante potente y la principal desventaja de usarlo es que funciona con versiones antiguas de Django (1.5) y Python (2.7), mientras que Django ahora tiene 3.2 LTS y soporte para 1.5. terminó hace mucho tiempo. Python está en 3.9 y el soporte para 2.7 finalizó el año pasado. Además de todo eso, Django MongoDB Engine parece haber detenido un mayor desarrollo en 2015.