Doff: Referencia de la Definición de Modelos

El capítulo sobre Doff explica lo básico de la definición de modelos. Este apéndice explica todas las opciones disponibles en la definición de modelos.

Campos

La parte más importante de un modelo – y la única requerida – es la lista de campos de la base de datos que define.

Restricciones en el nombre de los campos

Existen sólo dos restricciones en el nombre de los campos:

  1. Un nombre de campo no puede ser una palabra reservada, porque eso ocasionaría un error de sintaxis, por ejemplo:
var Ejemplo = type('Ejemplo', [ models.Model ], {
    // 'var' es una palabra reservada!
    var = new models.IntegerField()
});
  1. Un nombre de campo no puede contener dos o más guiones bajos consecutivos, debido a la forma en que trabaja la sintaxis de las consultas de búsqueda, por ejemplo:
var Ejemplo = type('Ejemplo', [models.Model], {
    // 'foo__bar' tiene dos guiones bajos!
    foo__bar = new models.IntegerField()
});

Estas limitaciones se pueden manejar sin mayores problemas, dado que el nombre del campo no necesariamente tiene que coincidir con el nombre de la columna en la base de datos (ver db_column).

Las palabras reservadas de SQL, como join, where, o select, son permitidas como nombres de campo, dado que se “escapean” todos los nombres de tabla y columna de la base de datos en cada consulta SQL subyacente.

Cada campo en el modelo debe ser una instancia del tipo de campo apropiado. Los tipos de Field son utilizados para determinar:

  • El tipo de columna de la base de datos (ej.:, INTEGER, VARCHAR).
  • El widget a usar en la generación de formularios (ej., <input type="text">, <select>).
  • Los requerimientos mínimos de validación.

A continuación se presenta una lista completa de los campos, ordenados alfabéticamente. Los campos de relación (ForeignKey, etc.) se tratan en las siguientes secciones.

AutoField

Un IntegerField que se incrementa automáticamente de acuerdo con los IDs disponibles. Normalmente no es necesario utilizarlos directamente ya que se agrega un campo de clave primaria automáticamente al modelo si no se especifica una clave primaria.

BooleanField

Un campo Verdadero/Falso.

CharField

Un campo string, para cadenas cortas o largas. Para grandes cantidades de texto, usar TextField.

CharField requiere un argumento extra, max_length, que es la longitud máxima (en caracteres) del campo. Esta longitud máxima es reforzada a nivel de la base de datos y en la validación.

DateField

Un campo de fecha. DateField tiene algunos argumentos opcionales extra, como se muestra a continuación:

Argumentos opcionales extra de DateField
Argumento Descripción
auto_now Asigna automáticamente al campo un valor igual al momento en que se salva el objeto. Es útil para las marcas de tiempo “última modificación”. Observar que siempre se usa la fecha actual; no es un valor por omisión que se pueda sobreescribir.
auto_now_add Asigna automáticamente al campo un valor igual al momento en que se crea el objeto. Es útil para la creación de marcas de tiempo. Observar que siempre se usa la fecha actual; no es un valor por omisión que se pueda sobreescribir.

DateTimeField

Un campo de fecha y hora. Tiene las mismas opciones extra que DateField.

EmailField

Un CharField que chequea que el valor sea una dirección de e-mail válida. No acepta max_length; su max_length se establece automáticamente en 75.

DecimalField

Un número de punto flotante, representado en JavaScript por una instancia de Number. Tiene dos argumentos requeridos, que se muestran en la tabla.

Opciones extra de DecimalField
Argumento Descripción
max_digits La cantidad máxima de dígitos permitidos en el número.
decimal_places La cantidad de posiciones decimales a almacenar con el número.

Por ejemplo, para almacenar números hasta 999 con una resolución de dos decimales, hay que usar:

models.DecimalField({..., max_digits: 5, decimal_places: 2})

Y para almacenar números hasta aproximadamente mil millones con una resolución de diez dígitos decimales, hay que usar:

models.DecimalField({..., max_digits: 19, decimal_places: 10})

IntegerField

Un entero.

