Doff: Referencia de la API de Base de Datos

La API de base de datos es la otra mitad de la API de modelos. Una vez definido un modelo, esta API es la que se utiliza en todo momento que se necesite acceder a la base de datos. Este apéndice explica todas las opciones de acceso a los datos detalladamente.

A lo largo de este apéndice, vamos a hacer referencia a los siguientes modelos, los cuales pueden formar una simple aplicación de blog:

var models = require('doff.db.models.base');

var Blog = type('Blog', [ models.Model ], {
    name: new models.CharField({ max_length: 100 }),
    tagline: new models.TextField(),

    __str__: function() {
        return this.name;
    }
});

var Author = type('Author', [ models.Model ], {
    name: new models.CharField({ max_length: 50 }),
    email: new models.EmailField(),

    __str__: function() {
        return this.name;
    }
});

var Entry = type('Entry', [ models.Model ], {
    blog: new models.ForeignKey(Blog),
    headline: new models.CharField({ max_length: 255 }),
    body_text: new models.TextField(),
    pub_date: new models.DateTimeField(),
    authors: new models.ManyToManyField(Author),

    __str__: function() {
        return this.headline;
    }
});

Creando Objetos

Para crear un objeto, se debe crear una instancia de la clase modelo usando argumentos de palabra clave y luego llama a save() para grabarlo en la base de datos:

>>> require('mysite.blog.models', 'Blog');
>>> var b = new Blog({ name: 'Beatles Blog',
    tagline: 'All the latest Beatles news.' });
>>> b.save();

Esto, ejecuta una sentencia SQL INSERT. No se produce el acceso a la base de datos hasta que explícitamente se invoque a save().

El método save() no retorna nada.

Para crear un objeto y grabarlo todo en un paso se usa el método create de el tipo Manager, que se describe más adelante.

Qué Pasa cuando se Graba?

Cuando se graba un objeto, se realizan los siguientes pasos:

  1. Emitir un evento pre_save. Provee una notificación de que un objeto está a punto de ser grabado. Se puede registrar un *listener* que será invocado en cuanto esta señal sea emitida.

  2. Preprocesar los datos. Se le solicita a cada campo del objeto implementar cualquier modificación automatizada de datos que pudiera necesitar realizar.

    La mayoría de los campos no realizan pre-procesamiento – los datos del campo se guardan tal como están. Sólo se usa pre-procesamiento en campos que tienen comportamiento especial, como campos de archivo.

  3. Preparar los datos para la base de datos. Se le solicita a cada campo que provea su valor actual en un tipo de dato que puede ser grabado en la base de datos.

    La mayoría de los campos no requieren preparación de los datos. Los tipos de datos simples, como enteros y cadenas, están “listos para escribir”. Sin embargo, tipos de datos más complejos requieren a menudo alguna modificación. Por ejemplo, DateFields usa un objeto Date para almacenar datos. Las bases de datos no almacenan objetos Date, de manera que el valor del campo debe ser convertido en una cadena de fecha que cumpla con la norma ISO correspondiente, para la inserción en la base de datos.

  4. Insertar los datos en la base. Los datos preprocesados y preparados son entonces incorporados en una sentencia SQL para su inserción en la base de datos.

  5. Emitir un evento post_save. Como con el evento pre_save, éste es utilizado para proporcionar notificación de que un objeto ha sido grabado satisfactoriamente.

Claves Primarias Autoincrementales

Por conveniencia, a cada modelo se le da una clave primaria autoincremental llamada id a menos que explícitamente se especifique primary_key = true en el campo (ver la sección AutoField en el apéndice sobre modelos).

Si el modelo tiene un AutoField, ese valor incrementado automáticamente será calculado y grabado como un atributo del objeto la primera vez que save() se llame:

>>> var b2 = new Blog({ name: 'Cheddar Talk',
    tagline: 'Thoughts on cheese.' });
>>> var b2.id;     // Retorna indefinido, porque no tiene ID.
null

>>> b2.save();
>>> b2.id;     // Retorna el ID del objeto.
14

No hay forma de saber cuál será el valor de un identificador antes que se llame a save() esto se debe a que ese valor es calculado por la base de datos.

Si un modelo tiene un AutoField pero se quiere definir el identificador de un nuevo objeto explícitamente cuando se graba, sólo hay que definirlo explícitamente antes de grabarlo en vez de confiar en la asignación automática de valor del identificador:

>>> var b3 = new Blog({ id: 3, name: 'Cheddar Talk',
            tagline: 'Thoughts on cheese.'} );
>>> b3.id;
3
>>> b3.save();
>>> b3.id;
3

Si se asignan manualmente valores de claves primarias autoincrementales ¡No usar un valor de clave primaria que ya existe!. Si se crea un objeto con un valor explícito de clave primaria que ya existe en la base de datos, se asumirá que se está cambiando el registro existente en vez de crear uno nuevo.

Dado el ejemplo precedente de blog 'Cheddar Talk', este ejemplo sobreescribiría el registro previo en la base de datos:

>>> var b4 = new Blog({ id: 3, name: 'Not Cheddar',
    tagline: 'Anything but cheese.' });
>>> b4.save();  // Sobrescribe el objeto previo con ID=3!

El especificar explícitamente valores de claves primarias autoincrementales es más útil cuando se están grabando objetos en lotes, cuando se está seguro de que no se tendrán colisiones de claves primarias.

Grabando Cambios de Objetos

Para grabar los cambios hechos a un objeto que existe en la base de datos, utilizar save().

Dada la instancia de Blog b5 que ya ha sido grabada en la base de datos, este ejemplo cambia su nombre y actualiza su registro en la base:

>>> b5.name = 'New name';
>>> b5.save();

Esto ejecuta una sentencia SQL UPDATE. De nuevo: no se accede a la base de datos hasta que save() es llamado explícitamente.

Cómo se sabe cuando usar UPDATE y cuándo usar INSERT

Los objetos de base de datos usan el mismo método save() para crear y cambiar objetos. La necesidad de usar sentencias SQL INSERT o UPDATE es abstraída. Específicamente, cuando se llama a save(), este es el algoritmo a seguir:

  • Si el atributo clave primaria del objeto tiene asignado un valor que evalúa true (esto es, un valor distinto a null o a la cadena vacía) se ejecuta una consulta SELECT para determinar si existe un registro con la clave primaria especificada.
  • Si el registro con la clave primaria especificada ya existe, se ejecuta una consulta UPDATE.
  • Si el atributo clave primaria del objeto no tiene valor o si lo tiene pero no existe un registro, se ejecuta un INSERT.

