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:
- 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()
});
- 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})
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:
Para ordenar por title en orden descendente:
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();
}
});