IPAddressField

Una dirección IP, en formato string (ej.: "24.124.1.30").

NullBooleanField

Similar a BooleanField, pero permite NULL como opción. Usar éste en lugar de un BooleanField con null = true.

PositiveIntegerField

Similar a IntegerField, pero debe ser positivo.

PositiveSmallIntegerField

Similar a PositiveIntegerField, pero sólo permite valores por debajo de un límite. El valor máximo permitido para estos campos depende de la base de datos, pero como las bases de datos tienen un tipo entero corto de 2 bytes, el valor máximo positivo usualmente es 65,535.

SlugField

“Slug” es un término de la prensa. Un slug es una etiqueta corta para algo, que contiene sólo letras, números, guiones bajos o simples. Generalmente se usan en URLs.

De igual forma que en CharField, se puede especificar max_length. Si max_length no está especificado, el valor por omisión es de 50.

Un SlugField implica db_index = true debido a que son los se usan principalmente para búsquedas en la base de datos.

SmallIntegerField

Similar a IntegerField, pero sólo permite valores en un cierto rango dependiente de la base de datos (usualmente -32,768 a +32,767).

TextField

Un campo de texto de longitud ilimitada.

TimeField

Un campo de hora. Acepta las mismas opciones de autocompletado que DateField y DateTimeField.

URLField

Un campo para una URL. Si la opción verify_exists es true (valor por omisión), se chequea la existencia de la URL dada (la URL carga y no da una respuesta 404).

Como los otros campos de caracteres, URLField toma el argumento max_length. Si no se especifica, el valor por omisión es 200.

Opciones para Todos los Campos

Los siguientes argumentos están disponibles para todos los tipos de campo. Todos son opcionales.

null

Si está en true, se almacenarán valores vacíos como NULL en la base de datos. El valor por omisión es false.

Los valores de string nulo siempre se almacenan como strings vacíos, no como NULL. null = true se debe utilizar sólo para campos no string, como enteros, booleanos y fechas. En los dos casos, también es necesario establecer blank = true si se desea permitir valores vacíos en los formularios, ya que el parámetro null sólo afecta al almacenamiento en la base de datos (ver la sección blank).

Se debe evitar utilizar null en campos basados en string como CharField y TextField salvo que se tenga una excelente razón para hacerlo. Si un campo basado en string tiene null = true, eso significa que tiene dos valores posibles para “sin datos”: NULL y el string vacío. En la mayoría de los casos, esto es redundante; la convención es usar el string vacío, no NULL.

blank

Si está en true, está permitido que el campo esté en blanco. El valor por omisión es false.

Es diferente de null. null sólo se relaciona con la base de datos, mientras que blank está relacionado con la validación. Si un campo tiene blank = true, la validación permitirá la entrada de un valor vacío. Si un campo tiene blank = false, es un campo requerido.

choices

Un arreglo conteniendo tuplas para usar como opciones para este campo.

Si ésto está dado, el sistema de formularios utilizará un cuadro de selección en lugar del campo de texto estándar, y limitará las opciones a las dadas.

Una lista de opciones se ve como:

var YEAR_IN_SCHOOL_CHOICES = [
    ['FR', 'Freshman'],
    ['SO', 'Sophomore'],
    ['JR', 'Junior'],
    ['SR', 'Senior'],
    ['GR', 'Graduate']
]

El primer elemento de cada tupla es el valor actual a ser almacenado. El segundo elemento es el nombre legible por humanos para la opción.

La lista de opciones puede ser definida también como parte del modelo:

var Foo = type('Foo', [ models.Model ], {
    GENDER_CHOICES: [
        ['M', 'Male'],
        ['F', 'Female']
    ],
    gender: new models.CharField({ max_length:1,
                        choices:GENDER_CHOICES}),
    });

o fuera del modelo:

var GENDER_CHOICES: [
        ['M', 'Male'],
        ['F', 'Female']
    ];
var Foo = type ('Foo', [models.Model], {
    gender: new models.CharField({ max_length:1,
                        choices:GENDER_CHOICES}),
    });