Debido a esto, se debe tener cuidado de no especificar un valor explícito para una clave primaria cuando se graban nuevos objetos si es que no se puede garantizar que el valor de clave primaria está disponible para ser usado.

La actualización de campos ForeignKey funciona exactamente de la misma forma; simplemente se asigna un objeto del tipo correcto al campo en cuestión:

>>> var joe = Author.objects.create({ name: "Joe" });
>>> entry.author = joe;
>>> entry.save();

Recuperando Objetos

Se recuperan objetos usando código como el siguiente:

>>> blogs = Blog.objects.filter({ author__name__contains: "Joe" });

Hay bastantes partes móviles detrás de escena aquí: cuando se recuperan objetos de la base de datos, lo que realmente ocurre es la creación de un QuerySet usando el Manager del modelo. Este QuerySet sabe cómo ejecutar SQL y retornar los objetos solicitados.

El apéndice sobre modelos trata ambos objetos desde el punto de vista de la definición del modelo.

Un QuerySet representa una colección de objetos de la base de datos. Puede tener cero, uno, o muchos filtros – criterios que limitan la colección basados en parámetros provistos. En términos de SQL un QuerySet se compara a una declaración SELECT y un filtro es una cláusula de limitación, como por ejemplo WHERE o LIMIT.

Se consigue un QuerySet usando el Manager del modelo. Cada modelo tiene por lo menos un Manager y tiene, por omisión, el nombre objects. Se accede al mismo directamente a través de la clase del modelo:

>>> Blog.objects
<doff.db.models.manager.Manager object>

Los Managers sólo son accesibles a través de las clases de los modelos, en vez desde una instancia de un modelo, para así hacer cumplir con la separación entre las operaciones a “nivel de tabla” y las operaciones a “nivel de registro”:

>>> var b = new Blog({ name: 'Foo', tagline: 'Bar' });
>>> b.objects;
AttributeError: Manager isn't accessible via Blog instances.

El Manager es la principal fuente de QuerySets para un modelo. Actúa como un QuerySet “raíz” que describe todos los objetos de la tabla de base de datos del modelo. Por ejemplo, Blog.objects es el QuerySets inicial que contiene todos los objetos Blog en la base de datos.

Caching y QuerySets

Cada QuerySet contiene una caché, para minimizar el acceso a la base de datos. Es importante entender como funciona, para escribir código más eficiente.

En un QuerySet recién creado, la caché está vacía. La primera vez que un QuerySet es evaluado – y, por lo tanto, ocurre un acceso a la base de datos – Se graba el resultado de la consulta en la caché del QuerySet y retorna los resultados que han sido solicitados explícitamente (por ejemplo, el siguiente elemento, si se está iterando sobre el QuerySet). Evaluaciones subsecuentes del QuerySet reusan los resultados alojados en la caché.

Hay que tener presente este comportamiento de caching, para usar los QuerySets correctamente. Por ejemplo, el siguiente ejemplo creará dos QuerySets, los evaluará, y los descartará:

print([e.headline for (e in Entry.objects.all())]);
print([e.pub_date for (e in Entry.objects.all())]);

Eso significa que la consulta será ejecutada dos veces en la base de datos, duplicando la carga sobre la misma. También existe una posibilidad de que las dos listas pudieran no incluir los mismos registros de la base de datos, porque se podría haber agregado o borrado un Entry durante el pequeñísimo período de tiempo entre ambas peticiones.

Para evitar este problema, simplemente se debe grabar el QuerySet y reusarlo:

var queryset = Poll.objects.all();
// Evaluate the query set.
print([p.headline for (p in queryset)]);
// Reuse the cache from the evaluation.
print([p.pub_date for (p in queryset)]);

Filtrando Objetos

La manera más simple de recuperar objetos de una tabla es conseguirlos todos. Para hacer esto, usa el método all() en un Manager:

>>> Entry.objects.all();

El método all() retorna un QuerySet de todos los objetos de la base de datos.

Sin embargo, usualmente sólo se necesita seleccionar un subconjunto del conjunto completo de objetos. Para crear tal subconjunto, se refina el QuerySet inicial, añadiendo condiciones con filtros. Usualmente éstos son lo métodos filter() o exclude():

>>> y2006 = Entry.objects.filter({ pub_date__year: 2006 });
>>> not2006 = Entry.objects.exclude({ pub_date__year: 2006 })

Tanto filter() como exclude() toman argumentos de patrones de búsqueda, los cuales se discutirán detalladamente más adelante.

Encadenando Filtros

El resultado de refinar un QuerySet es otro QuerySet así que es posible enlazar refinamientos, por ejemplo:

>>> qs = Entry.objects.filter({ headline__startswith: 'What' });
>>> qs = qs.exclude({ pub_date__gte: new Date() });
>>> qs = qs.filter({ pub_date__gte: new Date(2005, 1, 1) });

Esto toma el QuerySet inicial de todas las entradas en la base de datos, agrega un filtro, luego una exclusión, y luego otro filtro. El resultado final es un QuerySet conteniendo todas las entradas con un título que empieza con “What” que fueron publicadas entre Enero 1, 2005, y el día actual.

Es importante precisar aquí que los QuerySet son perezosos – el acto de crear un QuerySet no implica ninguna actividad en la base de datos. De hecho, las tres líneas precedentes no hacen ninguna llamada a la base de datos; se pueden enlazar/encadenar filtros todo el día y no se ejecutará realmente la consulta hasta que el QuerySet sea evaluado.

La evaluación de un QuerySet ocurre en cualquiera de las siguientes formas:

  • Iterando: Un QuerySet es iterable, y ejecuta su consulta en la base de datos la primera vez que se itera sobre él. Por ejemplo, el siguiente QuerySet no es evaluado hasta que sea iterado sobre él en el bucle for:

    qs = Entry.objects.filter({ pub_date__year: 2006 });
    qs = qs.filter({ headline__icontains: "bill" });
    for (var e in qs)
        print(e.headline);
    

    Esto imprime todos los títulos desde el 2006 que contienen “bill” pero genera sólo un acceso a la base de datos.

  • Rebanado: Según lo explicado en la próxima sección Limitando QuerySets, un QuerySet puede ser rebanado usando la sintaxis de rebanado de arreglos de Javascript. Usualmente el rebanar un QuerySet retorna otro QuerySet (no evaluado), pero se ejecutará la consulta a la base de datos si se usa el parámetro “step” de la sintaxis de rebanado.

  • Convirtiendo a un arreglo: Se puede forzar la evaluación de un QuerySet ejecutando array() sobre el mismo, por ejemplo:

    >>> entry_list = array(Entry.objects.all());
    

    Sin embargo, se advierte que ésto podría significar un gran impacto en la memoria ya que se cargará cada elemento de la lista en memoria. En cambio, al iterar sobre un QuerySet se saca ventaja de la base de datos para cargar datos e inicializar objetos sólo a medida que se van necesitando los mismos.