Para cada campo del modelo que tenga establecidas choices, se agregará un método para recuperar el nombre legible por humanos para el valor actual del campo. Ver el apéndice sobre la API de base de datos para más detalles.

db_column

El nombre de la columna de la base de datos a usar para este campo. De no estar definido, se utilizará el nombre del campo. Esto es útil cuando se está definiendo un modelo sobre una base de datos existente.

Si el nombre de columna de la base de datos es una palabra reservada de SQL, o contiene caracteres que no están permitidos en un nombre de variable, no hay problema. Los nombres de columna y tabla son escapeados por comillas detrás de la escena.

db_index

Si está en true, se crea un índice en la base de datos para esta columna cuando se crea la tabla.

default

El valor por omisión del campo.

editable

Si es false, el campo no será editable en el procesamiento de formularios. El valor por omisión es true.

help_text

Texto de ayuda extra a ser mostrado bajo el campo en el formulario. Es útil como documentación aunque el objeto no termine siendo representado en un formulario.

primary_key

Si es true, este campo es la clave primaria del modelo.

Si no se especifica primary_key = true para ningún campo del modelo, se agregará automáticamente este campo:

id = new models.AutoField('ID', { primary_key: true });

Por lo tanto, no es necesario establecer primary_key = true en ningún campo, salvo que se quiera sobreescribir el comportamiento por omisión de la clave primaria.

primary_key=true implica blank = false, null = false, y unique = true. Sólo se permite una clave primaria en un objeto.

radio_admin

Por omisión, la generación de formularios usa una interfaz de cuadro de selección (<select>) para campos que son ForeignKey o tienen choices. Si radio_admin es true, un radio-button es utilizado en su lugar.

No se debe utilizar para un campo que no sea ForeignKey o no tenga choices.

unique

Si es true, el valor para este campo debe ser único en la tabla.

unique_for_date

Se debe asignar como valor el nombre de un DateField o DateTimeField para requerir que este campo sea único para el valor del campo tipo fecha, por ejemplo:

var Story = type('Story', [ models.Model ] {
    pub_date: new models.DateTimeField(),
    slug: new models.SlugField({unique_for_date: "pub_date"}),
    ...
});

En este código, no se permite la creación de dos historias con el mismo slug publicado en la misma fecha. Ésto difiere de usar la restricción unique_together en que sólo toma en cuenta la fecha del campo pub_date; la hora no importa.

unique_for_month

Similar a unique_for_date, pero requiere que el campo sea único con respecto al mes del campo dado.

unique_for_year

Similar a unique_for_date y unique_for_month, pero para el año.

verbose_name

Cada tipo de campo, excepto ForeignKey, ManyToManyField, y OneToOneField, toma un primer argumento posicional opcional – un nombre descriptivo –. Si el nombre descriptivo no está dado, se crea automáticamente usando el nombre de atributo del campo, convirtiendo guiones bajos en espacios.

En este ejemplo, el nombre descriptivo es "Person's first name":

first_name = new models.CharField("Person's first name",
        { max_length: 30 })

En este ejemplo, el nombre descriptivo es "first name":

first_name = new models.CharField({maxlength: 30})

ForeignKey, ManyToManyField, y OneToOneField requieren que el primer argumento sea una clase del modelo, en este caso hay que usar verbose_name como argumento con nombre:

poll = new models.ForeignKey(Poll,
                {verbose_name: "the related poll"}),
sites = new models.ManyToManyField(Site,
                {verbose_name: "list of sites"}),
place = new models.OneToOneField(Place,
                {verbose_name: "related place"}),
...

La convención es no capitalizar la primera letra del verbose_name ya que éstas son pasadas a mayúscula automáticamente cuando sea necesario.

Relaciones

Es claro que el poder de las bases de datos se basa en relacionar tablas entre sí. Los tres tipos de relaciones más comunes en las bases de datos están soportadas: muchos-a-uno, muchos-a-muchos, y uno-a-uno (utilizada indirectamente en la herencia).

Relaciones Muchos-a-uno

El campo ForeignKey define las relaciones muchos-a-uno. Se usa como cualquier otro tipo Field: incluyéndolo como un atributo en el modelo.

ForeignKey requiere un argumento posicional: el tipo al cual se relaciona el modelo.

Por ejemplo, si un modelo Car tiene un Manufacturer – es decir, un Manufacturer fabrica múltiples autos pero cada Car tiene sólo un Manufacturer – la definición es:

var Manufacturer = type('Manufacturer', [ models.Model ], {
    ...
});

var Car = type('Car', [ models.Model ],
    manufacturer: new models.ForeignKey(Manufacturer),
    ...
});

Para crear una relación recursiva – un objeto que tiene una relación muchos-a-uno con él mismo – new models.ForeignKey('this'):

var Employee = type('Employee', [ models.Model ], {
    manager: new models.ForeignKey('this'),
    ...
});

Si se necesita crear una relación con un modelo que aún no se ha definido, el nombre del modelo puede ser utilizado en lugar del objeto modelo:

var Car = type('Car', [ models.Model ], {
    manufacturer: new models.ForeignKey('Manufacturer'),
    ...
});

var Manufacturer = type('Manufacturer', [ models.Model ], {
    ...
});

Observar que sólo se pueden usar strings para hacer referencia a modelos dentro del mismo archivo models.js – no se pueden usar strings para hacer referencias a un modelo en una aplicación diferente, o hacer referencia a un modelo que ha sido requerido de cualquier otro lado.

Detrás de la escena, "_id" es agregado al nombre de campo para crear su nombre de columna en la base de datos. En el ejemplo anterior, la tabla de la base de datos correspondiente al modelo Car, tendrá una columna manufacturer_id (se puede cambiar explícitamente especificando db_column; ver más arriba). De todas formas, el código nunca debe utilizar el nombre de la columna de la base de datos, salvo que se escriba SQL. Siempre se utilizarán los nombres de campo del modelo.

Se sugiere, pero no es requerido, que el nombre de un campo ForeignKey (manufacturer en el ejemplo) sea el nombre del modelo en minúsculas. Igualmente se puede poner cualquier nombre. Por ejemplo:

var Car = type('Car', [ models.Model ], {
    company_that_makes_it: new models.ForeignKey(Manufacturer),
    // ...
});

Los campos ForeignKey reciben algunos argumentos extra para definir cómo debe trabajar la relación (ver la tabla). Todos son opcionales.

Opciones de ForeignKey
Argumento Descripción
related_name El nombre a utilizar para la relación desde el objeto relacionado de hacia este objeto.
to_field El campo en el objeto relacionado con el cual se establece la relación. Por omisión, Doff usa la clave primaria del objeto relacionado.

Relaciones Muchos-a-Muchos

Para definir una relación muchos-a-muchos se utiliza ManyToManyField. Al igual que ForeignKey, ManyToManyField requiere un argumento posicional: el tipo al cual se relaciona el modelo.

Por ejemplo, si una Pizza tiene múltiples objetos Topping – es decir, un Topping puede estar en múltiples pizzas y cada Pizza tiene múltiples ingredientes (toppings) – debe representarse así:

var Topping = type('Topping', [ models.Model ], {
    ...
});

var Pizza = type('Pizza', [ models.Model ], {
    toppings: new models.ManyToManyField(Topping),
    ...
});

Como sucede con ForeignKey, una relación de un objeto con sí mismo puede definirse usando el string 'this' en lugar del nombre del modelo, y se pueden hacer referencias a modelos que todavía no se definieron usando un string que contenga el nombre del modelo. De todas formas sólo se pueden usar strings para hacer referencia a modelos dentro del mismo archivo models.js – no se puede usar un string para hacer referencia a un modelo en una aplicación diferente, o hacer referencia a un modelo que ha sido importado de cualquier otro lado.

Se sugiere, pero no es requerido, que el nombre de un campo ManyToManyField (toppings, en el ejemplo) sea un término en plural que describa al conjunto de objetos relacionados con el modelo.

Detrás de la escena, se crea una tabla join intermedia para representar la relación muchos-a-muchos.