Los QuerySets filtrados son únicos

Cada vez que se refina un QuerySet se obtiene un nuevo QuerySet que no está de ninguna manera atado al QuerySet` anterior. Cada refinamiento crea un QuerySet separado y distinto que puede ser almacenado, usado y reusado:

q1 = Entry.objects.filter({ headline__startswith: "What" });
q2 = q1.exclude({ pub_date__gte: new Date() });
q3 = q1.filter({ pub_date__gte: new Date() });

Estos tres QuerySets son separados. El primero es un QuerySet base que contiene todas las entradas que contienen un título que empieza con “What”. El segundo es un subconjunto del primero, con un criterio adicional que excluye los registros cuyo pub_date es mayor que el día de hoy. El tercero es un subconjunto del primero, con un criterio adicional que selecciona sólo los registros cuyo pub_date es mayor que el día de hoy. El QuerySet inicial (q1) no es afectado por el proceso de refinamiento.

Limitando QuerySets

Se usa la sintaxis de rebanado de arreglos de JavaScript para limitar los QuerySet a un cierto número de resultados. Esto es equivalente a las clausulas de SQL de LIMIT y OFFSET.

Por ejemplo, para retornar las primeras cinco entradas (LIMIT 5):

>>> Entry.objects.all().slice(0, 5);

y para retornar las entradas desde la sexta hasta la décima: (OFFSET 5 LIMIT 5):

>>> Entry.objects.all().slice(5, 10);

Generalmente, el rebanar un QuerySet retorna un nuevo QuerySet – no evalúa la consulta. Una excepción es si se usa el parámetro “step” de la sintaxis de rebanado. Por ejemplo, esto realmente ejecutaría la consulta con el objetivo de retornar una lista, objeto de por medio de los primeros diez:

>>> Entry.objects.all().slice(0, 10, 2);

Para recuperar un solo objeto en vez de una lista (por ej. SELECT foo FROM bar LIMIT 1) se usa un simple índice en vez de un rebanado. Por ejemplo, ésto retorna el primer Entry en la base de datos, después de ordenar las entradas alfabéticamente por título:

>>> Entry.objects.order_by('headline').get(0);

Métodos de Consulta que Retornan Nuevos QuerySets

Se provee una variedad de métodos de refinamiento de QuerySet que modifican ya sea los tipos de resultados retornados por el QuerySet o la forma en cómo se ejecuta la consulta SQL. Estos métodos se describen en las secciones que siguen. Algunos de estos métodos reciben argumentos de patrones de búsqueda, los cuales se discuten en detalle más adelante.

filter({ lookups })

Retorna un nuevo QuerySet conteniendo objetos que son iguales a los parámetros de búsqueda provistos.

exclude({ kwargs })

Retorna un nuevo QuerySet conteniendo objetos que no son iguales a los parámetros de búsqueda provistos.

order_by(campos, ...)

Por omisión, los resultados retornados por un QuerySet están ordenados por la tupla de ordenamiento indicada por la opción ordering en los metadatos del modelo (ver apéndice de modelos). Se puede sobrescribir ésto para una consulta particular usando el método order_by():

>>> Entry.objects.filter({ pub_date__year: 2005 }).order_by(
    '-pub_date', 'headline');

Este resultado será ordenado por pub_date de forma descendente, luego por headline de forma ascendente. El signo negativo en frente de "-pub_date" indica orden descendente. Si el - está ausente se asume un orden ascendente. Para ordenar aleatoriamente, "?", así:

>>> Entry.objects.order_by('?');

distinct()

Retorna un nuevo QuerySet que usa SELECT DISTINCT en su consulta SQL. Elimina filas duplicadas en el resultado de la misma.

Por omisión, un QuerySet no eliminará filas duplicadas. En la práctica ésto raramente es un problema porque consultas simples como Blog.objects.all() no introducen la posibilidad de registros duplicados.

Sin embargo, si la consulta abarca múltiples tablas, es posible obtener resultados duplicados cuando un QuerySet es evaluado. Esos son los casos en los que se usaría distinct().

values(campos, ...)

Retorna un QuerySet especial que evalúa a una lista de diccionarios en lugar de objetos instancia de modelo. Cada uno de esos diccionarios representa un objeto, con las las claves en correspondencia con los nombres de los atributos de los objetos modelo:

// This list contains a Blog object.
>>> Blog.objects.filter({ name__startswith: 'Beatles' });
[Beatles Blog]

// This list contains a dictionary.
>>> Blog.objects.filter({ name__startswith: 'Beatles' }).values();
[{'id': 1, 'name': 'Beatles Blog',
    'tagline': 'All the latest Beatles news.'}]

values() puede recibir argumentos posicionales opcionales, campos, los cuales especifican los nombres de campos a los cuales debe limitarse el SELECT. Si se especifican los campos, cada diccionario contendrá solamente las claves/valores de campos para los campos que se especifique. Si no se especifican los campos, cada diccionario contendrá una clave y un valor para todos los campos en la table de base de datos:

>>> Blog.objects.values();
[{'id': 1, 'name': 'Beatles Blog',
    'tagline': 'All the latest Beatles news.'}],
>>> Blog.objects.values('id', 'name');
[{'id': 1, 'name': 'Beatles Blog'}]

Este método es útil cuando se sabe de antemano que sólo se van a necesitar valores de un pequeño número de los campos disponibles y no se necesita la funcionalidad de un objeto instancia de modelo. Es más eficiente el seleccionar solamente los campos que se necesitan usar.

dates(campo, tipo, orden)

Retorna un QuerySet especial que evalúa a una lista de objetos Date que representan todas las fechas disponibles de un cierto tipo en el contenido de la QuerySet.

El argumento campo debe ser el nombre de un DateField o de un DateTimeField del modelo. El argumento tipo debe ser ya sea year, month o day. Cada objeto Date en la lista de resultados es truncado de acuerdo al tipo provisto:

  • "year" retorna una lista de todos los valores de años distintos entre sí para el campo.
  • "month" retorna una lista de todos los valores de años/mes distintos entre sí para el campo.
  • "day" retorna una lista de todos los valores de años/mes/día distintos entre sí para el campo.

orden, cuyo valor por omisión es 'ASC', debe ser 'ASC' o 'DESC'. El mismo especifica cómo ordenar los resultados.

Aquí tenemos algunos ejemplos:

>>> Entry.objects.dates('pub_date', 'year');
[Date(2005, 1, 1)]

>>> Entry.objects.dates('pub_date', 'month');
[Date(2005, 2, 1), Date(2005, 3, 1)]

>>> Entry.objects.dates('pub_date', 'day');
[Date(2005, 2, 20), Date(2005, 3, 20)]

>>> Entry.objects.dates('pub_date', 'day', 'DESC');
[Date(2005, 3, 20), Date(2005, 2, 20)]

>>> Entry.objects.filter({
    headline__contains: 'Lennon' }).dates('pub_date', 'day');
[Date(2005, 3, 20)]

extra()

A veces, el lenguaje de consulta de Doff no puede expresar fácilmente cláusulas WHERE complejas. Para estos casos extremos, Doff provee un modificador de QuerySet llamado extra() – una forma de inyectar cláusulas específicas dentro del SQL generado por un QuerySet.

Por definición, estas consultas especiales pueden no ser portables entre los distintos motores de bases de datos y violan el principio DRY, así que se deberían evitar de ser posible.

Se pueden especificar uno o más de params, select, where, o tables. Ninguno de los argumentos es obligatorio, pero se debería indicar al menos uno.

El argumento select permite indicar campos adicionales en una cláusula de SELECT. Debe contener un diccionario que mapee nombres de atributo a cláusulas SQL que se utilizarán para calcular el atributo en cuestión:

>>> Entry.objects.extra({'is_recent': "pub_date > '2006-01-01'"});

Como resultado, cada objeto Entry tendrá en este caso un atributo adicional, is_recent, un booleano que representará si el atributo pub_date del entry es mayor que el 1 de Enero de 2006.

El siguiente ejemplo es más avanzado; realiza una subconsulta para darle a cada objeto Blog resultante un atributo entry_count, un entero que indica la cantidad de objetos Entry asociados al blog:

>>> subq = 'SELECT COUNT(*) FROM blog_entry WHERE' +
               'blog_entry.blog_id = blog_blog.id'
>>> Blog.objects.extra({'entry_count': subq});

(En este caso en particular, se está aprovechando el hecho de que la consulta ya contiene la tabla blog_blog en su cláusula FROM.)

También es posible definir cláusulas WHERE explícitas – quizás para realizar joins implícitos – usando el argumento where. Se pueden agregar tablas manualmente a la cláusula FROM del SQL usando el argumento tables.

Tanto where como tables reciben una lista de cadenas. Todos los argumentos de where son unidos con AND a cualquier otro criterio de búsqueda:

>>> Entry.objects.extra(null, ['id IN (3, 4, 5, 20)'])

Los parámetros select y where, antes descriptos, pueden utilizar los comodines normales para bases de datos: '%s' para indicar parámetros que deberían ser escapados automáticamente por el motor de la base de datos. El argumento params es una lista de los parámetros que serán utilizados para realizar la sustitución:

>>> Entry.objects.extra(null, ['headline=%s'], ['Lennon'])

Siempre se debe utilizar params en vez de utilizar valores directamente en select o where ya que params asegura que los valores serán escapados correctamente de acuerdo con el motor de base de datos particular.

Éste es un ejemplo de lo que está incorrecto:

Entry.objects.extra(null, ["headline='%s'" % name])

Éste es un ejemplo de lo que es correcto:

Entry.objects.extra(null, ['headline=%s'], [name])

Metodos de QuerySet que no Devuelven un QuerySet

Los métodos de QuerySet que se describen a continuación evalúan el QuerySet y devuelven algo que no es un QuerySet – un objeto, un valor, o algo así.

get()

Devuelve el objeto que concuerde con el parámetro de búsqueda provisto. El parámetro debe proveerse de la manera descripta en la sección “Patrones de búsqueda“. Este método levanta AssertionError si más de un objecto concuerda con el patrón provisto.

Si no se encuentra ningún objeto que coincida con el patrón de búsqueda provisto get() levanta una excepción de DoesNotExist. Esta excepción es un atributo de la clase del modelo, por ejemplo:

>>> Entry.objects.get({ id: 'foo' }) # levanta Entry.DoesNotExist

La excepción DoesNotExist hereda de doff.core.exceptions.ObjectDoesNotExist, así que se puede proteger de múltiples excepciones DoesNotExist:

>>> require('doff.core.exceptions', 'ObjectDoesNotExist')
>>> try {
...     e = Entry.objects.get({ id: 3 } )
...     b = Blog.objects.get({ id: 1 })
... } catch (e if isintance(e, ObjectDoesNotExist)) {
...     print("Either the entry or blog doesn't exist."); }

create({ kwargs })

Este método sirve para crear un objeto y guardarlo en un mismo paso. Permite abreviar dos pasos comunes:

>>> p = new Person({ first_name: "Bruce", last_name: "Springsteen"})
>>> p.save()

en una sola línea:

>>> p = Person.objects.create({ first_name: "Bruce",
    last_name: "Springsteen"})

get_or_create({ kwargs })

Este método sirve para buscar un objeto y crearlo si no existe. Devuelve una tupla [object, created], donde object es el objecto encontrado o creado, y created es un booleano que indica si el objeto fue creado.

Está pensado como un atajo para el caso de uso típico y es más que nada útil para scripts de importación de datos, por ejemplo:

try {
    obj = Person.objects.get({first_name:'John', last_name:'Lennon'})
} catch (e if isintance(e, Person.DoesNotExist)){
    obj = new Person({ first_name: 'John',
            last_name: 'Lennon',
            birthday: new Date(1940, 10, 9)});
    obj.save();
}

Este patrón se vuelve inmanejable a medida que aumenta el número de campos en el modelo. El ejemplo anterior puede ser escrito usando get_or_create:

[ obj, created ] = Person.objects.get_or_create( {
    first_name: 'John',
    last_name:  'Lennon',
    defaults:   {'birthday': new Date(1940, 10, 9)} }
)

Cualquier argumento que se le pase a get_or_create()excepto el argumento opcional defaults – será utilizado en una llamada a get(). Si se encuentra un objeto, get_or_create devolverá una tupla con ese objeto y false. Si no se encuentra un objeto, get_or_create() instanciará y guardará un objeto nuevo, devolviendo una tupla con el nuevo objeto y true. El nuevo objeto será creado de acuerdo con el siguiente algoritmo:

defaults = kwargs['defaults'] || {};
delete kwargs['defaults'];
params = new Dict([[k, v] for each ([k, v] in
    items(kwargs) if (!('__' in k)]);
params.update(defaults);
obj = this.model(object(params));
obj.save();

Esto es, se comienza con los argumentos que no sean 'defaults' y que no contengan doble guión bajo (lo cual indicaría una búsqueda no exacta). Luego se le agrega el contenido de defaults, sobreescribiendo cualquier valor que ya estuviera asignado, y se usa el resultado como clave para el constructor del modelo.

Si el modelo tiene un campo llamado defaults y es necesario usarlo para una búsqueda exacta en get_or_create(), simplemente hay que utilizar 'defaults__exact':

Foo.objects.get_or_create(
    defaults__exact: 'bar',
    defaults: {'defaults': 'baz'}}
)

count()

Devuelve un entero representando el número de objetos en la base de datos que coincidan con el QuerySet. count() nunca levanta excepciones. He aquí un ejemplo:

# Returns the total number of entries in the database.
>>> Entry.objects.count();
4

# Returns the number of entries whose headline contains 'Lennon'
>>> Entry.objects.filter({ headline__contains: 'Lennon' }).count();
1

count() en el fondo realiza un SELECT COUNT(*), así que se debería siempre utilizar count() en vez de cargar todos los registros en objetos y luego invocar len() sobre el resultado.

in_bulk(id_list)

Este método toma una lista de claves primarias y devuelve un diccionario que mapea cada clave primaria en una instancia con el ID dado, por ejemplo:

>>> Blog.objects.in_bulk([1])
{1: Beatles Blog}
>>> Blog.objects.in_bulk([1, 2])
{1: Beatles Blog, 2: Cheddar Talk}
>>> Blog.objects.in_bulk([])
{}

Si no se encuentra un objeto en la base para un ID en particular, este id no aparecerá en el diccionario resultante. Si se pasa una lista vacía a in_bulk(), se obtendrá un diccionario vacío.

latest(field_name)

Devuelve el último objeto de la tabla, ordenados por fecha, utilizando el campo que se provea en el argumento field_name como fecha. Este ejemplo devuelve el Entry más reciente en la tabla, de acuerdo con el campo pub_date:

>>> Entry.objects.latest('pub_date')

Si el Meta del modelo especifica get_latest_by, se puede omitir el argumento field_name. Doff utilizará el campo indicado en get_latest_by por defecto.

Al igual que get(), latest() levanta DoesNotExist si no existe un objeto con los parámetros provistos.

Patrones de Búsqueda

Los patrones de búsqueda son la manera en que se especifican los parámetros de una cláusula WHERE de SQL. Consisten de argumentos de palabra clave para los métodos filter(), exclude() y get() de QuerySet.

Los parámetros básicos de búsqueda toman la forma de campo__tipodebusqueda: valor (notar el doble guión bajo). Por ejemplo:

>>> Entry.objects.filter({ pub_date__lte: '2006-01-01'})

se traduce (aproximadamente) al siguiente comando SQL:

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

Si se suministra un argumento de palabra clave inválido, la función levantará una excepción de TypeError.

A continuación se listan los tipos de búsqueda que existen.

exact

Realiza una búsqueda por coincidencias exactas:

>>> Entry.objects.get({ headline__exact: "Man bites dog" })

Busca objetos que tengan en el campo headline la frase exacta “Man bites dog”.

Si no se suministra un tipo de búsqueda – O sea, si el argumento de palabra clave no contiene un doble guión bajo – el tipo de búsqueda se asume como exact.

Por ejemplo, las siguientes dos sentencias son equivalentes:

>>> Blog.objects.get({ id__exact: 14 }) # Explicit form
>>> Blog.objects.get({ id: 14 }) # __exact is implied

Esto es por conveniencia, dado que las búsquedas con tipo de búsqueda exact son las más frecuentes.

iexact

Realiza una búsqueda por coincidencias exactas sin distinguir mayúsculas de minúsculas:

>>> Blog.objects.get({ name__iexact: 'beatles blog' })

Traerá objetos con nombre 'Beatles Blog', 'beatles blog', 'BeAtLes BLoG', etc.

contains

Realiza una búsqueda de subcadenas, distinguiendo mayúsculas y minúsculas:

Entry.objects.get({ headline__contains: 'Lennon' })

Coincidirá con el titular 'Today Lennon honored' pero no con 'today lennon honored'.

Escapado de porciento y guión bajo en sentencias LIKE

Los patrones de búsqueda que resulten en sentencias SQL LIKE (iexact, contains, icontains, startswith, istartswith, endswith, y iendswith) escaparán automáticamente los dos caracteres especiales utilizados en sentencias LIKE – el porciento y el guión bajo. (En una sentencia LIKE, el símbolo de porciento indica una secuencia de caracteres cualesquiera, y el guión bajo indica un solo caracter cualquiera).

Esto significa que las cosas deberían funcionar de manera intuitiva, porque la abstracción funciona bien. Por ejemplo, para obtener todos los Entries que contengan un símbolo de porciento, simplemente hace falta utilizar el símbolo de porcentaje como cualquier otro caracter:

Entry.objects.filter({ headline__contains: '%' })

Doff se hace cargo del escapado. El SQL resultante será algo similar a esto:

SELECT ... WHERE headline LIKE '%\%%';

Lo mismo vale para el guión bajo. Tanto el símbolo de porcentaje como el guión bajo se deberían manejar de manera transparente.

icontains

Realiza una búsqueda de subcadenas, sin distinguir mayúsculas y minúsculas:

>>> Entry.objects.get({ headline__icontains: 'Lennon' })

A diferencia de contains, icontains trerá today lennon honored.

gt, gte, lt, and lte

Representan los operadores de mayor a, mayor o igual a, menor a, y menor o igual a, respectivamente:

>>> Entry.objects.filter({ id__gt: 4 })
>>> Entry.objects.filter({ id__lt: 15 })
>>> Entry.objects.filter({ id__gte: 0 })

Estas consultas devuelven cualquier objeto con un ID mayor a 4, un ID menor a 15, y un ID mayor o igual a 1, respectivamente.

Por lo general estos operadores se utilizarán con campos numéricos. Se debe tener cuidado con los campos de caracteres, ya que el orden no siempre es el que se esperaría (i.e., la cadena “4” resulta ser mayor que la cadena “10”).

in

Aplica un filtro para encontrar valores en una lista dada:

Entry.objects.filter({ id__in: [1, 3, 4]})

Devolverá todos los objetos que tengan un ID de 1, 3 o 4.

startswith

Busca coincidencias de prefijos distinguiendo mayúsculas y minúsculas:

>>> Entry.objects.filter({ headline__startswith: 'Will' })

Encontrará los titulares “Will he run?” y “Willbur named judge”, pero no “Who is Will?” o “will found in crypt”.

istartswith

Realiza una búsqueda por prefijos, sin distinguir mayúsculas y minúsculas:

>>> Entry.objects.filter({ headline__istartswith: 'will' })

Devolverá los titulares “Will he run?”, “Willbur named judge”, y “will found in crypt”, pero no “Who is Will?”

endswith and iendswith

Realiza búsqueda de sufijos, distinguiendo y sin distinguir mayúsculas de minúsculas, respectivamente:

>>> Entry.objects.filter({ headline__endswith: 'cats' })
>>> Entry.objects.filter({ headline__iendswith: 'cats' })

range

Realiza una búsqueda por rango:

>>> start_date = new Date(2005, 1, 1)
>>> end_date = new Date(2005, 3, 31)
>>> Entry.objects.filter({pub_date__range: [ start_date, end_date ]})

Se puede utilizar range en cualquier lugar donde se puede utilizar BETWEEN en SQL – para fechas, números, e incluso cadenas de caracteres.

year, month, and day

Para campos date y datetime, realiza búsqueda exacta por año, mes o día:

# Búsqueda por año
>>>Entry.objects.filter({ 'pub_date__year': 2005 })

# Búsqueda por mes -- toma enteros
>>> Entry.objects.filter({ 'pub_date__month': 12 })

# Búsqueda por día
>>> Entry.objects.filter({ 'pub_date__day': 3 })

# Combinación: devuelve todas las entradas de Navidad de cualquier año
>>> Entry.objects.filter({ 'pub_date__month': 12, 'pub_date_day': 25 })

isnull

Toma valores true o false, que corresponderán a consultas SQL de IS NULL``y ``IS NOT NULL, respectivamente:

>>> Entry.objects.filter({ pub_date__isnull: true})

El Patrón de Búsqueda pk

Por conveniencia, Doff provee un patrón de búsqueda pk, que realiza una búsqueda sobre la clave primaria del modelo (pk por primary key, del inglés).

En el modelo de ejemplo Blog, la clave primaria es el campo id, así que estas sentencias serían equivalentes:

>>> Blog.objects.get({ 'id__exact': 14 }) # Forma explícita
>>> Blog.objects.get({ 'id': 14 }) # __exact implícito
>>> Blog.objects.get({ 'pk': 14 }) # pk implica id__exact

El uso de pk no se limita a búsquedas __exact – cualquier patrón de búsqueda puede ser combinado con pk para realizar una búsqueda sobre la clave primaria de un modelo:

# Buscar entradas en blogs con id 1, 4, o 7
>>> Blog.objects.filter({ 'pk__in': [1,4,7] })

# Buscar entradas en blogs con id > 14
>>> Blog.objects.filter({ 'pk__gt': 14 })

Las búsquedas pk también funcionan con joins. Por ejemplo, estas tres sentencias son equivalentes:

>>> Entry.objects.filter({ 'blog__id__exact': 3 }) # Forma explícita
>>> Entry.objects.filter({ 'blog__id': 3 }) # __exact implícito
>>> Entry.objects.filter({ 'blog__pk': 3 }) # __pk implica __id__exact

Búsquedas Complejas con Objetos Q

Los argumentos de palabras clave en las búsquedas – en filter() por ejemplo – son unidos con AND. Si se necesita realizar búsquedas más complejas (búsquedas con sentencias OR), se puede utilizar objetos Q.

Un objeto Q (doff.db.models.Q) es un objeto que se utiliza para encapsular una colección de argumentos de palabra clave. Estos argumentos de palabra clave son especificados como se indica en la sección Patrones de Búsqueda.

Por ejemplo, este objeto Q encapsula una consulta con un único LIKE:

new Q({ question__startswith: ‘What’ })

Los objetos Q pueden ser combinados utilizando los operadores and y or. Cuando se utiliza un operador sobre dos objetos, se obtiene un nuevo objeto Q. Por ejemplo, un OR de dos consultas question__startswith sería:

new Q({ question__startswith: 'Who' }).or( new Q({
    question__startswith: 'What' }) )

Será equivalente a la siguiente cláusula WHERE en SQL:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

Se puede componer sentencias de complejidad arbitraria combinando objetos Q con los operadores and y or. También se pueden utilizar paréntesis para agrupar.

Cualquier función de búsqueda que tome argumentos de palabra clave (filter(), exclude(), get()) puede recibir también uno o más objetos Q como argumento posicional (no nombrado). Si se proveen múltiples objetos Q como argumentos a una función de búsqueda, los argumentos serán unidos con AND, por ejemplo:

Poll.objects.get(
    new Q({ question__startswith: 'Who' }),
    new Q({ pub_date: new Date(2005, 5, 2) }).or(new Q({
            pub_date: new Date(2005, 5, 6) }))
)

se traduce aproximadamente al siguiente SQL:

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

Las funciones de búsqueda pueden además mezclar el uso de objetos Q y de argumentos de palabra clave. Todos los argumentos provistos a una función de búsqueda (sean argumentos de palabra clave u objetos Q) son unidos con AND. Sin embargo, si se provee un objeto Q debe preceder la definición de todos los argumentos de palabra clave.

Objetos Relacionados

Cuando se define una relación en un modelo (un ForeignKey, OneToOneField, or ManyToManyField), las instancias de ese modelo tendrán una API conveniente para acceder a estos objetos relacionados.

Por ejemplo, si e es un objeto Entry, puede acceder a su Blog asociado accediendo al atributo blog, esto es e.blog.

Doff también crea una API para acceder al “otro” lado de la relación – el vínculo del modelo relacionado al modelo que define la relación. Por ejemplo, si b es un objeto Blog, tiene acceso a la lista de todos los objetos Entry a través del atributo entry_set: b.entry_set.all().

Todos los ejemplos en esta sección utilizan los modelos de ejemplo Blog, Author y Entry que se definen al principio de esta sección.

Consultas que Cruzan Relaciones

Doff ofrece un mecanismo poderoso e intuitivo para “seguir” relaciones cuando se realizan búsquedas, haciéndose cargo de los JOINs de SQL de manera automática. Para cruzar una relación simplemente hace falta utilizar el nombre de campo de los campos relacionados entre modelos, separados por dos guiones bajos, hasta que se llegue al campo que se necesite.

Este ejemplo busca todos los objetos Entry que tengan un Blog cuyo nombre sea 'Beatles Blog':

>>> Entry.objects.filter({ blog__name__exact: 'Beatles Blog' })

Este camino puede ser tan largo como quieras.

También funciona en la otra dirección. Para referirse a una relación “inversa”, simplemente hay que utilizar el nombre en minúsculas del modelo.

Este ejemplo busca todos los objetos Blog que tengan al menos un Entry cuyo headline contenga 'Lennon':

>>> Blog.objects.filter({ entry__headline__contains: 'Lennon' })

Relaciones de Clave Foránea

Si un modelo contiene un ForeignKey, las instancias de ese modelo tendrán acceso al objeto relacionado (foráneo) vía un simple atributo del modelo, por ejemplo:

e = Entry.objects.get({ id: 2 })
e.blog # Devuelve el objeto Blog relacionado

Se puede acceder y asignar el valor de la clave foránea vía el atributo. Como es de esperar, los cambios a la clave foránea no se guardan en el modelo hasta que se invoque el método save(), por ejemplo:

e = Entry.objects.get({ id: 2 })
e.blog = some_blog
e.save()

Si un campo ForeignKey tiene la opción null = true seteada (permite valores NULL), se le puede asignar null:

e = Entry.objects.get({ id: 2 })
e.blog = null
e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"

El acceso a relaciones uno-a-muchos se almacena la primera vez que se accede al objeto relacionado. Cualquier acceso subsiguiente a la clave foránea del mismo objeto es cacheada, por ejemplo:

e = Entry.objects.get({ id: 2 })
print(e.blog)  # Busca el Blog asociado en la base de datos.
print(e.blog)  # No va a la base de datos; usa la versión cacheada.

Notar que el método de QuerySet select_related() busca inmediatamente todos los objetos de relaciones uno-a-muchos de la instancia:

e = Entry.objects.select_related().get({ id: 2 })
print(e.blog)  # No va a la base de datos; usa la versión cacheada.
print(e.blog)  # No va a la base de datos; usa la versión cacheada.

Relaciones de Clave Foránea “Inversas”

Las relaciones de clave foránea son automáticamente simétricas – se infiere una relación inversa de la presencia de un campo ForeignKey que apunte a otro modelo.

Si un modelo tiene una ForeignKey, las instancias del modelo de la clave foránea tendrán acceso a un Manager que devuelve todas las instancias del primer modelo. Por defecto, este Manager se llama FOO_set, donde FOO es el nombre modelo que contiene la clave foránea, todo en minúsculas. Este Manager devuelve QuerySets, que pueden ser filtrados y manipulados como se describe en la sección Recuperando Objetos.

Aquí se muestra un ejemplo:

b = Blog.objects.get({ id: 1 })
b.entry_set.all() # Encontrar todos los objetos Entry relacionados a b.

# b.entry_set es un Manager que devuelve QuerySets.
b.entry_set.filter({ headline__contains: 'Lennon' })
b.entry_set.count()

Se puede cambiar el nombre del atributo FOO_set indicando el parámetro related_name en la definición del ForeignKey(). Por ejemplo, si el modelo Entry fuera cambiado por blog = ForeignKey(Blog, { related_name: 'entries' }), el ejemplo anterior pasaría a ser:

b = Blog.objects.get({ id: 1 })
b.entries.all() # Encontrar todos los objetos Entry relacionados a b.

# b.entries es un Manager que devuelve QuerySets.
b.entries.filter({ headline__contains: 'Lennon' })
b.entries.count()

No se puede acceder al Manager de ForeignKey inverso desde la clase misma; debe ser accedido desde una instancia:

Blog.entry_set
# Raises AttributeError: "Manager must be accessed via instance".

Además de los metodos de QuerySet definidos en la sección Recuperando Objetos, el Manager de ForeignKey tiene los siguientes métodos adicionales:

  • add(obj1, obj2, ...): Agrega los objetos del modelo indicado al conjunto de objetos relacionados, por ejemplo:

    b = Blog.objects.get({ id: 1 })
    e = Entry.objects.get({ id: 234 })
    b.entry_set.add(e) # Associates Entry e with Blog b.
    
  • create({ kwargs }): Crea un nuevo objeto, lo guarda, y lo deja en el conjunto de objetos relacionados. Devuelve el objeto recién creado:

    b = Blog.objects.get({ id: 1 })
    e = b.entry_set.create({ headline: 'Hello',
                  body_text: 'Hi', pub_date: new Date(2005, 1, 1) })
    # No hace falta llamar a e.save() acá -- ya ha sido guardado

    Esto es equivalente a (pero más simple que):

    b = Blog.objects.get({ id: 1 })
    e = Entry({ blog: b, headline: 'Hello',
                  body_text: 'Hi', pub_date: new Date(2005, 1, 1) })
    e.save()

    Notar que no es necesario especificar el argumento de palabra clave correspondiente al modelo que define la relación. En el ejemplo anterior, no se pasa el parámetro blog a create(). Doff deduce que el campo blog del nuevo Entry debe ser b.

  • remove(obj1, obj2, ...): Quita los objetos indicados del conjunto de objetos relacionados:

    b = Blog.objects.get({ id: 1 })
    e = Entry.objects.get({ id: 234 })
    b.entry_set.remove(e) # Desasociar al Entry e del Blog b.
    

    Para evitar inconsistencias en la base de datos, este método sólo existe para objetos ForeignKey donde null = true. Si el campo relacionado no puede pasar ser null, entonces un objeto no puede ser quitado de una relación sin ser agregado a otra. En el ejemplo anterior, el quitar a e de b.entry_set() es equivalente a hacer e.blog = null, y dado que la definición del campo ForeignKey blog (en el modelo Entry) no indica null = true, es una acción inválida.

  • clear(): Quita todos los objetos del conjunto de objetos relacionados:

    b = Blog.objects.get({ id: 1 })
    b.entry_set.clear()
    

    Notar que ésto no borra los objetos relacionados – simplemente los desasocia.

    Al igual que remove(), clear sólo está disponible para campos ForeignKey donde null = true.

Para asignar todos los miembros de un conjunto relacionado en un solo paso, simplemente se le asigna al conjunto un objeto iterable, por ejemplo:

b = Blog.objects.get({ id: 1 })
b.entry_set = [e1, e2]

Si el método clear() está definido, todos los objetos preexistentes serán quitados del entry_set antes de que todos los objetos en el iterable (en este caso, la lista) sean agregados al conjunto. Si el método clear() no está disponible, todos los objetos del iterable son agregados al conjunto sin quitar antes los objetos preexistentes.

Todas las operaciones “inversas” definidas en esta sección tienen efectos inmediatos en la base de datos. Toda creación, borrado y agregado son inmediatas y automáticamente grabadas en la base de datos.

Relaciones Muchos-a-muchos

Ambos extremos de las relaciones muchos-a-muchos obtienen una API de acceso automáticamente. La API funciona igual que las funciones “inversas” de las relaciones uno-a-muchos (descriptas en la sección anterior).

La única diferencia es el nombrado de los atributos: el modelo que define el campo ManyToManyField usa el nombre del atributo del campo mismo, mientras que el modelo “inverso” utiliza el nombre del modelo original, en minúsculas, con el sufijo '_set' (tal como lo hacen las relaciones uno-a-muchos).

Un ejemplo de esto lo hará más fácil de entender:

e = Entry.objects.get({ id: 3 })
e.authors.all() # Devuelve todos los objetos Author para este Entry.
e.authors.count()
e.authors.filter({ name__contains: 'John' })

a = Author.objects.get({ id: 5 })
a.entry_set.all() # Devuelve todos los obejtos Entry para este Author.

Al igual que los campos ForeignKey, los ManyToManyField pueden indicar un related_name. En el ejemplo anterior, si el campo ManyToManyField en el modelo Entry indicara related_name = 'entries', cualquier instancia de Author tendría un atributo entries en vez de entry_set.

Consultas que Abarcan Objetos Relacionados

Las consultas que involucran objetos relacionados siguen las mismas reglas que las consultas que involucran campos normales. Cuando se indica el valor que se requiere en una búsqueda, se puede utilizar tanto una instancia del modelo o bien el valor de la clave primaria del objeto.

Por ejemplo, si b es un objeto Blog con id=5, las tres siguientes consultas son idénticas:

Entry.objects.filter({ blog: b }) # Query using object instance
Entry.objects.filter({ blog: b.id }) # Query using id from instance
Entry.objects.filter({ blog: 5 }) # Query using id directly

Borrando Objetos

El métodos para borrar es delete(). Este método inmediatamente borra el objeto y no tiene ningún valor de retorno:

e.delete()

También se pueden borrar objetos en grupo. Todo objeto QuerySet tiene un método delete() que borra todos los miembros de ese QuerySet. Por ejemplo, para borrar todos los objetos Entry que tengan un año de pub_date igual a 2005:

Entry.objects.filter({ pub_date__year: 2005 }).delete()

Cuando Doff borra un objeto, emula el comportamiento de la restricción de SQL ON DELETE CASCADE – en otras palabras, todos los objetos que tengan una clave foránea que apunte al objeto que está siendo borrado serán borrados también, por ejemplo:

b = Blog.objects.get({ pk: 1 })
# Esto borra el Blog y todos sus objetos Entry.
b.delete()

Notar que delete() es el único método de QuerySet que no está expuesto en el Manager mismo. Esto es un mecanismo de seguridad para evitar que accidentalmente se solicite Entry.objects.delete() y se borren todos los Entry. Si realmente se quiere borrar todos los objetos, hay que pedirlo explícitamente al conjunto completo de objetos:

Entry.objects.all().delete()

Métodos de Instancia Adicionales

Además de save() y delete(), un objeto modelo puede tener cualquiera o todos los siguientes métodos:

get_FOO_display()

Por cada campo que indica la opción choices, el objeto tendrá un método get_FOO_display(), donde FOO es el nombre del campo. Este método devuelve el valor “humanamente legible” del campo. Por ejemplo, en el siguiente modelo:

GENDER_CHOICES = [
    ['M', 'Male'],
    ['F', 'Female'],
]

var Person = type('Person', [ models.Model ], {
    name: new models.CharField({ max_length: 20 })
    gender: new models.CharField({ max_length: 1,
            choices: GENDER_CHOICES })
    });

cada instancia de Person tendrá un método get_gender_display:

>>> p = Person({ name: 'John', gender: 'M' })
>>> p.save()
>>> p.gender
'M'
>>> p.get_gender_display()
'Male'

get_next_by_FOO({ kwargs }) y get_previous_by_FOO({ kwargs })

Por cada campo DateField y DateTimeField que no tenga null = true, el objeto tendrá dos métodos get_next_by_FOO() y get_previous_by_FOO(), donde FOO es el nombre del campo. Estos métodos devuelven el objeto siguiente y anterior en orden cronológico respecto del campo en cuestión, respectivamente, levantando la excepción DoesNotExist cuando no exista tal objeto.

Ambos métodos aceptan argumentos de palabra clave opcionales, que estar en la forma descripta en la sección Patrones de Búsqueda.

Notar que en el caso de valores de fecha idénticos, estos métodos utilizarán el ID como un chequeo secundario. Esto garantiza que no se saltearán registros ni aparecerán duplicados.

Atajos (Shortcuts)

A medida que se desarrollen las vistas, se descubrirán una serie de modismos en la manera de utilizar la API de la base de datos. Doff codifica algunos de estos modismos como atajos que pueden ser utilizados para simplificar el proceso de escribir vistas. Estas funciones se pueden hallar en el módulo doff.utils.shortcuts.

get_object_or_404()

Un modismo frecuente es llamar a get() y levantar un Http404 si el objeto no existe. Este modismo es capturado en la función get_object_or_404(). Esta función toma un modelo Doff como su primer argumento, y una cantidad arbitraria de argumentos de palabra clave, que le pasa al método get() del Manager por defecto del modelo. Luego levanta un Http404 si el objeto no existe, por ejemplo:

# Get the Entry with a primary key of 3
e = get_object_or_404(Entry, { pk: 3 })

Cuando se le pasa un modelo a esta función, se utiliza el Manager por defecto para ejecutar la consulta get() subyacente. Si no se quiere que se utilice el manager por defecto, o si se quiere buscar en una lista de objetos relacionados, se le puede pasar a get_object_or_404() un objeto Manager en vez:

# Get the author of blog instance e with a name of 'Fred'
a = get_object_or_404(e.authors, { name: 'Fred' })

# Use a custom manager 'recent_entries' in the search for an
# entry with a primary key of 3
e = get_object_or_404(Entry.recent_entries, { pk: 3 })

get_list_or_404()

get_list_or_404() se comporta igual que get_object_or_404(), salvo porque llama a filter() en vez de a get(). Levanta un Http404 si la lista resulta vacía.

Utilizando SQL Crudo

Si es necesario escribir una consulta SQL que es demasiado compleja para manejarla con el mapeador de base de datos de Doff, se puede optar por escribir la sentencia directamente en SQL crudo.

La forma preferida para hacer ésto es dándole al modelo métodos personalizados o métodos de Manager personalizados que realicen las consultas. Aunque no exista ningún requisito en Doff que exija que las consultas a la base de datos vivan en la capa del modelo, esta implementación pone a toda la lógica de acceso a los datos en un mismo lugar, lo cual es una buena idea desde el punto de vista de organización del código.

Finalmente, es importante notar que la capa de base de datos de Doff es meramente una interfaz a la base de datos. Se puede acceder a la base de datos utilizando otras herramientas, lenguajes de programación o frameworks de bases de datos – No hay nada específicamente de Doff acerca de la base de datos.