No importa cuál de los modelos tiene el ManyToManyField, pero es necesario que esté en uno de los modelos – no en los dos.

Los objetos ManyToManyField toman algunos argumentos extra para definir cómo debe trabajar la relación (ver la tabla). Todos son opcionales.

Opciones de ManyToManyField
Argumento Descripción
related_name El nombre a utilizar para la relación desde el objeto relacionado hacia este objeto.
symmetrical

Sólo utilizado en la definición de ManyToManyField sobre sí mismo. Con el siguiente modelo:

var Person = type('Person',
            [ models.Model ], {
    friends:
            new models.ManyToManyField("this")
});

cuando Doff lo procesa, identifica que tiene un ManyToManyField sobre sí mismo, y como resultado, no agrega un atributo person_set a la clase Person. En lugar de eso, se asume que el ManyToManyField es simétrico.

Si no se desea la simetría en las relaciones ManyToMany con this, se debe establecer symmetrical en false. Ésto forzará a Doff a agregar el descriptor para la relación inversa, permitiendo que las relaciones ManyToMany sean asimétricas.

db_table El nombre de la tabla a crear para almacenar los datos de la relación muchos-a-muchos. Si no se provee, Doff asumirá un nombre por omisión basado en los nombres de las dos tablas a ser vinculadas.

Opciones de los Metadatos del Modelo

Los metadatos específicos de un modelo viven en un Object Meta definido en el cuerpo del modelo:

var Book = type('Book', [ models.Model ], {
    title: new models.CharField({max_length:100}),

    Meta: {
        // model metadata options go here
        ...
    }
});

Los metadatos del modelo son “cualquier cosa que no sea un campo”, como opciones de ordenamiento, etc.

Las secciones que siguen presentan una lista de todas las posibles Meta opciones. Ninguna de estas opciones es requerida. Agregar Meta a un modelo es completamente opcional.

db_table

El nombre de la tabla de la base de datos a usar para el modelo.

Si no se define el nombre de la tabla de la base de datos es derivado automáticamente a partir del nombre del modelo y la aplicación que lo contiene. Un nombre de tabla de base de datos de un modelo se construye uniendo la etiqueta de la aplicación del modelo – el nombre que tiene la aplicación – con el nombre del modelo, con un guión bajo entre ellos.

Por ejemplo, para la aplicación books, un modelo definido como Book tendrá una tabla en la base de datos llamada book_books.

Para sobreescribir el nombre de la tabla de la base de datos, se debe usar el parámetro db_table dentro de Meta:

var Book = type('Book', [ models.Model ], {
    ...

    Meta: {
        db_table: 'things_to_read'
    }
});

Si el nombre de tabla de base de datos es una palabra reservada de SQL, o contiene caracteres que no están permitidos en los nombres de variable, no hay problema. Los nombres de tabla y de columna son escapeados con comillas al generar el SQL.

get_latest_by

El nombre de un DateField o DateTimeField del modelo. Especifica el campo a utilizar por omisión en el método latest() del Manager del modelo.

Por ejemplo:

var CustomerOrder = type('CustomerOrder' ,[ models.Model ], {
    order_date: new models.DateTimeField(),
    ...

    Meta: {
        get_latest_by: "order_date"
    }
});

Ver el apéndice sobre API de base de datos para más información sobre el método latest().

order_with_respect_to

Marca este objeto como “ordenable” con respecto al campo dado. Se utiliza casi siempre con objetos relacionados para permitir que puedan ser ordenados respecto a un objeto padre. Por ejemplo, si un Answer se relaciona a un objeto Question, y una pregunta tiene más de una respuesta, y el orden de las respuestas importa:

var Answer = type('Answer', [ models.Model ], {
    question: new models.ForeignKey(Question),
    ...

    Meta: {
        order_with_respect_to: 'question'
    }
});

ordering

El ordenamiento por omisión del objeto, utilizado cuando se obtienen listas de objetos:

var Book = type('Book', [ models.Model ], {
    title: new models.CharField({maxlength: 100}),

    Meta: {
        ordering: ['title']
    }
});

Es un arreglo de strings. Cada string es un nombre de campo con un prefijo opcional -, que indica orden descendente. Los campos sin un - precedente se ordenarán en forma ascendente. Se debe usar el string "?" para ordenar al azar.

Por ejemplo, para ordenar por un campo title en orden ascendente:

ordering: ['title']

Para ordenar por title en orden descendente:

ordering: ['-title']

Para ordenar por title en orden descendente, y luego por author en orden ascendente:

ordering: ['-title', 'author']

unique_together

Conjuntos de nombres de campo que tomados juntos deben ser únicos:

var Employee = type('Employee', [ models.Model ], {
    department: new models.ForeignKey(Department),
    extension: new models.CharField({max_length: 10}),
    ...

    Meta: {
        unique_together: [["department", "extension"]]
    }
});

Es un arreglo de arreglos de campos que deben ser únicos cuando se consideran juntos. Es usado en la validación de formularios y se refuerza a nivel de base de datos (esto es, se incluyen las sentencias UNIQUE apropiadas en la sentencia CREATE TABLE).

verbose_name

Un nombre legible por humanos para el objeto, en singular:

var CustomerOrder = type('CustomerOrder', [ models.Model ], {
    order_date: new models.DateTimeField(),
    ...

    Meta: {
        verbose_name: "order"
    }
});

Si no se define, se utilizará una versión adaptada del nombre del modelo, en la cual CamelCase se convierte en camel case.

verbose_name_plural

El nombre del objeto en plural:

var Sphynx = type('Sphynx', [ models.Model ], {
    ...

    Meta: {
        verbose_name_plural: "sphynges"
    }
});

Si no se define, se agregará una “s” al final del verbose_name.

Managers

Un Manager es la interfaz a través de la cual se proveen las operaciones de consulta de la base de datos a los modelos. Existe al menos un Manager para cada modelo en una aplicación.

La forma en que trabajan los tipos Manager está documentada en el apéndice de base de datos. Esta sección trata específicamente las opciones del modelo que personaliza el comportamiento del Manager.

Nombres de Manager

Por omisión, se agrega un Manager llamado objects a cada tipo de modelo. De todas formas, si se quiere usar objects como nombre de campo, o usar un nombre distinto de objects para el Manager, se puede renombrar en cada uno de los modelos. Para renombrar el Manager para un modelo dato, se define un atributo de clase de tipo models.Manager() en ese modelo, por ejemplo:

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

var Person = type('Person', [ models.Model ], {
    ...

    people: new models.Manager(),
});

Usando este modelo de ejemplo, Person.objects generará una excepción AttributeError (dado que Person no tiene un atributo objects), pero Person.people.all() devolverá una lista de todos los objetos Person.

Managers Personalizados

Se puede utilizar un Manager personalizado en un modelo en particular extendiendo el tipo base Manager e instanciando un Manager personalizado.

Hay dos razones por las que se puede querer personalizar un Manager: para agregar métodos extra al Manager, y/o para modificar el QuerySet inicial que devuelve el Manager.

Agregando Métodos Extra al Manager

Agregar métodos extra al Manager es la forma preferida de agregar funcionalidad a nivel de tabla a los modelos (para funcionalidad a nivel de registro – esto es, funciones que actúan sobre una instancia simple de un objeto modelo – se deben usar métodos del modelo, no métodos de Manager personalizados).

Un método Manager personalizado puede retornar cualquier cosa que se necesite. No tiene que retornar un QuerySet.

Por ejemplo, este Manager personalizado ofrece un método with_counts(), que retorna una lista de todos los objetos OpinionPoll, cada uno con un atributo extra num_responses que es el resultado de una consulta agregada:

require('doff.db.base', 'connection');

var PollManager = type('PollManager', [ models.Manager ], {

    with_counts: function() {
        var cursor = connection.cursor();
        cursor.execute("
            SELECT p.id, p.question, p.poll_date, COUNT(*)
            FROM polls_opinionpoll p, polls_response r
            WHERE p.id = r.poll_id
            GROUP BY 1, 2, 3
            ORDER BY 3 DESC");
        var result_list = [];
        for each (var row in cursor.fetchall()) {
            var p = new this.model({ id: row[0],
                question: row[1], poll_date: row[2]});
            p.num_responses = row[3];
            result_list.append(p);
        }
        return result_list;
    }
});

var OpinionPoll = type(OpinionPoll, [ models.Model ], {
    question: new models.CharField({ max_length: 200 }),
    poll_date: new models.DateField(),
    objects: new PollManager()
});

var Response = type('Response', [ models.Model ], {
    poll: new models.ForeignKey(Poll),
    person_name: new models.CharField({ max_length: 50 }),
    response: new models.TextField()
});

En este ejemplo, se puede usar OpinionPoll.objects.with_counts() para retornar la lista de objetos OpinionPoll con el atributo num_responses.

Otra cosa a observar en este ejemplo es que los métodos de un Manager pueden acceder a this.model para obtener el tipo del modelo a la cual están anexados.

Modificando los QuerySets Iniciales del Manager

Un QuerySet base de un Manager devuelve todos los objetos en el sistema. Por ejemplo, usando este modelo:

var Book = type('Book', [ models.Model ], {
    title: new models.CharField({ max_length: 100 }),
    author: new models.CharField({ max_length: 50 })
});

la sentencia Book.objects.all() retornará todos los libros de la base de datos.

Se puede sobreescribir el QuerySet base, sobreescribiendo el método Manager.get_query_set(). get_query_set() debe retornar un QuerySet con las propiedades requeridas.

Por ejemplo, el siguiente modelo tiene dos managers – uno que devuelve todos los objetos, y otro que retorna sólo los libros de Roald Dahl:

// First, define the Manager subclass.
var DahlBookManager = type('DahlBookManager', [ models.Manager ], {
    get_query_set: function() {
        return super(Manager, this).get_query_set().filter(
                { author: 'Roald Dahl' });
});

// Then hook it into the Book model explicitly.
var Book = type('Book', [ models.Model ], {
    title: new models.CharField({ max_length: 100 }),
    author: new models.CharField({ max_length: 50 }),

    objects: new models.Manager(), // The default manager.
    dahl_objects: new DahlBookManager() // The Dahl-specific manager
});

Con este modelo de ejemplo, Book.objects.all() retornará todos los libros de la base de datos, pero Book.dahl_objects.all() solo retornará aquellos escritos por Roald Dahl.

Por supuesto, como get_query_set() devuelve un objeto QuerySet, se puede usar filter(), exclude(), y todos los otros métodos de QuerySet sobre él. Por lo tanto, estas sentencias son todas legales:

Book.dahl_objects.all();
Book.dahl_objects.filter({ title: 'Matilda' });
Book.dahl_objects.count();

Este ejemplo también señala otra técnica interesante: usar varios managers en el mismo modelo. Se pueden agregar tantas instancias de Manager() como se requieran. Esta es una manera fácil de definir “filters” comunes para los modelos. Aquí hay un ejemplo:

var MaleManager = type('MaleManager', [ models.Manager ], {
    get_query_set: function() {
        return super(Manager, this).get_query_set().filter({ sex: 'M' });
    }
});

var FemaleManager = type('FemaleManager', [ models.Manager ], {
    get_query_set: function() {
        return super(Manager, this).get_query_set().filter({ sex: 'F' });
    }
});

var Person = type('Person', [ models.Model ], {
    first_name: new models.CharField({ max_length: 50 }),
    last_name: new models.CharField({ max_length: 50 }),
    sex: new models.CharField({ max_length: 1, choices:
        [['M', 'Male'], ['F', 'Female']] }),
    people: new models.Manager(),
    men: new MaleManager(),
    women: new FemaleManager(),
});

Este ejemplo permite consultar Person.men.all(), Person.women.all(), y Person.people.all(), con los resultados predecibles.

Si se usan objetos Manager personalizados, el primer Manager que se encuentre (en el orden en el que están definidos en el modelo) tiene un status especial. Se interpreta el primer Manager definido en una clase como el Manager por omisión, por lo que generalmente es una buena idea que el primer Manager esté relativamente sin filtrar. En el último ejemplo, el manager people está definido primero – por lo cual es el Manager por omisión.

Métodos de Modelo

La forma de agregar funcionalidad es definiendo métodos en un modelo, de este modo se personaliza a nivel de registro. Mientras que los métodos Manager están pensados para hacer cosas a nivel de tabla, los métodos de modelo deben actuar en una instancia particular del modelo.

Es una técnica valiosa para mantener la lógica del negocio en un sólo lugar: el modelo. Por ejemplo, este modelo tiene algunos métodos personalizados:

var Person = type('Person', [ models.Model ], {
    first_name: new models.CharField({ max_length: 50 }),
    last_name: new models.CharField({ max_length: 50 }),
    birth_date: new models.DateField(),
    address: new models.CharField({ max_length: 100 }),
    city: new models.CharField({ max_length: 50 }),

    baby_boomer_status: function() {
        /*Returns the person's baby-boomer status.*/
        if (Date(1945, 8, 1) <=
                this.birth_date <= Date(1964, 12, 31))
            return "Baby boomer";
        if (this.birth_date < Date(1945, 8, 1))
            return "Pre-boomer";
        return "Post-boomer";
    },

    get full_name() {
        /*Returns the person's full name.*/
        return '%s %s'.subs(this.first_name, this.last_name);
    }
});

El último método en este ejemplo es un getter – un atributo implementado por código personalizado. Los getter son un un truco ingenioso agregado en JavaScript 1.6; se puede leer más acerca de ellos en https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Object.

Existen también algunos métodos de modelo que tienen un significado “especial” para JavaScript o Protopy. Estos métodos se describen en las secciones que siguen.

__str__

__str__() es un “método mágico” de Protopy que define lo que debe ser devuelto si se llama a string() sobre el objeto. Se usa string(obj) en varios lugares, particularmente como el valor mostrado para hacer el render de un objeto y como el valor insertado en un plantilla cuando muestra un objeto. Por eso, siempre se debe retornar un string agradable y legible por humanos en el __str__ de un objeto. A pesar de que esto no es requerido, es altamente recomendado.

Aquí hay un ejemplo:

var Person = type('Person', [ models.Model ], {
    first_name: new models.CharField({ max_length: 50 }),
    last_name: new models.CharField({ max_length: 50 }),

    __str__: function() {
        return '%s %s'.subs(this.first_name, this.last_name);
    }
});

Ejecutando SQL Personalizado

Se pueden escribir sentencias SQL personalizadas en métodos personalizados de modelo y métodos a nivel de módulo. El objeto doff.db.base.connection representa la conexión actual a la base de datos. Para usarla, se invoca a connection.cursor() para obtener un objeto cursor. Después, se llama a cursor.execute(sql, [params]) para ejecutar la SQL, y cursor.fetchone() o cursor.fetchall() para devolver las filas resultantes:

my_custom_sql: function() {
    require('doff.db.base', 'connection');
    var cursor = connection.cursor();
    cursor.execute("SELECT foo FROM bar WHERE baz = %s", [this.baz]);
    row = cursor.fetchone();
    return row;
}

connection y cursor implementan en su mayor parte la DB-API estándar.

Sobreescribiendo los Métodos por Omisión del Modelo

Como se explica en el apéndice sobre consultas, cada modelo obtiene algunos métodos automáticamente – los más notables son save() y delete(). Éstos se pueden sobreescribir para alterar el comportamiento.

Un caso de uso clásico de sobreescritura de los métodos incorporados es cuando se necesita que suceda algo cuando se guarda un objeto, por ejemplo:

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

    save: function() {
        do_something();
        // Call the "real" save() method.
        super(models.Model, this).save();
        do_something_else();
    }
});

También se puede evitar el guardado:

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

    save: function() {
        if (this.name == "Yoko Ono's blog")
            return; // Yoko shall never have her own blog!
        else
                // Call the "real" save() method
            super(models.Model, this).save();
    }
});