Odoo 10 Development Essential Es Master PDF
Odoo 10 Development Essential Es Master PDF
Odoo 10 Development Essential Es Master PDF
md 07/02/2018
1 # odoo essentials
2
3 ## Capítulo 1. Iniciando con desarrollo Odoo
4
5 Antes de sumergirnos en el desarrollo en Odoo, necesitamos armar
nuestro ambiente de desarrollo y aprender las tareas de administraciión
básicas para ello.
6
7 Oddo se construye utilizando el lenguaje de programación Phyton, y
utiliza la base de datos PostgreSQL para almacenamiento de datos; estos
son los dos principales requerimientos de un huesped Odoo. Para correr
Odoo desde la fuente, necesitaremos primero instalar las librerias de
Phyton de as cuales depende. El código de fuente de Odoo puede
posteriormente ser descargado desde GitHub. Mientras podemos descargar
un archivo ZIP o tarball, veremos que es mejor si obtenemos las fuentes
utilizando la versión Git de aplicación de control.; Nos ayudará a
tenerlo instalado en nuestro huesped Odoo también.
8
9 ## Creando un huesped para el servidor Odoo
10
11 Se recomienda para el sistema Debian/Ubuntu para el servidor Odoo. Aún
serás capaz de trabajar desde tu sistema de escritorio favorito, bien
sea Windows, Mac, o Linux.
12
13 Odoo puede correr con gran variedad de sistemas operativos, entonces,
por qué escoger Debian a expensas de otros sistemas operativos?: Porque
Debian es considerado la plataforma de despliegue de referencia por el
equipo Odoo; tiene el mejor soporte. Será más facil hallar ayuda y
recursos adicionales si trabajamos con Debian/Ubuntu.
14
15 También es la plataforma con la que la mayoría de desarrolladores
trabajan y donde se implementan más despliegues. Así que
inevitablemente, se espera que los desarrolladores de Odoo se sientan
cómodos con la plataforma Debian/Ubuntu. Incluso si tienes un pasado
Windows, será importante que tengas algún conocimiento acerca de esto.
16
17 En este capítulo, aprenderás cómo armar y trabajar con Odoo hospedado
en un sistema Debian, usando sólo la linea de comando. Para aquellos en
casa con un sistema Windows, cubriremos cómo configurar una máquina
virtual para hospedar el servidor Odoo. Como ventaja, las técnicas que
aprenderás aquí también te permitirán administrar Odoo en servidores en
la nube, donde tu único acceso será a través de Secure Shel ( SSH).
18
19 ### Nota
20
21 Manten en mente que estas instrucciones están destinadas a organizar un
nuevo sistemas para desarrollo. Si deseas probar algunos de ellos en un
sistema existente, siempre toma una copia de seguridad antes de tiempo
para poder restaurarlo en caso de que algo salga mal.
22
23 ## Provisión para un huesped Debian
24
25 Como se explicó antes, necesitaremos un huesped basado en Debian para
nuestro servidor Odoo. Si estos son tus primeros pasos con Linux, puede
gustarte notar que Ubuntu es una distribución Linux basada en Debian,
así que son muy similares.
26
27 Está garantizado que Odoo trabaje con la versión actual estable de
Debian o Ubuntu. Al tiempo de escribir, estos son Debian 8 "Jessie" y
Ubuntu 16.04.1 LTS (Xenial Xerus). Ambos, vienen con Python 2.7, el
cual es necesario para correr Odoo. Es importante señalar que Odoo no
Page 1
capitulo--1.md 07/02/2018
107 $ sudo apt-get install npm # Install NodeJs and its package manager
108
109 $ sudo ln -s /usr/bin/nodejs /usr/bin/node # call node runs nodejs
110
111 $ sudo npm install -g less less-plugin-clean-css #Install less compiler
112
113 ```
114
115 Partiendo de la versión 9.0, el cliente web de Odoo requiere que el
preprocesador `less` CSS esté instalado en el sistema para que las
páginas web puedan ser renderizadas correctamente. Para instalar esto,
necesitamos Node.js y npm.
116
117 Luego, necesitamos obtener el código fuente Odoo e instalar sus
dependencias. El código fuente Odoo incluye un script de utilidades,
dentro del directorio `odoo/setup/`, para ayudarnos a instalar las
dependencias requeridas en un sistema Debian/Ubuntu:
118
119 ```
120 $ mkdir ~/odoo-dev # Create a directory to work in
121
122 $ cd ~/odoo-dev # Go into our work directory
123
124 $ git clone https://github.com/odoo/odoo.git -b 10.0 --depth=1 # Get
Odoo source code
125
126 $ ./odoo/setup/setup_dev.py setup_deps # Installs Odoo system
dependencies
127
128 $ ./odoo/setup/setup_dev.py setup_pg # Installs PostgreSQL & db
superuser for unix user
129 ```
130
131 Al final, Odoo debería estar listo para utilizarse. El símbolo ~ es n
atajo para nuestro directorio `home` (por ejemplo, `/home/odoo`).
132 La opción `git -b 10.0` indíca a Git que descargue específicamente la
rama 10.0 de Odoo. Al tiempo de la escritura, esto es redundante ya que
10.0 es la rama por defecto; sin embargo, esto puede cambiar, entonces,
puede hacer el script a prueba del futuro. La opción `--depth=1` indica
a Git que descargue sólo la última revisión, en vez del último
historial de cambio completo, haciendo la descarga más pequeña y más
veloz.
133
134 Para iniciar un servidor Odoo, solo ejecuta:
135
136 ```
137 $ ~/odoo-dev/odoo/odoo-bin
138 ```
139
140 ### Tip
141
142 En Odoo 10, el script `odoo.py`, utilizado en versiones previas para
iniciar el servidor, fue reemplazado con `odoo-bin`.
143
144 De forma predeterminada, las instancias Odoo escuchan en el puerto
8069, por lo que si apuntamos un navegador a `http: //
<dirección-servidor>: 8069`, llegaremos a estas instancias. Cuando lo
accedemos por primera vez, nos muestra un asistente para crear una
nueva base de datos, como se muestra en la siguiente captura de pantalla:
145
146 
Page 4
capitulo--1.md 07/02/2018
147
148 Como desarrolladores, necesitaremos trabajar con varias bases de datos,
así que es más convenientes más conveniente crearlos desde la línea de
comandos, así que aprenderemos cómo hacerlo. Ahora presione ***Ctrl +
C*** en el terminal para detener el servidor Odoo y volver al prompt de
comando.
149
150 ## Inicializando una nueva database Odoo
151
152 Para ser capaces de crear una nueva database, tu usuario debe ser un
super usuario PostgreSQL. El sigiente comando crea un superusuario
PostgreSQL para el usuario actual Unix.
153
154 ```
155 $ sudo createuser --superuser $(whoami)
156 ```
157 Para crear una nueva database, usa el comando `createdb`. Creeamos una
database `demo`:
158
159 ```
160 $ createdb demo
161 ```
162
163 Para inicializar esta database con el esquema de datos Odoo, debemos
ejecutar Odoo en la database vacía, usando la opción `-d`:
164
165 ```
166 $ ~/odoo-dev/odoo/odoo-bin -d demo
167 ```
168
169 Esto tomará un par de minutos para inicializar una database `demo`, y
terminará con un mensaje de registro INFO, **Módulos cargados**.
170
171 ### Nota
172 Ten en cuenta que puede no ser el último mensaje de registro, y puede
estar en las últimas tres o cuatro líneas. Con esto, el servidor estará
listo para escuchar las peticiones del cliente.
173
174 De forma predeterminada, esto inicializará la database con datos de
demostración, que a menudo es útil para las databases de desarrollo.
Para inicializar una database sin datos de demostración, agregue la
opción `--without-demo-data = all` al comando.
175
176 Ahora abre `http: // <server-name>: 8069` con tu navegador para que se
presente la pantalla de inicio de sesión. Si no conoces el nombre del
servidor, escribe el comando `hostname` en el terminal para encontrarlo
o el comando `ifconfig` para encontrar la dirección IP.
177
178 Si estás hospedando Odoo en una máquina virtual, es posible que debas
establecer algunas configuraciones de red para poder acceder desde tu
sistema huesped. La solución más simple es cambiar el tipo de red de la
máquina virtual de NAT a Bridged. Con esto, en lugar de compartir la
dirección IP del huesped, la máquina virtual invitada tendrá su propia
dirección IP. También es posible utilizar NAT, pero eso requiere que
configures el reenvío de puertos para que su sistema sepa que algunos
puertos, como `8069`, deben ser manejados por la máquina virtual. En
caso de que tengas problemas, esperamos que estos detalles te ayuden a
encontrar información relevante en la documentación del software de
virtualización elegido.
179
180 La cuenta de administrador predeterminada es `admin` con su contraseña
Page 5
capitulo--1.md 07/02/2018
287 ```
288 $ ~ / Odoo-dev / odoo / odoo-bin --xmlrpc-port = 8071
289 ```
290
291 Ahí lo tienes: dos instancias Odoo en el mismo servidor de escucha en
diferentes puertos! Las dos instancias pueden utilizar bases de datos
iguales o diferentes, dependiendo de los parámetros de configuración
utilizados. Y los dos podrían estar ejecutando las mismas o diferentes
versiones de Odoo.
292
293 ### La opción filtro de la base de datos
294
295 Cuando se desarrolla con Odoo, es frecuente trabajar con varias bases
de datos, ya veces incluso con diferentes versiones de Odoo. Detener e
iniciar diferentes instancias de servidor en el mismo puerto y cambiar
entre distintas bases de datos puede provocar que las sesiones de
cliente web se comporten de forma incorrecta.
296
297 El acceso a nuestra instancia utilizando una ventana del navegador que
se ejecuta en modo privado puede ayudar a evitar algunos de estos
problemas.
298
299 Otra buena práctica es habilitar un filtro de base de datos en la
instancia del servidor para asegurarse de que sólo permite las
solicitudes de la base de datos con la que queremos trabajar, ignorando
todos las demás. Esto se hace con la opción `--db-filter`. Acepta una
expresión regular que se utiliza como filtro para los nombres de base
de datos válidos. Para que coincida con un nombre exacto, la expresión
debe comenzar con un `^` y terminar con `$`.
300
301 Por ejemplo, para permitir sólo la base de datos `demo` utilizaríamos
este comando:
302
303 ```
304 $ ~ / Odoo-dev / odoo / odoo-bin --db-filter = ^ demo $
305 ```
306
307 ### Administrar los mensajes de registro del servidor
308
309 La opción `--log-level` nos permite establecer la verbosidad del
registro. Esto puede ser muy útil para entender lo que está sucediendo
en el servidor. Por ejemplo, para habilitar el nivel de registro de
depuración, use la opción `--log-level=debug`.
310
311 Los siguientes niveles de registro pueden ser particularmente
interesantes:
312
313 + `Debug_sql` para inspeccionar consultas SQL generadas por el servidor
314 + `Debug_rp`c para detallar las peticiones recibidas por el servidor
315 + `Debug_rpc_answer` para detallar las respuestas enviadas por el
servidor
316
317 De forma predeterminada, la salida del registro se dirige a la salida
estándar (la pantalla de la consola), pero se puede dirigir a un
archivo de registro con la opción `--logfile=<filepath>`.
318
319 Finalmente, la opción `--dev=all` mostrará el depurador de Python
(`pdb`) cuando se genera una excepción. Es útil hacer un análisis
post-mortem de un error de servidor. Ten en cuenta que no tiene ningún
efecto en la verbosidad del registrador. Puedes encontrar más detalles
sobre los comandos del depurador de Python en
Page 9
capitulo--1.md 07/02/2018
https://docs.python.org/2/library/pdb.html#debugger-commands.
320
321
322 ### Desarrollando desde tu estación de trabajo
323 Puedes estar ejecutando Odoo con un sistema Debian / Ubuntu en una
máquina virtual local o en un servidor a través de la red. Pero puede
que prefieras hacer el trabajo de desarrollo en tu estación de trabajo
personal, utilizando tu editor de texto favorito o IDE. Este suele ser
el caso de los desarrolladores que trabajan desde estaciones de trabajo
Windows. Pero también puede ser el caso de los usuarios de Linux que
necesitan trabajar en un servidor Odoo a través de la red local.
324
325 Una solución para esto es para permitir el uso compartido de archivos
en el huesped Odoo para que los archivos sean fáciles de editar desde
nuestra estación de trabajo. Para las operaciones del servidor Odoo,
como un reinicio del servidor, podemos usar un shell SSH (como PuTTY en
Windows) junto con nuestro editor favorito.
326
327 #### Usando un editor de texto Linux
328 Tarde o temprano, necesitaremos editar archivos desde la línea de
comandos del shell. En muchos sistemas Debian, el editor de texto
predeterminado es vi. Si no te sientes cómodo con él, probablemente
podrías usar una alternativa más amigable. En los sistemas Ubuntu, el
editor de texto predeterminado es nano. Es posible que prefieras este,
ya que es más fácil de usar. En caso de que no esté disponible en tu
servidor, se puede instalar con:
329
330 ```
331 $ sudo apt-get install nano
332 ```
333
334 En las siguientes secciones, asumiremos nano como el editor preferido.
Si prefieres cualquier otro editor, siéntete libre de adaptar los
comandos en consecuencia.
335
336 ## Instalando y configurando Samba
337 El servicio Samba ayuda a que los servicios de compartición de archivos
de Linux sean compatibles con los sistemas Microsoft Windows. Podemos
instalarlo en nuestro servidor Debian / Ubuntu con este comando:
338
339
340 ```
341
342 $ Sudo apt-get instalar samba samba-common-bin
343
344 ```
345
346
347
348 El paquete `samba` instala los servicios de intercambio de archivos y
el paquete `samba-common-bin` es necesario para la herramienta
`smbpasswd`. De forma predeterminada, los usuarios autorizados a
acceder a archivos compartidos deben registrarse con él. Necesitamos
registrar a nuestro usuario, `odoo` por ejemplo, y establecer una
contraseña para su acceso a compartir archivos:
349 ```
350
351
352 $ Sudo smbpasswd -a odoo
353 ```
354
Page 10
capitulo--1.md 07/02/2018
355
356
357
358
359 Después de esto, se nos pedirá una contraseña para usar para acceder al
directorio compartido, y el usuario `odoo` podrá acceder a los archivos
compartidos para su directorio personal, aunque será de sólo lectura.
Queremos tener acceso de escritura, por lo que necesitamos editar el
archivo de configuración de Samba para cambiarlo de la siguiente manera:
360
361 ```
362
363
364 $ Sudo nano /etc/samba/smb.conf
365
366 ```
367
368 En el archivo de configuración, busque la sección `[homes]`. Edita sus
líneas de configuración para que coincidan con la configuración de la
siguiente manera:
369
370 ```
371 [homes]
372 comment = Home Directories
373 browseable = yes
374 read only = no
375 create mask = 0640
376 directory mask = 0750
377 ```
378
379 Para que los cambios de configuración tengan efecto, reinicia el
servicio:
380
381 ```
382 $ sudo /etc/init.d/smbd restart
383 ```
384 #### Tip
385 ##### Descargando el código ejemplo
386
387 Puedes descargar los archivos de códigos de ejemplo para todos los
libros Packt que hayas comprado desde tu cuenta en
http://www.packtpub.com. Si compraste este libro en algun otro sitio,
puedes entrar a http://www.packtpub.com/support y registrate para que
te envien los archivos directamente por correo electrónico.
388
389 Para acceder a los archivos desde Windows, podemos asignar una unidad
de red para la ruta `\\ <my-server-name>\odoo`
390 utilizando el nombre de usuario y la contraseña específicos definidos
con `smbpasswd` Al intentar iniciar sesión con el usuario `odoo`,
podría encontrar problemas con Windows agregando el dominio del equipo
al nombre de usuario (por ejemplo, `MYPC \ odoo`). Para evitar esto,
utilice un dominio vacío añadiendo un caracter`\` al inicio de sesión
(por ejemplo, `\ odoo`):
391
392 
393
394
395 Si ahora abrimos la unidad asignada con el Explorador de Windows,
podremos acceder y editar el contenido del directorio home del usuario
`odoo`:
396
Page 11
capitulo--1.md 07/02/2018
397 
398
399 Odoo incluye un par de herramientas que son muy útiles para los
desarrolladores, y vamos a hacer uso de ellAs a lo largo del libro. Son
características técnicas y el modo de desarrollo. Estos están
desactivados por defecto, por lo que este es un buen momento para
aprender a habilitarlos.
400
401 ### Activación de las herramientas de desarrollo
402 Las herramientas de desarrollo proporcionan una configuración y
funciones avanzadas de servidor. Estos incluyen un menú de depuración
en la barra de menú superior junto con opciones de menú adicionales en
el menú **Settings**, en particular el menú **Technical**.
403
404 Estas herramientas vienen deshabilitadas de forma predeterminada y,
para habilitarlas, debemos iniciar sesión como administrador. En la
barra de menú superior, seleccione el menú **Settings**. En la parte
inferior derecha, debajo de la versión Odoo, encontrará dos opciones
para habilitar el modo de desarrollo; cualquiera de ellas habilitará
los menús **Debug** y **Technical**. La segunda opción, **Activate the
developer mode (whit assest)**, también deshabilita la minificación de
JavaScript y CSS utilizada por el cliente web, facilitando la
depuración del comportamiento del cliente:
405
406 
407
408 Después de eso, la página se vuelve a cargar y debería verse un icono
de error en la barra de menú superior, justo antes del avatar y nombre
de usuario de la sesión que proporciona las opciones de modo de
depuración. Y en la opción **Settings** en el menú superior, deberíamos
ver una nueva sección del menú **Technical** que da acceso a muchos
internos de la instancia de Odoo:
409
410 
411
412 #### Tip
413 La opción **Technical** del menú nos permite inspeccionar y editar
todas las configuraciones Odoo almacenadas en la base de datos, desde
la interfaz de usuario hasta la seguridad y otros parámetros del
sistema. Aprenderás más sobre muchos de estos a lo largo del libro.
414
415 ## Instalación de módulos de terceros
416 Hacer nuevos módulos disponibles en una instancia Odoo para que puedan
instalarse es algo que los recién llegados a Odoo suelen encontrar
confuso. Pero no tiene que ser así, así que vamos a desmitificarlo.
417
418 ## Encontrar módulos comunitarios
419 Hay muchos módulos Odoo disponibles en Internet. La tienda de
aplicaciones de Odoo en apps.odoo.com es un catálogo de módulos que se
pueden descargar e instalar en su sistema. La **Odoo Community
Association (OCA)** coordina las contribuciones de la comunidad y
mantiene bastantes repositorios de módulos en GitHub en
https://github.com/OCA/.
420
421 Para agregar un módulo a una instalación de Odoo, podríamos copiarlo en
el directorio `addons` junto con los módulos oficiales. En nuestro
caso, el directorio `addons` está en `~ / odoo-dev / odoo / addons /`.
Esto podría no ser la mejor opción para nosotros, ya que nuestra
instalación de Odoo se basa en un repositorio de código controlado por
versiones, y queremos mantenerlo sincronizado con el repositorio de
GitHub.
Page 12
capitulo--1.md 07/02/2018
422
423 Afortunadamente, podemos usar ubicaciones adicionales para los módulos
para que podamos mantener nuestros módulos personalizados en un
directorio diferente, sin tenerlos mezclados con los oficiales.
424
425 Como ejemplo, vamos a descargar el código de este libro, disponible en
GitHub, y hacer disponíbles esos módulos addon en nuestra instalación
de Odoo.
426
427 Para obtener el código fuente de GitHub, ejecute los siguientes comandos:
428
429 ```
430 $ cd ~/odoo-dev
431
432 $ git clone https://github.com/dreispt/todo_app.git -b 10.0
433 ```
434 Usamos la opción `-b` para asegurarnos de que estamos escargando los
módulos para la versión 10.0.
435
436 Después de esto, tendremos un directorio nuevo `/ todo_app` junto al
directorio `/ odoo`, que contiene los módulos. Ahora debemos informar a
Odoo sobre este nuevo directorio de módulos.
437
438 ### Configurandola ruta addons
439 El servidor Odoo tiene una opción de configuración llamada
`addons_path` para establecer dónde el servidor debe buscar módulos. De
forma predeterminada, esto apunta al directorio `/ addons`, donde se
ejecuta el servidor Odoo.
440
441 Podemos proporcionar no sólo una, sino una lista de directorios donde
se pueden encontrar módulos. Esto nos permite mantener nuestros módulos
personalizados en un directorio diferente, sin tenerlos mezclados con
los addons oficiales.
442
443 Vamos a iniciar el servidor con una ruta addons que incluye nuestro
nuevo directorio de módulos:
444 ```
445 $ cd ~/odoo-dev/odoo
446
447 $ ./odoo-bin -d demo --addons-path="../todo_app,./addons"
448 ```
449
450 Si miras más de cerca el registro del servidor, notarás una línea que
informa de la ruta de complementos en uso: `INFO? Odoo: addons paths:
[...]`. Confirma que contiene nuestro directorio `todo_app`.
451
452 ### Actualizando la lista de aplicaciones
453 Todavía necesitamos pedirle a Odoo que actualice su lista de módulos
antes de que estos nuevos módulos estén disponibles para la instalación.
454
455 Para ello, necesitamos activar el modo desarrollador, ya que
proporciona la opción de menú **Actualizar Lista de Aplicaciones**. Se
puede encontrar en el menú superior de **Aplicaciones**.
456
457 Después de actualizar la lista de módulos, podemos confirmar que los
nuevos módulos están disponibles para la instalación. Utilice la opción
de menú **Aplicaciones** para ver la lista de módulos locales. Busca
`todo` y deberías ver los nuevos módulos disponibles.
458
459 Ten en cuenta que la segunda opción de menú **App Store** muestra la
lista de módulos del almacén de aplicaciones Odoo en lugar de los
Page 13
capitulo--1.md 07/02/2018
módulos locales:
460
461 
462
463 ## Resumen
464 En este capítulo, aprendimos a configurar un sistema Debian para alojar
Odoo e instalarlo desde el código fuente de GitHub. También aprendimos
a crear bases de datos Odoo y ejecutar instancias de Odoo. Para
permitir a los desarrolladores utilizar sus herramientas favoritas en
su estación de trabajo personal, explicamos cómo configurar el uso
compartido de archivos en el huesped Odoo.
465
466 Ahora deberíamos tener un ambiente Odoo en funcionamiento para trabajar
y estar cómodos con la administración de bases de datos e instancias.
467
468 Con esto en su lugar, estamos listos para ir directamente a la acción.
En el próximo capítulo, crearemos nuestro primer módulo Odoo desde cero
y entenderemos los principales elementos que involucra.
469
470 ¡Entonces empecemos!
Page 14
capitulo-2.md 07/02/2018
128 ```
129 $ cd ~/odoo-dev
130
131
132 $ ./odoo/odoo-bin -d todo --addons-path="custom-addons,odoo/addons"
--save
133 ```
134 La opción `--save` guarda las opciones que utilizaste en un archivo de
configuración. Esto nos evita repetirlas cada vez que reiniciamos el
servidor: solo se ejecuta `./odoo-bin` y se utilizará la última opción
guardada.
135
136 Observa atentamente el registro del servidor. Debe tener una línea
`INFO? Odoo: addons paths: [...]`. Debe incluir nuestro directorio de
`custom-addons`.
137
138 Recuerda incluir también cualquier otro directorio de complementos que
puedas estar utilizando. Por ejemplo, si también tienes un directorio
`~ / odoo-dev / extra` que contiene módulos adicionales que se
utilizarán, es posible que desees incluirlos también utilizando la
opción `--addons-path`:
139 ```
140
141 --addons-path = "custom-addons, extra, odoo / addons"
142
143 ```
144
145
146 Ahora necesitamos la instancia Odoo para reconocer el nuevo módulo que
acabamos de agregar.
147
148 ### Instalando el nuevo módulo
149 En el menú superior de **Aplicaciones**, seleccione la opción
**Actualizar Lista de Aplicaciones**. Esto actualizará la lista de
módulos, añadiendo los módulos que se hayan agregado desde la última
actualización a la lista. Recuerda que necesitamos activar el modo
desarrollador para que esta opción sea visible. Esto se hace en el
panel de **Configuración**, en el enlace de abajo a la derecha, debajo
de la información del número de versión de Odoo.
150
151 #### Tip
152 Asegúrate de que tu sesión de cliente web está funcionando con la base
de datos correcta. Puedes comprobarlo en la parte superior derecha: el
nombre de la base de datos se muestra entre paréntesis, justo después
del nombre de usuario. Una manera de aplicar la base de datos correcta
es iniciar la instancia del servidor con la opción adicional
`--db-filter = ^ MYDB $`.
153
154 La opción **Aplicaciones** nos muestra la lista de módulos disponibles.
De forma predeterminada, muestra sólo los módulos de aplicación. Ya que
hemos creado un módulo de aplicación, no necesitamos eliminar ese
filtro para verlo. Escribe `todo` en la búsqueda y debes ver nuestro
nuevo módulo, listo para ser instalado:
155
156 ![Installed]file:img/2-01.jpg)
157
158 Ahora haZ clic en el botón **Instalar** del módulo y ¡estamos listos!
159
160 ### Actualizando un módulo
161 El desarrollo de un módulo es un proceso iterativo, y querrás que los
cambios hechos en los archivos fuente sean aplicados y hechos visibles
Page 5
capitulo-2.md 07/02/2018
en Odoo.
162
163 En muchos casos, esto se realiza actualizando el módulo: busca el
módulo en la lista de **Aplicaciones** y una vez que ya esté instalado,
tendrás disponible un botón de **Actualización**.
164
165 Sin embargo, cuando los cambios son sólo en código Python, la
actualización puede no tener un efecto. En lugar de una actualización
de módulo, es necesario reiniciar el servidor de aplicaciones. Dado que
Odoo carga el código Python sólo una vez, cualquier cambio posterior en
el código requiere que se aplique un reinicio del servidor.
166
167 En algunos casos, si los cambios de módulo estuvieran en los archivos
de datos y en el código de Python, es posible que necesites ambas
operaciones. Esta es una fuente común de confusión para los nuevos
desarrolladores Odoo.
168 Pero afortunadamente, hay una mejor manera. La manera más segura y
rápida de hacer que todos nuestros cambios en un módulo sean efectivos
es detener y reiniciar la instancia del servidor, solicitando que
nuestros módulos sean actualizados a nuestra base de datos de trabajo.
169
170 En el terminal donde se ejecuta la instancia de servidor, utiliza
**Ctrl + C** para detenerla. A continuación, inicie el servidor y
actualice el módulo `todo_app` mediante el siguiente comando:
171
172 ```
173 $ ./odoo-bin -d todo -u todo_app
174
175 ```
176
177
178 La opción `-u` (o `--update` en el forma larga) requiere la opción `-d`
y acepta una lista de módulos separados por comas para actualizar. Por
ejemplo, podríamos usar `-u todo_app, mail`. Cuando se actualiza un
módulo, también se actualizan todos los módulos instalados que dependen
de él. Esto es esencial para mantener la integridad de los mecanismos
de herencia, utilizados para extender características.
179
180 A lo largo del libro, cuando necesites aplicar el trabajo realizado en
módulos, la forma más segura es reiniciar la instancia Odoo con el
comando anterior. Al presionar la tecla de flecha hacia arriba, se
obtiene el comando anterior que se utilizó. Por lo tanto, la mayoría de
las veces, te encontrará usando la combinación de teclas _**Ctrl +
C**_, arriba y _**Enter**_.
181
182 Desafortunadamente, tanto la actualización de la lista de módulos como
la desinstalación de módulos son acciones que no están disponibles a
través de la línea de comandos. Estos deben hacerse a través de la
interfaz web en el menú de **Aplicaciones**.
183
184 ### El modo de desarrollo del servidor
185 En Odoo 10 se introdujo una nueva opción que proporciona
características amigables para los desarrolladores. Para usarla, inicia
la instancia del servidor con la opción adicional `--dev = all`.
186 Esto permite que algunas características prácticas aceleren nuestro
ciclo de desarrollo. Los más importantes son:
187 + Recargar código Python automáticamente, una vez que se guarda un
archivo Python, evitando un reinicio manual del servidor
188 + Leer las definiciones de vista directamente desde los archivos XML,
evitando actualizaciones manuales del módulo
189
Page 6
capitulo-2.md 07/02/2018
190 La opción `--dev` acepta una lista de opciones separadas por comas,
aunque la opción `all` será adecuada la mayor parte del tiempo. También
podemos especificar el depurador que preferimos usar. De forma
predeterminada, se utiliza el depurador Python, `pdb`. Algunas personas
pueden preferir instalar y usar depuradores alternativos. Aquí también
se admiten `ipdb` y `pudb`.
191
192 ## La capa modelo
193 Ahora que Odoo conoce nuestro nuevo módulo, comencemos agregándole un
modelo simple.
194
195 Los modelos describen objetos de negocio, como una oportunidad, ordenes
de clientes o socios (cliente, proveedor, etc.). Un modelo tiene una
lista de atributos y también puede definir su negocio específico.
196
197 Los modelos se implementan utilizando una clase Python derivada de una
clase de plantilla Odoo. Se traducen directamente a objetos de base de
datos, y Odoo se encarga de esto automáticamente al instalar o
actualizar el módulo. El mecanismo responsable de esto es el **Modelo
Relacional de Objetos (ORM)**.
198
199 Nuestro módulo será una aplicación muy simple para mantener las tareas
pendientes. Estas tareas tendrán un solo campo de texto para la
descripción y una casilla de verificación para marcarlas como
completas. Más adelante deberíamos añadir un botón para limpiar la
lista de tareas de las tareas completas.
200
201 ### Creando el modelo de datos
202 Las directrices de desarrollo de Odoo establecen que los archivos
Python para los modelos deben colocarse dentro de un subdirectorio
`models`. Para simplificar, no lo seguiremos aquí, así que vamos a crar
un archivo `todo_model.py` en el directorio principal del módulo
`todo_app`.
203
204 Añade el siguiente contenido:
205
206 ```
207 # -*- coding: utf-8 -*-
208 from odoo import models, fields
209 class TodoTask(models.Model):
210 _name = 'todo.task'
211 _description = 'To-do Task'
212 name = fields.Char('Description', required=True)
213 is_done = fields.Boolean('Done?')
214 active = fields.Boolean('Active?', default=True)
215
216 ```
217 La primera línea es un marcador especial que indica al intérprete de
Python que este archivo tiene UTF-8 para que pueda esperar y manejar
caracteres no ASCII. No usaremos ninguno, pero es una buena práctica
tenerlo de todos modos.
218
219 La segunda línea es una instrucción de importación de código Python,
haciendo disponibles los objetos `models` y `fields` del núcleo Odoo.
220
221 La tercera línea declara nuestro nuevo modelo. Es una clase derivada de
`models.Model`.
222
223 La siguiente línea establece el atributo `_name` que define el
identificador que se utilizará en Odoo para referirse a este modelo.
Toma en cuenta que el nombre real de la clase Python, `TodoTask` en
Page 7
capitulo-2.md 07/02/2018
294
295 Tenemos elementos de menú que pueden activar acciones que pueden hacer
vistas. Por ejemplo, la opción de menú **Usuarios** procesa una acción
también denominada **Usuarios**, que a su vez genera una serie de
vistas. Existen varios tipos de vista disponibles, como las vistas de
lista y formulario y las opciones de filtro también disponíbles, están
definidas por un tipo particular de vista, la vista de búsqueda.
296
297 Las directrices de desarrollo de Odoo establecen que los archivos XML
que definen la interfaz de usuario deben colocarse dentro de un
subdirectorio `views /` subdirectorio
298 Comencemos a crear la interfaz de usuario para nuestra aplicación de
tareas pendientes.
299 ###Agregar elementos de menú
300
301 Ahora que tenemos un modelo para almacenar nuestros datos, debemos
hacerlo disponible en la interfaz de usuario.
302
303 Para ello, debemos añadir una opción de menú para abrir el modelo
`To–do Task` para que pueda utilizarse.
304
305 Cree el archivo `views / todo_menu.xml` para definir un elemento de
menú y la acción realizada por él:
306
307
308 ```
309
310 <?xml version="1.0"?>
311 <odoo>
312 <!-- Action to open To-do Task list -->
313 <act_window id="action_todo_task"
314 name="To-do Task"
315 res_model="todo.task"
316 view_mode="tree,form" />
317 <!-- Menu item to open To-do Task list -->
318 <menuitem id="menu_todo_task"
319 name="Todos"
320 action="action_todo_task" />
321 </odoo>
322
323 ```
324
325 La interfaz de usuario, incluidas las opciones y las acciones de menú,
se almacena en las tablas de la base de datos. El archivo XML es un
archivo de datos utilizado para cargar esas definiciones en la base de
datos cuando el módulo se instala o actualiza. El código anterior es un
archivo de datos Odoo, que describe dos registros para añadir a Odoo:
326
327 + El elemento `<act_window>` define una acción de ventana del lado del
cliente que abrirá el modelo `todo.task` con las vistas de árbol y
formulario habilitadas, en ese orden.
328 + El `<menuitem>` define un elemento de menú superior que llama a la
acción `action_todo_task`, que se definió anteriormente.
329
330 Ambos elementos incluyen un atributo id. Este atributo id también
llamado **XML ID**, es muy importante: se utiliza para identificar de
forma única cada elemento de datos dentro del módulo, y puede ser
utilizado por otros elementos para referenciarlo. En este caso, el
elemento `<menuitem>` necesita hacer referencia a la acción para
procesar, y necesita hacer uso de la <act_window> ID para eso. Los ID
XML se tratan con mayor detalle en el Capítulo 4, *Datos del módulo*
Page 10
capitulo-2.md 07/02/2018
331
332 Nuestro módulo aún no conoce el nuevo archivo de datos XML. Esto se
hace agregándolo al atributo de datos en el archivo `__manifest__.py`.
Este, contiene la lista de archivos a cargar por el módulo. Agregue
este atributo al diccionario del manifiesto:
333 ```
334
335 'Data': ['views / todo_menu.xml'],
336 ```
337
338 Ahora necesitamos actualizar el módulo de nuevo para que estos cambios
surtan efecto. Vaya al menú superior de **Todos** y debe ver nuestra
nueva opción de menú disponible:
339
340 
341
342 Aunque no hemos definido nuestra vista de interfaz de usuario, al hacer
clic en el menú **Todos** se abrirá un formulario generado
automáticamente para nuestro modelo, lo que nos permitirá agregar y
editar registros.
343
344 Odoo es lo suficientemente agradable como para generarlos
automáticamente para que podamos empezar a trabajar con nuestro modelo
de inmediato.
345
346 ¡Hasta aquí todo bien! Vamos a mejorar nuestra interfaz de usuario
ahora. Trata de hacer mejoras graduales como se muestra en las próximas
secciones, haciendo actualizaciones de módulos frecuentes, y no tengas
miedo de experimentar. También puedes intentar la opción de servidor
`--dev = all`. Usándolo, las definiciones de vista se leen directamente
desde los archivos XML para que tus cambios puedan estar inmediatamente
disponibles para Odoo sin necesidad de una actualización de módulo.
347
348 ### Tip
349
350 Si una actualización falla debido a un error de XML, no te preocupe!
Comenta las últimas porciones XML editadas o elimina el archivo XML de
`__manifest__.py` y repita la actualización. El servidor debe iniciarse
correctamente. Ahora lee el mensaje de error en el registro del
servidor con cuidado: debe señalarte dónde está el problema.
351
352 Odoo admite varios tipos de vistas, pero las tres más importantes son:
`tree` (generalmente llamado vistas de lista), `form` y `search views`.
Vamos a añadir un ejemplo de cada uno a nuestro módulo.
353 ### Creando la vista de formulario
354
355 Todas las vistas se almacenan en la base de datos, en el modelo
`ir.ui.view`. Para añadir una vista a un módulo, declaramos un elemento
`<record>` que describe la vista en un archivo XML, que se va a cargar
en la base de datos cuando se instala el módulo.
356
357 Agregue este nuevo archivo `views / todo_view.xml` para definir nuestra
vista de formulario:
358 ```
359 <?xml version="1.0"?>
360 <odoo>
361 <record id="view_form_todo_task" model="ir.ui.view">
362 <field name="name">To-do Task Form</field>
363 <field name="model">todo.task</field>
364 <field name="arch" type="xml">
365
Page 11
capitulo-2.md 07/02/2018
411
412
413
414
415
416
417 <sheet>
418
419
420
421
422 <!-- Content goes here: -->
423 <group>
424 <field name="name"/>
425 <field name="is_done"/>
426 <field name="active" readonly="1"/>
427 </group>
428
429 </sheet>
430
431
432
433
434 </form>
435
436 ```
437
438 ### Añadiendo botones de acción
439
440 Los formularios pueden tener botones para realizar acciones. Estos
botones pueden ejecutar acciones de ventana como abrir otro formulario
o ejecutar funciones de Python definidas en el modelo.
441
442 Pueden colocarse en cualquier lugar dentro de un formulario, pero para
los formularios de estilo de documento, el lugar recomendado para ellos
es la sección `<header>`.
443
444 Para nuestra aplicación, agregaremos dos botones para ejecutar los
métodos del modelo `todo.task`:
445 ```
446
447 <header>
448
449 <button name="do_toggle_done" type="object"
450 string="Toggle Done" class="oe_highlight" />
451 <button name="do_clear_done" type="object"
452 string="Clear All Done" />
453
454
455
456
457 </header>
458 ```
459
460
461 Los atributos básicos de un botón comprenden lo siguiente:
462
463 + `stryng` con el texto a mostrar en el botón
464 + `type` de acción que realiza
465 + `name` es el identificador de esa acción
466 + `class` es un atributo opcional para aplicar estilos CSS, como en
Page 13
capitulo-2.md 07/02/2018
HTML normal
467
468 ### Uso de grupos para organizar formularios
469
470 La etiqueta `<group> `te permite organizar el contenido del formulario.
Colocar elementos `<group>` dentro de un elemento `<group>` crea un
diseño de dos columnas dentro del grupo externo. Se aconseja que los
elementos del grupo tengan un atributo de nombre para que sea más fácil
para otros módulos extenderlos.
471
472 Usaremos esto para organizar mejor nuestro contenido. Cambiemos el
contenido `<sheet>` de nuestro formulario para que coincida con este:
473
474 ```
475 <sheet>
476
477 <group name="group_top">
478 <group name="group_left">
479
480
481
482
483 <field name="name"/>
484
485 </group>
486 <group name="group_right">
487
488
489
490
491 <field name="is_done"/>
492 <field name="active" readonly="1"/>
493
494 </group>
495 </group>
496
497
498
499
500 </sheet>
501 ```
502
503 ### La vista de formulario completa
504
505 En este punto, nuestro formulario `todo.task` debe verse así:
506
507 ```
508 <form>
509 <header>
510 <button name="do_toggle_done" type="object"
511 string="Toggle Done" class="oe_highlight" />
512 <button name="do_clear_done" type="object"
513 string="Clear All Done" />
514 </header>
515 <sheet>
516 <group name="group_top">
517 <group name="group_left">
518 <field name="name"/>
519 </group>
520 <group name="group_right">
521 <field name="is_done"/>
Page 14
capitulo-2.md 07/02/2018
567 </search>
568
569
570
571
572 </field>
573 </record>
574 ```
575
576 Los elementos `<field>` definen campos que también se buscan al
escribir en el cuadro de búsqueda. Los elementos `<filter>` añaden
condiciones de filtro predefinidas, que se pueden alternar con un clic
de usuario, definido mediante el uso de una sintaxis específica.
577
578 ## La capa de lógica de negocio
579
580 Ahora vamos a añadir algo de lógica a nuestros botones. Esto se hace
con código Python, utilizando los métodos de la clase de modelos Python.
581 ### Añadiendo lógica de negocio
582
583 Debemos editar el archivo Python `todo_model.py` para agregar a la
clase los métodos llamados por los botones. Primero, necesitamos
importar la nueva API, así que agréguala a la declaración de
importación en la parte superior del archivo Python:
584 ```
585 from odoo import models, fields, api
586 ```
587
588 La acción del botón **Toggle Done** será muy simple: solo cambia la
bandera **Is Done?**. Para la lógica de los registros, utiliza el
decorador `@api.multi`. Aquí, `self` representará un conjunto de
registros, y entonces deberíamos hacer un bucle a través de cada
registro.
589
590 Dentro de la clase TodoTask, añade esto:
591 ```
592 @api.multi
593 def do_toggle_done(self):
594 for task in self:
595 task.is_done = not task.is_done
596 return True
597 ```
598 El código pasa por todos los registros de tarea y, para cada uno,
modifica el campo `is_done`, invirtiendo su valor. El método no
necesita devolver nada, pero debemos tenerlo al menos para devolver un
valor `True`. La razón es que los clientes pueden utilizar XML-RPC para
llamar a estos métodos y este protocolo no admite funciones de servidor
devolviendo sólo un valor `None`.
599
600 Para el botón **Clear All Done**, queremos ir un poco más lejos. Debe
buscar todos los registros activos que están hechos, y hacerlos
inactivos. Normalmente, se espera que los botones de formulario actúen
sólo en el registro seleccionado, pero en este caso, queremos que actúe
también en registros distintos del actual:
601 ```
602 @api.model
603 def do_clear_done(self):
604 dones = self.search([('is_done', '=', True)])
605 dones.write({'active': False})
606 return True
607 ```
Page 16
capitulo-2.md 07/02/2018
608
609
610 En los métodos decorados con `@ api.model`, la variable `self`
representa el modelo sin registro en particular. Construiremos un
conjunto de registros `dones` que contenga todas las tareas marcadas
como terminadas. A continuación, establecemos el indicador `active`
para `False` en ellos.
611
612 El método de búsqueda es un método API que devuelve los registros que
cumplen algunas condiciones. Estas condiciones están escritas en un
dominio, que es una lista de tripletes. Exploraremos los dominios con
más detalle en el Capítulo 6, *Vistas – Diseñando la interfaz de
usuario*.
613
614 El método `write` establece los valores de una vez en todos los
elementos del conjunto de registros. Los valores a escribir se
describen utilizando un diccionario. Usar `write here` es más eficiente
que iterar a través del conjunto de registros para asignar el valor a
cada uno de ellos uno por uno.
615 ### Añadiendo de pruebas
616
617 Ahora debemos agregar pruebas para la lógica de negocio. Idealmente,
queremos que cada línea de código sea cubierta por al menos un caso de
prueba. En `tests / test_todo.py`, agregua unas cuantas líneas más de
código al método `test_create ()`:
618 ```
619 # def test_create(self):
620 # ...
621
622 # Test Toggle Done
623 task.do_toggle_done()
624 self.assertTrue(task.is_done)
625 # Test Clear Done
626 Todo.do_clear_done()
627 self.assertFalse(task.active)
628
629 ```
630
631
632 Si ahora ejecutamos las pruebas y los métodos del modelo están
correctamente escritos, no deberíamos ver ningún mensaje de error en el
registro del servidor:
633
634 ```
635 $ ./odoo-bin -d todo -i todo_app --test-enable
636
637 ```
638 ## Configurando la seguridad de acceso
639
640 Es posible que haya notado que, al cargar, nuestro módulo recibe un
mensaje de advertencia en el registro del servidor:
641
642 **The model todo.task has no access rules, consider adding one.**
643
644
645 (**El modelo todo.task no tiene reglas de acceso, considere agregar
una.**)
646
647
648
649
Page 17
capitulo-2.md 07/02/2018
729
730 ```
731 Como antes, actualice el módulo para que estas adiciones entren en
vigor. El mensaje de advertencia debe desaparecer, y podemos confirmar
que los permisos están bien iniciando sesión con el usuario `demo` (la
contraseña también es `demo`). Si ejecutamos nuestras pruebas ahora
solo deberían fallar el caso de prueba `test_record_rule`.
732 ### Reglas de acceso a nivel de fila
733
734 Podemos encontrar la opción **Record Rules** en el menú **Technical**,
junto con **Access Control List*.
735
736 Las reglas de registro se definen en el modelo `ir.rule`. Como de
costumbre, necesitamos proporcionar un nombre distintivo. También
necesitamos el modelo en el que operan y el filtro de dominio que se
utilizará para la restricción de acceso. El filtro de dominio utiliza
la lista usual de tuplas sintáctica utilizada en Odoo.
737
738 Por lo general, las reglas se aplican a algunos grupos de seguridad en
particular. En nuestro caso, lo haremos aplicable al grupo Empleados.
Si no se aplica a ningún grupo de seguridad en particular, se considera
global (el campo `global` se establece automáticamente en `True`). Las
reglas globales son diferentes porque imponen restricciones que las
reglas no globales no pueden anular.
739
740 Para agregar la regla de registro, debemos crear un archivo `security /
todo_access_rules.xml` con el siguiente contenido:
741 ```
742 <?xml version="1.0" encoding="utf-8"?>
743 <odoo>
744 <data noupdate="1">
745 <record id="todo_task_user_rule" model="ir.rule">
746 <field name="name">ToDo Tasks only for owner</field>
747 <field name="model_id" ref="model_todo_task"/>
748 <field name="domain_force">
749 [('create_uid','=',user.id)]
750 </field>
751 <field name="groups" eval="
752 [(4,ref('base.group_user'))]"/>
753 </record>
754 </data>
755 </odoo>
756
757 ```
758
759 ### Nota
760
761 Observa el atributo `noupdate = "1"`. Significa que estos datos no se
actualizarán en actualizaciones de módulos. Esto le permitirá ser
personalizado más adelante ya que las actualizaciones de módulos no
destruirán los cambios realizados por el usuario. Pero ten en cuenta
que esto también será el caso durante el desarrollo, por lo que es
posible que desees establecer `noupdate = "0" ` durante el desarrollo
hasta que estéss satisfecho con el archivo de datos.
762
763 En el campo de grupos, también encontrarás una expresión especial. Es
un campo relacional de uno a muchos, y tienen una sintaxis especial
para operar. En este caso, la tupla (4, x) indica anexar `x` a los
registros, y aquí `x` es una referencia al grupo Empleados,
identificado por `base.group_user`. Esta sintaxis especial de escritura
de uno-a-muchos se discute con más detalle en el Capítulo 4, *Datos de
Page 20
capitulo-2.md 07/02/2018
Módulo*.
764
765 Como antes, debemos añadir el archivo a `__manifest__.py` antes de
poder cargarlo en el módulo:
766 ```
767 'data': [
768 'security/ir.model.access.csv',
769 'security/todo_access_rules.xml',
770 'todo_view.xml',
771 'todo_menu.xml',
772 ],
773
774 ```
775
776 Si lo hicimos bien, podemos ejecutar las pruebas de módulo y ahora
deben pasar.
777
778 ## Describiendo mejor el módulo
779
780 Nuestro módulo se ve bien. ¿Por qué no añadir un icono para que se vea
aún mejor? Para esto, solo necesitamos agregar al módulo un archivo
`static / description / icon.png` con el icono que se va a usar.
781
782 Estaremos reutilizando el icono de la aplicación existente **Notes**,
por lo que deberíamos copiar el archivo `odoo / addons / static /
description / icon.png` en el directorio `addons / todo_app / static /
description`.
783
784 Los siguientes comandos deben hacer ese truco para nosotros:
785
786 ```
787 $ mkdir -p ~/odoo-dev/custom-addons/todo_app/static/description
788 $ cp ~/odoo-dev/odoo/addons/note/static/description/icon.png
~/odoo-dev/custom-addons/todo_app/static/description
789
790 ```
791
792 Ahora, si actualizamos la lista de módulos, nuestro módulo debe
mostrarse con el nuevo icono.
793
794 También podemos añadir una descripción mejor para explicar lo que hace
y lo grandioso que es. Esto se puede hacer en la clave `description`
del archivo `__manifest__.py`. Sin embargo, la forma preferida es
agregar un archivo `README.rst` al directorio raíz del módulo.
795
796 ## Resumen
797
798 Hemos creado un nuevo módulo desde el principio, cubriendo los
elementos más utilizados en un módulo: modelos, los tres tipos básicos
de vistas (formulario, lista y búsqueda), lógica empresarial en los
métodos de modelo y seguridad de acceso.
799
800 En el proceso, nos familiarizamos con el proceso de desarrollo de
módulos, que implica actualizaciones de módulos y reinicios del
servidor de aplicaciones para hacer que los cambios graduales sean
efectivos en Odoo.
801
802 Recuerde siempre, cuando se agregan campos del modelo, se necesita una
actualización. Al cambiar el código de Python, incluyendo el archivo de
manifiesto, se necesita un reinicio. Al cambiar archivos XML o CSV, se
necesita una actualización; También, en caso de duda, haz lo siguiente:
Page 21
capitulo-2.md 07/02/2018
Page 22
capitulo-3.md 07/02/2018
64
65
66 Crea `todo_user / models / __ init__.py` con el siguiente código:
67 ```
68 from . import todo_task
69 ```
70
71
72 La línea anterior le indica a Python que busque un archivo llamado
odoo_task.py en el mismo directorio y lo importe. Por lo general,
tendrías una línea `from` para cada archivo Python en el directorio:
73 ahora crea el archivo `todo_user/models/todo_task.py` para extender el
modelo original:
74 ```
75 # -*- coding: utf-8 -*-
76 from odoo import models, fields, api
77 class TodoTask(models.Model):
78 _inherit = 'todo.task'
79 user_id = fields.Many2one('res.users', 'Responsible')
80 date_deadline = fields.Date('Deadline')
81 ```
82 El nombre de clase `TodoTask` es local para este archivo de Python y,
en general, es irrelevante para otros módulos. El atributo de clase
`_inherit` es la clave aquí: le dice a Odoo que esta clase está
heredando y modificando así el modelo `todo.task`.
83
84 #### Nota
85 Observa que el atributo `_name` está ausente. No es necesario porque ya
está heredado del modelo padre.
86
87 Las dos líneas siguientes son declaraciones de campo regulares. El
campo `user_id` representa un usuario del modelo de usuarios
`res.users`. Es un campo `Many2one`, que es equivalente a una clave
extranjera en la jerga de la base de datos. El `date_deadline` es un
simple campo de fecha. En el capítulo 5, *Modelos - Estructurando de
los datos de aplicación*, explicaremos los tipos de campos disponibles
en Odoo con más detalle.
88
89 Para que los nuevos campos se agreguen a la tabla de base de datos de
soporte del modelo, necesitamos realizar una actualización de módulo.
Si todo sale como se esperaba, deberías ver los nuevos campos al
inspeccionar el modelo `todo.task` en las opciones de menú **Technical
| Database Estructure | Models**.
90
91 ### Modificando campos existentes
92
93 Como puedes ver, agregar nuevos campos a un modelo existente es
bastante sencillo. Desde Odoo 8, también es posible modificar los
atributos en los campos heredados existentes. Se hace agregando un
campo con el mismo nombre y estableciendo valores sólo para los
atributos que se van a cambiar.
94
95 Por ejemplo, para agregar una herramienta de ayuda al campo de nombre,
agregamos esta línea a `todo_ task.py`, descrito anteriormente:
96
97 ```
98 name = fields.Char(help="What needs to be done?")
99 ```
100
101 Esto modifica el campo con los atributos especificados, dejando sin
modificar todos los otros atributos que no se utilizan explícitamente
Page 3
capitulo-3.md 07/02/2018
171 extension</field>
172 <field name="model">todo.task</field>
173 <field name="inherit_id"
174 ref="todo_app.view_form_todo_task"/>
175 <field name="arch" type="xml">
176 <!-- ...match and extend elements here! ... -->
177 </field
178 </record>
179
180 ```
181 El campo `inherit_id` identifica la vista que debe extenderse
consultando su identificador externo usando el atributo especial `ref`.
Los identificadores externos se tratarán con más detalle en el Capítulo
4, *Datos del módulo*.
182
183 Siendo XML, la mejor manera de localizar elementos en XML es usar
expresiones XPath. Por ejemplo, tomando la vista de formulario definida
en el capítulo anterior, una expresión XPath para localizar el elemento
`<field name = "is_done">` es `// field [@name] = 'is_done'`. Esta
expresión encuentra cualquier elemento `field` con un atributo `name`
igual a `is_done`. Puedes encontrar más información sobre XPath en
https://docs.python.org/2/library/xml.etree.elementtree.html#xpath-suppor
t.
184
185 Si una expresión XPath coincide con varios elementos, sólo se
modificará la primera. Por lo tanto, deben ser hechos lo más específico
posible, utilizando atributos únicos. El uso del atributo `name` es la
forma más fácil de asegurar que encontramos los elementos exactos que
queremos utilizar un punto de extensión. Por lo tanto, es importante
configurarlos en nuestros elementos de vista XML.
186
187 Una vez localizado el punto de extensión, puedes modificarlo o tener
elementos XML añadidos cerca de él. Como un ejemplo práctico, para
agregar el campo `date_deadline` antes del campo `is_done`,
escribiremos lo siguiente en `arch`:
188 ```
189 <xpath expr="//field[@name]='is_done'" position="before">
190 <field name="date_deadline" />
191 </xpath>
192 ```
193 Afortunadamente, Odoo proporciona la notación de acceso directo para
esto, así que la mayoría de las veces podemos evitar la sintaxis XPath
por completo. En lugar del elemento XPath anterior, podemos utilizar
sólo la información relacionada con el tipo de tipo de elemento para
localizar y sus atributos distintivos, y en lugar de la anterior XPath,
escribimos esto:
194 ```
195 <field name="is_done" position="before">
196 <field name="date_deadline" />
197 </field>
198 ```
199 Sólo ten en cuenta que si el campo aparece más de una vez en la misma
vista, siempre debe utilizar la sintaxis XPath. Esto es porque Odoo se
detendrá en la primera aparición del campo y puede aplicar sus cambios
al campo incorrecto.
200
201 A menudo, queremos agregar nuevos campos junto a los existentes, por lo
que la etiqueta `<field>` se utilizará como localizador con frecuencia.
Pero cualquier otra etiqueta se puede utilizar: `<sheet>, <group>,
<div>`, etc. El atributo `name` suele ser la mejor opción para los
elementos coincidentes, pero a veces, es posible que necesitemos
Page 6
capitulo-3.md 07/02/2018
293
294 </field
295 </record>
296 ```
297 Para la vista de búsqueda, agregamos la búsqueda por el usuario y
filtros predefinidos para las propias tareas del usuario y las tareas
no asignadas a nadie:
298 ```
299 <record id="view_filter_todo_task_inherited"
300 model="ir.ui.view">
301 <field name="name">Todo Task tree - User
302 extension</field>
303 <field name="model">todo.task</field>
304
305 <field name="inherit_id"
306
307
308
309
310
311 ref="todo_app.view_filter_todo_task"/>
312
313
314
315
316 <field name="arch" type="xml">
317
318 <field name="name" position="after">
319 <field name="user_id" />
320 <filter name="filter_my_tasks" string="My Tasks"
321 domain="[('user_id','in',[uid,False])]" />
322 <filter name="filter_not_assigned" string="Not
323
324
325
326
327
328 Assigned" domain="[('user_id','=',False)]" />
329 </field>
330
331
332
333
334 </field
335 </record>
336
337 ```
338
339 No se preocupe demasiado por la sintaxis específica de estas vistas.
Los cubriremos con más detalle en el Capítulo 6, *Vistas - Diseñando la
interfaz de usuario*.
340
341 ## Más modelos de mecanismos de herencia
342
343 Hemos visto la extensión básica de los modelos, llamada *herencia de
clase* en la documentación oficial. Este es el uso más frecuente de la
herencia, y es más fácil pensar en ello como una *extensión in situ*.
Tu tomas un modelo y lo extiendes. A medida que agregas nuevas
funciones, se agregan al modelo existente. No se crea un nuevo modelo.
También podemos heredar de varios modelos padre, estableciendo una
lista de valores para el atributo `_inherit`. Con esto, podemos hacer
Page 9
capitulo-3.md 07/02/2018
473
474
475 La idea es mantener los archivos de seguriad relacionados en un
subdirectorio `segurity`, por lo que crearemos un archivo `security /
todo_access_rules.xml` con el siguiente contenido:
476 ```
477 <?xml version="1.0" encoding="utf-8"?>
478 <odoo>
479 <data noupdate="1">
480 <record id="todo_app.todo_task_per_user_rule"
481 model="ir.rule">
482 <field name="name">ToDo Tasks for owner and
483 followers</field>
484 <field name="model_id" ref="model_todo_task"/>
485 <field name="groups" eval="[(4,
486 ref('base.group_user'))]"/>
487 <field name="domain_force">
488 ['|',('user_id','in', [user.id,False]),
489 ('message_follower_ids','in',
490 [user.partner_id.id])]
491 </field>
492 </record>
493 </data>
494 </odoo>
495 ```
496
497 Esto sobrescribe la regla de registro `todo_task_per_user_rule` del
módulo `todo_app`. El nuevo filtro de dominio ahora hace que una tarea
sea visible para el usuario responsable, `user_id` o para todos si el
usuario responsable no está establecido (equivale a `False`); Es
visible para todos los seguidores de la tarea también.
498
499 La regla de registro se ejecuta en un contexto en el que una variable
`user` está disponible y representa el registro para el usuario de
sesión actual. Dado que los seguidores son socios, no `users`, en lugar
de `user.id`, necesitamos usar `user.partner_id`.
500
501 El campo de grupos es una relación de muchos. La edición de datos en
estos campos utiliza una notación especial. El código `4` utilizado
aquí es para añadir a la lista de registros relacionados. También se
utiliza con frecuencia el código `6`, para reemplazar completamente los
registros relacionados con una nueva lista. Podremos analizar esta
notación con mayor detalle en el Capítulo 4, *Datos del Módulo*.
502
503 El atributo `noupdate = "1"` del elemento de registro significa que
estos datos de registro sólo se escribirán en las acciones de
instalación y se ignorarán en las actualizaciones del módulo. Esto
permite su personalización, sin asumir los riesgos de sobrescribir
personalizaciones y perderlas al hacer una actualización de módulo en
algún momento en el futuro.
504 ####Tip
505 Trabajar en archivos de datos con `<data noupdate ="1">` en el momento
del desarrollo puede ser complicado porque las ediciones posteriores de
la definición XML serán ignoradas en las actualizaciones de módulos.
Para evitar esto, puedes volver a instalar el módulo. Esto es más fácil
hecho a través de la línea de comandos usando el `-i`
506
507 Como de costumbre, no debemos olvidar agregar el nuevo archivo al
atributo de datos en el `__manifest__.py`:
508 ```
509 'data': ['views/todo_task.xml', 'security/todo_access_rules.xml'],
Page 14
capitulo-3.md 07/02/2018
510 ```
511
512 # Resumen
513
514 Ahora deberías ser capaz de crear tus propios módulos extendiendo los
existentes.
515
516 Para demostrar cómo hacerlo, ampliamos el módulo Tareas que creamos en
el capítulo anterior, añadiendo nuevas características a las varias
capas que componen una aplicación.
517
518 Hemos ampliado un modelo Odoo para agregar nuevos campos y ampliar sus
métodos de lógica de negocio. A continuación, modificamos las vistas
para poner a su disposición los nuevos campos. Finalmente, aprendimos a
heredar características de otros modelos y usarlas para agregar las
funciones de red social a la aplicación de tareas pendientes.
519
520 Los tres primeros capítulos nos dieron una visión general de las
actividades comunes involucradas en el desarrollo Odoo, desde la
instalación y configuración de Odoo hasta la creación y extensión de
módulos.
521
522 Los próximos capítulos se centrarán en un área específica del
desarrollo Odoo, la mayoría de los cuales visitamos brevemente en estos
primeros capítulos. En el capítulo siguiente, abordaremos la
serialización de datos y el uso de archivos de datos XML y CSV con más
detalle.
523
524
525
526
Page 15
capitulo-4.md 07/02/2018
50
51 Para hallar el identificador externo para elementos de vista, como
formulario, árbol, búsqueda o acción, el menú **Developer** también es
una buena fuente de ayuda. Para ello, podemos utilizar la opción
**Manage Views** o abrir la información para la vista deseada mediante
la opción **Edit <view type>**. A continuación, selecciona la opción
**View metadata**.
52
53 ## Exportando e importando data
54
55 Comenzaremos a explorar cómo exportar e importar datos desde la
interfaz de usuario de Odoo, ya partir de ahí, pasaremos a los detalles
más técnicos sobre cómo utilizar los archivos de datos en nuestros
módulos de complemento.
56
57 ### Exportando data
58
59 La exportación de datos es una característica estándar disponible en
cualquier vista de lista. Para usarlo, primero debemos seleccionar las
filas para exportar seleccionando las casillas de verificación
correspondientes en el extremo izquierdo y luego seleccionar la opción
**Export** del botón **More**.
60
61 He aquí un ejemplo, usando las tareas de tareas pendientes creadas
recientemente:
62
63 
64
65 También podemos marcar la casilla de verificación en el encabezado de
la columna. Comprobará todos los registros a la vez y exportará todos
los registros que coincidan con los criterios de búsqueda actuales.
66
67 ### Nota
68
69 En versiones anteriores de Odoo, sólo los registros vistos en la
pantalla (la página actual) se exportarían realmente. Desde Odoo 9,
esta casilla de verificación cambió y marcar el checkbox en el
encabezado exportará todos los registros que coincidan con el filtro
actual, no sólo los que se muestran actualmente. Esto es muy útil para
exportar grandes conjuntos de registros que no encajan en la pantalla.
70
71 La opción **Export** nos lleva a un formulario de diálogo, donde
podemos elegir qué exportar. La opción **Import-Compatible Export**
(Importar exportación compatible) asegura que el archivo exportado se
puede importar de nuevo a Odoo. Necesitaremos usar esto.
72
73 El formato de exportación puede ser CSV o Excel. Preferiremos un
archivo CSV para obtener una mejor comprensión del formato de
exportación. A continuación, seleccionamos las columnas que queremos
exportar y hacemos clic en el botón **Export to File**. Esto iniciará
la descarga de un archivo con los datos exportados:
74
75 
76
77 Si seguimos estas instrucciones y seleccionamos los campos mostrados en
la captura de pantalla anterior, deberíamos terminar con un archivo de
texto CSV similar a este:
78
79 ```
80 "id","name","user_id/id","date_deadline","is_done"
81 "todo_user.todo_task_a","Install
Page 3
capitulo-4.md 07/02/2018
Odoo","base.user_root","2015-01-30","False"
82 "__export__.todo_task_9","Create my first
module","base.user_root","","False"
83 ```
84
85
86 #### Nota
87
88 Observa que Odoo exportó automáticamente una columna de id adicional.
Este es un identificador externo asignado a cada registro. Si no se ha
asignado ninguna todavía, se genera automáticamente una nueva
utilizando `__export__` en lugar de un nombre de módulo real. Los
nuevos identificadores se asignan solamente a los registros que no
tienen ya uno, y de allí en adelante, se mantienen ligados al mismo
expediente. Esto significa que las exportaciones posteriores
preservarán los mismos identificadores externos.
89
90 ### Importando datos
91
92 Primero tenemos que asegurarnos de que la función de importación esté
habilitada. Ya que Odoo 9 está habilitado de forma predeterminada. Si
no, la opción está disponible en el menú principal **Settings**, opción
**General Settings**. Bajo la sección **Import | Export** hay una
casilla de verificación **Allow users to import data from
CSV/XLS/XLSX/ODS files** que debe ser habilitada.
93
94 #### Nota
95
96 Esta característica es proporcionada por el complemento **Initial Setup
Tools** (`base_setup` es el nombre técnico). El efecto real de la
casilla **Allow users to import**... es instalar o desinstalar
`base_setup`.
97
98 Con esta opción habilitada las vistas de lista muestran una opción
**Import** junto al botón **Create** en la parte superior de la lista.
99
100 Vamos a realizar una edición masiva en nuestros datos de tareas
pendientes primero. Abre el archivo CSV que acabamos de descargar en
una hoja de cálculo o un editor de texto y cambia algunos valores.
Además, agregua algunas nuevas filas, dejando la columna de ID en blanco.
101
102 Como se mencionó anteriormente, la primera columna, id, proporciona un
identificador único para cada fila. Esto permite actualizar los
registros ya existentes en vez de duplicarlos cuando importamos los
datos de nuevo a Odoo. Para las nuevas filas añadidas al archivo CSV,
podemos elegir entre proporcionar un identificador externo de nuestra
elección, o dejar en blanco la columna id, se creará un nuevo registro
para ellos.
103
104 Después de guardar los cambios en el archivo CSV, haz clic en la opción
**Import** (junto al botón **Create**) y se nos presentará el asistente
de importación.
105
106 Allí, debemos seleccionar la ubicación del archivo CSV en el disco y
hacer clic en **Validate** para comprobar su exactitud. Ya que que el
archivo a importar se basa en una exportación Odoo, lo más probable es
que sea válido:
107
108 
109
110 Ahora podemos hacer clic en **Import**, y ahí lo tienes; Nuestras
Page 4
capitulo-4.md 07/02/2018
258
259 El elemento `<field>` también tiene un atributo `ref` para establecer
el valor de un campo many-to-one, utilizando un identificador externo.
Con esto, podemos fijar el valor para `user_id` usando apenas esto:
260 ```
261
262 <Field name = "user_id" ref = "base.user_demo" />
263 ```
264
265 Para los campos one-to-many y many-to-many, se espera una lista de IDs
relacionados, por lo que se necesita una sintaxis diferente; Odoo
proporciona una sintaxis especial para escribir en este tipo de campos.
266
267 El siguiente ejemplo, tomado de la aplicación oficial Fleet, reemplaza
la lista de registros relacionados de un campo `tag_ids`:
268 ```
269 <field name="tag_ids"
270 eval="[(6,0,
271 [ref('vehicle_tag_leasing'),
272 ref('fleet.vehicle_tag_compact'),
273 ref('fleet.vehicle_tag_senior')]
274 )]" />
275
276 ```
277
278 Para escribir en un campo to-many, usamos una lista de triples. Cada
triple es un comando de escritura que hace cosas diferentes según el
código usado:
279
280 + (0,_ ,{'field': value}) crea un nuevo regristro y lo enlaza con este
281 + (1,id,{'field': value}) Actualiza los valores en un registro
previamente enlazado
282 + (2,id,_) Desenlaza y elimina un registro relacionado
283 + (3,id,_) Desenlaza pero no elimina un registro relacionado
284 + (4,id,_) Enlaza un registro ya existente
285 + (5,_,_) Desenlaza pero no elimina todos los registros relacionados
286 + (6,_,[ids]) reemplaza la lista de registros enlazados con la lista
proporcionada
287
288 El símbolo de subrayado utilizado en la lista anterior representa
valores irrelevantes, normalmente se rellenan con `0` o `False`.
289
290 ### Atajos para los modelos de uso frecuente
291
292 Si volvemos al Capítulo 2, *Construyendo tu Primera Aplicación Odoo*,
encontraremos elementos distintos de `<record>`, como `<act_window>` y
`<menuitem>`, en los archivos XML.
293
294 Estos son accesos directos convenientes para los modelos de uso
frecuente que también se pueden cargar utilizando elementos `<record>`
normales. Ellos cargan datos en modelos de base que soportan la
interfaz de usuario y se explorarán con más detalle más adelante,
específicamente en el Capítulo 6, *Vistas - Diseñando la interfaz de
usuario*.
295
296 Como referencia, los siguientes elementos de acceso directo están
disponibles con los modelos correspondientes en los que cargan datos:
297
298 + `<Act_window>` es el modelo de acción de la ventana,
`ir.actions.act_window`
299 + `<Menuitem>` es el modelo de elementos de menú, `ir.ui.menu`
Page 10
capitulo-4.md 07/02/2018
Page 12
capitulo-5.md 07/02/2018
67
68 Para que las tareas pendientes tengan un tablero kanban, necesitamos
Etapas. Las etapas son columnas del tablero, y cada tarea cabrá en una
de estas columnas:
69
70
71 1. Edita `todo_ui/__init__.py` para importar el submodulo `models`:
72
73 ```
74 from . import models
75 ```
76
77 1. Crea el directorio `todo_ui/models` y añádelo al archivo `an
__init__.py` con esto:
78 ```
79 from . import todo_model
80 ```
81 1. Ahora, añadamoslo al archivo de código Pyton
`todo_ui/models/todo_model.py`:
82 ```
83 # -*- coding: utf-8 -*-
84 from odoo import models, fields, api
85
86 class Tag(models.Model):
87 _name = 'todo.task.tag'
88 _description = 'To-do Tag'
89 name = fields.Char('Name', 40, translate=True)
90 class Stage(models.Model):
91 _name = 'todo.task.stage'
92 _description = 'To-do Stage'
93 _order = 'sequence,name'
94
95 name = fields.Char('Name', 40, translate=True)
96 sequence = fields.Integer('Sequence')
97 ```
98
99 Aquí hemos creado los dos nuevos modelos que serán referenciados en las
tareas pendientes.
100
101 Centrándonos en las etapas de la tarea, tenemos una clase Python,
Etapa, basada en la clase `models.Model`, que define un nuevo modelo
Odoo llamado `todo.task.stage`. También tenemos dos campos: `nombre y
secuencia`. Podemos ver algunos atributos de modelo (prefijados con un
subrayado) que son nuevos para nosotros. Echemos un vistazo a ellos.
102
103 ## Atributos del modelo
104
105 Las clases de modelo pueden utilizar atributos adicionales que
controlan algunos de sus comportamientos. Estos son los atributos más
utilizados:
106
107 + `_name` es el identificador interno del modelo Odoo que estamos
creando. Obligatorio cuando se crea un nuevo modelo.
108 + `_description` es un título fácil de usar para los registros del
modelo, que se muestra cuando se ve el modelo en la interfaz de
usuario. Opcional pero recomendado.
109 + `_order` establece el orden predeterminado para utilizar cuando se
exploran los registros del modelo o se muestran en una vista de lista.
Es una cadena de texto que se usará como cláusula `SQL order by`, por
lo que puede ser cualquier cosa que puedas utilizar allí, aunque tiene
un comportamiento inteligente y admite nombres de campo traducibles y
Page 3
capitulo-5.md 07/02/2018
muchos a uno.
110
111 Para completar, hay un par de más atributos que se pueden utilizar en
casos avanzados:
112
113 + `_rec_name` indica el campo a utilizar como la descripción del
registro cuando se hace referencia desde campos relacionados, tales
como una relación de varios a uno. De forma predeterminada, utiliza el
campo de `nombre`, que es un campo común en los modelos. Pero este
atributo nos permite usar cualquier otro campo para ese propósito.
114 + `_table` es el nombre de la tabla de la base de datos que soporta el
modelo. Por lo general, se deja que se calcule automáticamente, y es el
nombre del modelo con los puntos reemplazados por subrayados. Pero es
posible establecer para indicar un nombre de tabla específico.
115
116 También podemos tener los atributos `_inherit` y `_inherits`, como se
explicó en el capítulo 3, *Herencia - Extendiendo las aplicaciones
existentes.
117
118 ### Modelos y clases Python
119 Los modelos Odoo están representados por clases Python. En el código
anterior, tenemos una `Etapa` clase Python, basada en la clase
`models.Model`, que define un nuevo modelo Odoo llamado
`todo.task.stage`.
120
121 Los modelos Odoo se mantienen en un registro central, también conocido
como piscina en las versiones más antiguas de Odoo. Es un diccionario
que mantiene referencias a todas las clases de modelo disponibles en la
instancia, y puede ser referenciado por un nombre de modelo.
Específicamente, el código de un método de modelo puede usar `self.env
['x']` para obtener una referencia a una clase que representa el modelo
`x`.
122
123 Puedes ver que los nombres de los modelos son importantes ya que son
las claves utilizadas para acceder al registro. La convención para los
nombres de modelo es usar una lista de palabras en minúsculas unidas
con puntos, como `todo.task.stage`. Otros ejemplos de los módulos
principales son `project.project`, `project.task` o
`project.task.type`. Deberíamos utilizar el modelo singular `todo.task`
en lugar de `todo.task`. Por razones históricas, es posible encontrar
algunos modelos básicos que no siguen esto, como `res.users`, pero no
es la regla.
124
125 Los nombres de los modelos deben ser globalmente únicos. Debido a esto,
la primera palabra debe corresponder a la aplicación principal que
módulo se refiere. En nuestro ejemplo, es `todo`. Otros ejemplos de los
módulos principales son `project`, `crm` o `sale`.
126
127 Las clases de Python, por otro lado, son locales al archivo Python
donde se declaran. El identificador utilizado para ellos es sólo
significativo para el código en ese archivo. Debido a esto, no se
requiere que los identificadores de clase sean prefijados por la
aplicación principal con la que se relacionan. Por ejemplo, no hay
ningún problema para nombrar nuestra clase `Stage` para el modelo
`todo.task.stage`. No hay riesgo de colisión con las posibles clases
con el mismo nombre en otros módulos.
128
129 Se pueden utilizar dos convenciones diferentes para los identificadores
de clase: `snake_case` o `CamelCase`. Históricamente, el código de Odoo
usó el caso snake, y todavía es posible encontrar las clases que
utilizan esta convención. Pero la tendencia es utilizar el caso case,
Page 4
capitulo-5.md 07/02/2018
194
195 Estos son los argumentos de posición estándar esperados por cada uno de
los tipos de campo:
196
197 + `Char` espera un segundo tamaño de argumento opcional para el tamaño
máximo de texto. Se recomienda no usarlo a menos que exista un
requisito de negocio que lo requiera, como un número de seguro social
con una longitud fija.
198 + `Text`o difiere de `Char`, ya que puede albergar contenido de texto
multilínea, pero espera los mismos argumentos.
199 + `Selection` es una lista de selección desplegable. El primer
argumento es la lista de opciones seleccionables y el segundo es el
título de la cadena. El elemento de selección es una lista de tuplas
(`'value'`, `'Títle'`), para el valor almacenado en la base de datos y
la correspondiente descripción de interfaz de usuario. Cuando se
extiende a través de la herencia, el argumento `selection_add` está
disponible para añadir nuevos elementos a una lista de selección
existente.
200 + `Html` se almacena como un campo de texto, pero tiene un manejo
específico en la interfaz de usuario, para la presentación de contenido
HTML. Por razones de seguridad, se desinfectan de forma predeterminada,
pero este comportamiento se puede sobreescribir.
201 + `Integer` sólo espera un argumento de cadena para el título del campo.
202 + `Float` tiene un segundo argumento opcional, una tupla (`x,y`) con la
precisión del campo: `x` es el número total de dígitos; De éstos, `y`
son dígitos decimales.
203 + Los campos `Date` y `Datetime` sólo esperan la cadena de texto como
un argumento de posicional. Por razones históricas, el ORM maneja sus
valores en un formato de cadena. Las funciones auxiliares se deben
utilizar para convertirlas en objetos de fecha real. También los
valores de fecha y hora se almacenan en la base de datos en tiempo UTC
pero presentadas en hora local, utilizando las preferencias de zona
horaria del usuario. Esto se discute con más detalle en el Capítulo 6,
*Vistas - Diseñando la interfaz de usuario*.
204 + `Boolean` tiene valores `True` o `False`, como puedes esperar, y sólo
tiene un argumento de posición para la cadena de texto.
205 + `Binary` almacena datos binarios de tipo archivo y también espera
sólo el argumento de cadena. Pueden ser manejados por código Python
usando cadenas codificadas en `base64`.
206
207 Aparte de estos, también tenemos los campos relacionales, que serán
presentados más adelante en este capítulo. Pero ahora, todavía hay más
para aprender acerca de estos tipos de campo y sus atributos.
208
209 ### Atributos de campo comunes
210
211 Los campos tienen atributos que se pueden establecer al definirlos.
Dependiendo del tipo de campo, algunos atributos pueden ser pasados en
posición, sin una palabra clave de argumento, como se muestra en la
sección anterior.
212
213 Por ejemplo, `name=fields.Char('Name', 40)` podría hacer uso de
argumentos posicionales. Usando los argumentos de la palabra clave, lo
mismo se podría escribir como `name=fields.Char (size=40,
string='Name')`. Puedes encontrar más información sobre argumentos de
palabras clave en la documentación oficial de Python en
https://docs.python.org/2/tutorial/controlflow.html#keyword-arguments.
214
215 Todos los atributos disponibles se pueden pasar como un argumento de
palabra clave. Estos son los atributos generalmente disponibles y las
palabras clave de argumento correspondientes:
Page 7
capitulo-5.md 07/02/2018
216
217 + `string` es la etiqueta por defecto del campo, que se utilizará en la
interfaz de usuario. Excepto para los campos de selección y
relacionales, es el primer argumento posicional, por lo que la mayoría
de las veces no se utiliza como argumento de palabra clave.
218 + `default` establece un valor predeterminado para el campo. Puede ser
un valor estático, como una cadena o una referencia callable, ya sea
una función con nombre o una función anónima (una expresión lambda).
219 + `size` sólo se aplica a los campos `Char` y puede establecer un
tamaño máximo permitido. La mejor práctica actual es no usarla a menos
que sea realmente necesaria.
220 + `translate` se aplica sólo a los campos `Char`, `Text` y `Html`, y
hace que el contenido del campo se pueda traducir, manteniendo valores
diferentes para diferentes idiomas.
221 + `help` proporciona el texto para las sugerencias que se muestran a
los usuarios.
222 + `readonly=True` hace que el campo por defecto no sea editable por la
interfaz de usuario. Esto no se aplica a nivel API; Es sólo una
configuración de interfaz de usuario.
223 + `required=True` hace obligatorio el campo por defecto en la interfaz
de usuario. Esto se aplica en el nivel de base de datos mediante la
adición de una restricción `NOT NULL` en la columna.
224 + `index=True` creará un índice de base de datos en el campo.
225 + `copy=False` tiene el campo ignorado cuando se utiliza la función de
registro duplicado, método ORM `copy ()`. Los campos no relacionales
son `copyable` de forma predeterminada.
226 + `groups` permite limitar el acceso y la visibilidad del campo a sólo
algunos grupos. Espera una lista separada por comas de IDs XML para
grupos de seguridad, como `groups='base.group_user, base.group_system'`.
227
228 + `states` espera un diccionario que asigna valores para los atributos
UI que dependen de los valores del campo `satate`. Por ejemplo:
`states = {'done': [('readonly', True)]}`. Los atributos que se pueden
utilizar son `readonly`, `required` e `invisible`.
229
230 #### Nota
231
232 Ten en cuenta que el campo`satates` es equivalente al atributo `attrs`
en las vistas. Nota que las vistas admiten un atributo `states`, pero
tiene un uso diferente: acepta una lista de estados separados por comas
para controlar cuando el elemento debe ser visible.
233
234 Para completar, a veces se utilizan otros dos atributos cuando se
actualiza entre versiones Odoo principales:
235
236 + `deprecated=True` registra una advertencia cada vez que se utiliza el
campo.
237 + `oldname='field'` se utiliza cuando un campo se renombra en una
versión más reciente, permitiendo que los datos en el campo antiguo se
copien automáticamente en el nuevo campo.
238
239 ### Nombres de campos especiales
240
241 Algunos nombres de campo están reservados para ser utilizados por el ORM.
242
243 El campo `id` es un número automático que identifica de manera única
cada registro y se utiliza como la clave principal de la base de datos.
Se agrega automáticamente a cada modelo.
244
245 Los siguientes campos se crean automáticamente en los nuevos modelos, a
menos que se establezca el atributo `_log_access=False`:
Page 8
capitulo-5.md 07/02/2018
246
247 + `create_uid` es para el usuario que creó el registro
248 + `create_date` es la fecha y la hora en que se crea el registro
249 + `write_uid` es para que el último usuario modifique el registro
250 + `write_date` es la última fecha y hora en que se modificó el registro
251
252 Esta información está disponible desde el cliente web, navegando hasta
el menú **Developer Mode** y seleccionando la opción **View Metadata**.
253
254 Algunas características API incorporadas por defecto esperan nombres de
campos específicos. Debemos evitar el uso de estos nombres de campo
para propósitos diferentes a los que se pretenden. Algunos de ellos son
incluso reservado y no se puede utilizar para otros fines en absoluto:
255
256 + `name` se utiliza de forma predeterminada como el nombre para mostrar
para el registro. Normalmente es un campo de tipo `Char`, pero también
puede ser un `Text` o un `Many2one`. Todavía podemos establecer otro
campo para ser utilizado para el nombre de visualización, utilizando el
atributo del modelo `_rec_name`.
257 + `Active`, de tipo `Boolean`, permite inactivar registros. Los
registros con `active==False` se excluirán automáticamente de las
consultas. Para acceder a ellos debe añadirse una condición `('active',
'=', False)` al dominio de búsqueda, o `'active_test': False` Se debe
agregar al contexto actual.
258 + `Sequence`, de tipo `Integer`, si está presente en una vista de
lista, permite definir manualmente el orden de los registros. Para que
funcione correctamente, no debes olvidar usarlo con el atributo del
modelo `_order`.
259 + `State`, de tipo `Selection`, representa los estados básicos del
ciclo de vida del registro y puede ser utilizado por el atributo de
campo del estado para modificar dinámicamente la vista: algunos campos
de formulario se pueden `readonly` o `invisible` en estados de registro
específicos.
260 + `parent_id`, `parent_left` y `parent_right`, de tipo `Integer`,
tienen un significado especial para las relaciones jerárquicas
padre/hijo. Lo analizaremos en detalle en la siguiente sección.
261
262 Hasta ahora, hemos discutido campos no relacionales. Pero una buena
parte de una estructura de aplicación de datos es acerca de describir
las relaciones entre entidades. Ahora vamos a mirar esto.
263
264
265 ## Relaciones entre modelos
266 Mirando de nuevo el diseño de nuestro módulo, tenemos estas relaciones:
267
268 + Cada tarea tiene una etapa. Esa es una relación de muchos a uno,
también conocida como clave extranjera. La inversa es una relación
uno-a-muchos, lo que significa que cada etapa puede tener muchas tareas.
269 + Cada tarea puede tener muchas etiquetas. Esa es una relación de
muchos a muchos. La relación inversa, por supuesto, es también un mucho
a muchos, ya que cada etiqueta puede estar en muchas tareas.
270
271 El siguiente diagrama de relación de entidad puede ayudar a visualizar
las relaciones que estamos a punto de crear en el modelo. Las líneas
que terminan con un triángulo representan muchos lados de las relaciones:
272
273 
274
275 Añadamos los correspondientes campos de relación a las tareas
pendientes en nuestro archivo `todo_model.py`:
276
Page 9
capitulo-5.md 07/02/2018
277 ```
278 class TodoTask(models.Model):
279 _inherit = 'todo.task'
280 stage_id = fields.Many2one('todo.task.stage', 'Stage')
281 tag_ids = fields.Many2many('todo.task.tag', string='Tags')
282 ```
283
284 El código anterior muestra la sintaxis básica de estos campos,
estableciendo el modelo relacionado y el título del campo `string`. La
convención para los nombres de campos relacionales es añadir `_id` o
`_ids` a los nombres de campo, para a-una y a-muchas relaciones,
respectivamente.
285
286 Como ejercicio, puedes intentar también agregar las relaciones inversas
correspondientes a los modelos relacionados:
287
288 + La inversa de la relación `Many2one` es un campo `One2many` en las
etapas, ya que cada etapa puede tener muchas tareas. Deberíamos añadir
este campo a la clase Etapas.
289 + La inversa de la relación `Many2many` es también un campo `Many2many`
en Etiquetas, ya que cada etiqueta también se puede usar en muchas
Tareas.
290
291 Echemos un vistazo más de cerca a las definiciones de campos
relacionales.
292
293 ### Relaciones de muchos a uno (Many-to-one)
294
295 La relación `Many2one` acepta dos argumentos posicionales: el modelo
relacionado (correspondiente al argumento de la palabra clave
`comodel`) y el título `string`. Crea un campo en la tabla de base de
datos con una clave externa a la tabla relacionada.
296
297 Algunos argumentos con nombre adicionales también están disponibles
para utilizar con este tipo de campo:
298
299 + `ondelete` define lo que ocurre cuando se elimina el registro
relacionado. Su valor predeterminado es `set null`, lo que significa
que se establece un valor vacío cuando se elimina el registro
relacionado. Otros valores posibles son `restrict`, generando un error
que impida la eliminación y `cascade` también eliminar este registro.
300 + `context` es un diccionario de datos, significativo para las vistas
del cliente web, para llevar información al navegar a través de la
relación. Por ejemplo, para establecer vales predeterminados. Se
explicará mejor en el Capítulo 6, *Vistas - Diseñando la interfaz de
usuario*.
301 + `domain` es una expresión de dominio, una lista de tuplas, filtra los
registros disponibles para el campo de relación.
302 + `auto_join=True` permite al ORM utilizar combinaciones de SQL cuando
se realizan búsquedas utilizando esta relación. Si se usan, las reglas
de seguridad de acceso serán anuladas y el usuario podría tener acceso
a registros relacionados que las reglas de seguridad no permitirían,
pero las consultas SQL serán más eficientes y se ejecutarán más rápido.
303
304 ### Relaciones de muchos a muchos (Many-to-many)
305 La firma mínima `Many2many` acepta un argumento para el modelo
relacionado, y se recomienda proporcionar también el argumento
`strings` con el título del campo.
306
307 En el nivel de base de datos, no se agrega ninguna columna a las tablas
existentes. En su lugar, crea automáticamente una nueva tabla de
Page 10
capitulo-5.md 07/02/2018
relación que tiene sólo dos campos de ID con las claves externas para
las tablas relacionadas. El nombre de la tabla de relación y los
nombres del campo se generan automáticamente. El nombre de tabla de
relación es el nombre de ambas tablas unidos con un subrayado con
`_rel` añadido a él.
308
309 En algunas ocasiones podemos necesitar anular estos valores
predeterminados automáticos.
310
311 Uno de estos casos es cuando los modelos relacionados tienen nombres
largos, y el nombre de la tabla de relaciones generado automáticamente
es demasiado largo, superando el límite de 63 caracteres de PostgreSQL.
En estos casos, debemos elegir manualmente un nombre para la tabla de
relaciones, para que se ajuste al límite de tamaño de nombre de tabla.
312
313 Otro caso es cuando necesitamos una segunda relación muchos-a-muchos
entre los mismos modelos. En estos casos, necesitamos proporcionar
manualmente un nombre para la tabla de relaciones, para que no
colisione con el nombre de tabla que ya se está utilizando para la
primera relación.
314
315 Hay dos alternativas para anular manualmente estos valores: ya sea
utilizando argumentos posicionales o argumentos de palabra clave.
316
317 Utilizando argumentos posicionales para la definición de campo tenemos:
318
319 ```
320 # Task <-> Tag relation (positional args):
321 tag_ids = fields.Many2many(
322 'todo.task.tag', # related model
323 'todo_task_tag_rel', # relation table name
324 'task_id', # field for "this" record
325 'tag_id', # field for "other" record
326 string='Tags')
327 ```
328
329 #### Nota
330
331 Ten en cuenta que los argumentos adicionales son opcionales. Podríamos
simplemente establecer el nombre de la tabla de relaciones y dejar que
los nombres de campo usen los valores predeterminados automáticos.
332
333 En su lugar, podemos utilizar argumentos de palabras clave, que algunas
personas prefieren para la legibilidad:
334
335 ```
336 # Task <-> Tag relation (keyword args):
337 tag_ids = fields.Many2many(
338 comodel_name='todo.task.tag', # related model
339 relation='todo_task_tag_rel',# relation table name
340 column1='task_id', # field for "this" record
341 column2='tag_id', # field for "other" record
342 string='Tags')
343 ```
344
345 Al igual que los campos muchos a uno, los campos muchos-a-muchos
también admiten los atributos de palabras clave `domain` y `context`.
346
347 #### Nota
348
349 Actualmente hay una limitación en el diseño de ORM, con respecto a los
Page 11
capitulo-5.md 07/02/2018
398
399 tasks = fields.One2many(
400
401
402
403
404
405
406 'todo.task', # related model
407
408
409
410
411
412
413 'stage_id', # field for "this" on related model
414
415
416
417
418
419
420 'Tasks in this stage')
421
422
423 ```
424 El `One2many` acepta tres argumentos posicionales: el modelo
relacionado, el nombre del campo en ese modelo que hace referencia a
este registro y la cadena de título. Los dos primeros argumentos
posicionales corresponden a los argumentos de palabra clave
`comodel_name` y `inverse_name`.
425
426 Los parámetros de palabras clave adicionales disponibles son los mismos
que para `Many2one : context`, `domain`, `ondelete` (aquí actúa en el
lado **muchos** de la relación) y `auto_join`.
427
428 ### Relaciones jerárquicas
429
430 Las relaciones de árbol padre-hijo se representan usando una relación
`Many2one` con el mismo modelo, de modo que cada registro hace
referencia a su padre. Y la inversa `One2many` hace que sea fácil para
un padre mantener el seguimiento de sus hijos.
431
432 Odoo proporciona un soporte mejorado para estas estructuras de datos
jerárquicas, para una navegación más rápida a través de hermanos de
árbol y para una búsqueda más fácil usando el operador adicional de
expresiones de dominio `child_of`.
433
434 Para habilitar estas características necesitamos establecer la bandera
de atributo `_parent_store` y añadir al modelo los campos auxiliares:
`parent_left` y `parent_right`. Ten en cuenta que esta operación
adicional se produce en tiempo de almacenamiento y penalidades de
tiempo de ejecución, por lo que es mejor utilizarla cuando se espera
leer con más frecuencia que escribir, como en el caso de un árbol de
categorías.
435
436 Revisitando el modelo `Tags`, definido en el archivo `todo_model.py`,
debemos editarlo para que parezca lo siguiente:
437
438 ```
439 class Tags(models.Model):
Page 13
capitulo-5.md 07/02/2018
491 ```
492 # class TodoTask(models.Model):
493 refers_to = fields.Reference(
494 [('res.user', 'User'), ('res.partner', 'Partner')],
495 'Refers to')
496 ```
497
498 Como puedes ver, la definición de campo es similar a un campo de
selección, pero aquí la lista de selección contiene los modelos que se
pueden utilizar. En la interfaz de usuario, el usuario primero
seleccionará un modelo de la lista disponible av, y luego escogerá un
registro de ese modelo.
499
500 Esto puede llevarse a otro nivel de flexibilidad: existe una tabla de
configuración de **Modelos de Referenciables** que puede ser utilizada
en los campos de **Referencia**. Está disponible en el menú
**Settings|Technical|database Structure**. Al crear tal campo podemos
configurarlo para usar cualquier modelo registrado allí, con la ayuda
de la función `referenceable_models (` en el módulo
`odoo.addons.res.res_request`.
501
502 Utilizando la configuración de **Modelos Referenciables**, una versión
mejorada del campo `Refers to` luciría así:
503
504
505 ```
506
507 from odoo.addons.base.res.res_request import referenceable_models
508
509
510
511
512 # class TodoTask(models.Model):
513
514 refers_to = fields.Reference(
515
516
517
518
519
520
521 referenceable_models, 'Refers to')
522
523
524
525
526
527 ```
528
529 Ten en cuenta que en Odoo 9.0 esta función utiliza una ortografía
ligeramente diferente y todavía estaba utilizando la API antigua. Así
que en la versión 9.0, antes de usar el código mostrado antes, tenemos
que añadir algún código en la parte superior de nuestro archivo Python
para envolverlo para que utilice la nueva API:
530
531 ```
532 from openerp.addons.base.res import res_request
533 def referenceable_models(self):
534 return res_request.referencable_models(
535 self, self.env.cr, self.env.uid, context=self.env.context)
536 ```
Page 15
capitulo-5.md 07/02/2018
537
538 ## Campos computados
539
540 Los campos pueden tener valores calculados por una función, en lugar de
simplemente leer un valor almacenado en una base de datos. Un campo
computado se declara igual que un campo regular, pero tiene el
argumento de adicional `compute` que define la función utilizada para
calcularlo.
541
542 En la mayoría de los casos, los campos computados implican escribir
alguna lógica de negocio, por lo que desarrollaremos este tema más en
el Capítulo 7, *Aplicación Lógica de ORM - Soportando procesos de
negocios`. Seguiremos explicándolos aquí, pero mantendremos la lógica
de negocios lo más simple posible.
543
544 Vamos a trabajar en un ejemplo: Las etapas tienen un campo de plegado
`fold`. Vamos a añadir a las Tareas pendientes un campo computado con
la etiqueta **Folded?** para la Etapa correspondiente.
545
546 Debemos editar el modelo `TodoTask` en el archivo `todo_model.py` para
agregar lo siguiente:
547
548 ```
549 # class TodoTask(models.Model):
550 stage_fold = fields.Boolean(
551 'Stage Folded?',
552 compute='_compute_stage_fold')
553
554 @api.depends('stage_id.fold')
555 def _compute_stage_fold(self):
556 for task in self:
557 task.stage_fold = task.stage_id.fold
558 ```
559 El código anterior agrega un nuevo campo `stage_fold` y el método
`_compute_stage_fold` utilizado para computarlo. El nombre de la
función se pasó como una cadena, pero también se le permite pasar como
una referencia llamable (el identificador de función sin comillas). En
este caso, debemos asegurarnos de que la función esté definida en el
archivo Python antes de que lo sea el campo.
560
561 El decorador `@api.depends` es necesario cuando la computación depende
de otros campos, como suele ocurrir. Permite al servidor saber cuándo
volver a calcular los valores almacenados o en datos caché. Uno o más
nombres de campo se aceptan como argumentos y la notación de puntos se
puede utilizar para seguir relaciones de campo.
562
563 Se espera que la función de computación asigne un valor al campo o a
los campos a computar. Si no lo hace, se producirá un error. Dado que
`self` es un objeto de registro, nuestro cálculo aquí es simplemente
para obtener el campo **Folded?** utilizando `stage_id.fold`. El
resultado se logra asignando ese valor (escribiéndolo) al campo
computado, `stage_fold`.
564
565 No vamos a estar trabajando todavía en las vistas de este módulo, pero
puede hacer ahora una edición rápida en el formulario de tarea para
confirmar si el campo computado está funcionando como se esperaba:
usando el **Developer Mode** selecciona la opción **Edit View** y
agregua el archivo directamente en el formato XML. No te preocupes:
será reemplazado por la vista de módulo limpio en la próxima
actualización.
566
Page 16
capitulo-5.md 07/02/2018
648 Las restricciones de Python pueden usar una pieza de código arbitrario
para comprobar las condiciones. La función de verificación debe estar
decorada con `@api.constraints`, indicando la lista de campos
implicados en el chequeo. La validación se activa cuando cualquiera de
ellos se modifica y generará una excepción si la condición falla.
649
650 Por ejemplo, para validar que un nombre de tarea tiene al menos cinco
caracteres, podríamos agregar la siguiente restricción:
651
652 ```
653 rom odoo.exceptions import ValidationError
654 # class TodoTask(models.Model):
655 @api.constrains('name')
656 def _check_name_size(self):
657 for todo in self:
658 if len(todo.name) < 5:
659 raise ValidationError('Must have 5
660 chars!')
661 ```
662
663 ## Resumen
664 Pasamos por una explicación detallada de los modelos y los campos,
utilizandolos para ampliar la aplicación de tareas pendientes con
etiquetas y etapas en las tareas. Aprendiste cómo definir relaciones
entre modelos, incluyendo relaciones jerárquicas entre padres e hijos.
Finalmente, vimos ejemplos simples de campos computados y restricciones
usando código Python.
665
666 En el siguiente capítulo, trabajaremos en la interfaz de usuario para
estas características del modelo de backend, haciéndolas disponibles en
las vistas utilizadas para interactuar con la aplicación.
Page 19
capitulo-6.md 07/02/2018
activate default filters on the target view, using keys with the
default_ or default_search_ prefixes. Here are some examples:
157
158 To set the current user as a default value of the user_id field, we
will use the following:
159 En el lado del servidor, algunos valores de campo del conjunto de
registros pueden depender de la configuración local proporcionada por
el contexto. En particular, la clave `lang` afecta al valor de los
campos traducibles. El contexto también puede proporcionar señales para
el código del lado del servidor. Por ejemplo, la clave `active_test`
cuando se establece en `False` cambia el comportamiento del método de
ORM `search ()` para que no filtre los registros inactivos.
160
161 Un contexto inicial del cliente web se ve así:
162
163 ```
164 {'lang': 'en_US', 'tz': 'Europe/Brussels', 'uid': 1}
165
166 ```
167
168 Puedes ver la clave `lang` con el idioma del usuario, `tz` con la
información de zona horaria y `uid` con el ID de usuario actual.
169
170 Al abrir un formulario desde un enlace o un botón en una vista
anterior, se agrega una clave `active_id` al contexto, con el ID de
registro en el que estábamos ubicados, en el formulario de origen. En
el caso particular de las vistas de lista, tenemos una clave de
contexto `active_ids` que contiene una lista de los identificadores de
registro seleccionados en la lista anterior.
171
172 En el lado del cliente, el contexto se puede utilizar para establecer
valores predeterminados o activar filtros predeterminados en la vista
de destino, utilizando claves con los prefijos `default_` o
`default_search_`. Aquí hay unos ejemplos:
173
174 Para configurar el usuario actual como un valor predeterminado del
campo `user_id`, utilizaremos lo siguiente:
175
176 ```
177 {'
178 default
179
180
181
182 _user_id': uid}
183
184 To have a filter_my_tasks filter activated by default on the target
view, we will use this:
185
186 {'
187 default_search
188
189
190
191 _filter_my_tasks': 1}
192
193 ```
194
195 ### Expresiones de dominio
196
197 El **dominio** se utiliza para filtrar registros de datos. Utilizan una
Page 5
capitulo-6.md 07/02/2018
257
258 No es la ruta más utilizada, para ayudar a mantener nuestros ejemplos
tan legibles como sea posible, usaremos el enfoque de prioridad en
nuestros próximos ejemplos.
259
260 ### Vistas del documento de negocios
261
262 Las aplicaciones empresariales son a menudo sistemas de registro - para
productos en un almacén, facturas en un departamento de contabilidad, y
muchos más. La mayoría de los datos grabados se pueden representar como
un documento en papel. Para una mejor experiencia de usuario, las
vistas de formulario pueden imitar estos documentos en papel. Por
ejemplo, en nuestra aplicación, podríamos pensar en una Tarea pendiente
como algo que tiene un simple formulario de papel que rellenar.
Proporcionaremos una vista de formulario que siga este diseño.
263
264 Para agregar una vista XML con el esqueleto básico de una vista de
documento de negocios, debemos editar el archivo `views/
todo_views.xml` y añadirlo a la parte superior:
265
266 ```
267 <record id="view_form_todo_task_ui"
268 model="ir.ui.view">
269 <field name="model">todo.task</field>
270 <field name="priority">15</field>
271 <field name="arch" type="xml">
272 <form>
273
274 <header>
275
276
277
278
279 <!-- To add buttons and status widget -->
280
281 </header>
282
283
284
285
286
287 <sheet>
288
289
290
291
292 <!-- To add form content -->
293
294 </sheet>
295
296
297
298
299 <!-- Discuss widgets for history and
300 communication: -->
301
302 <div class="oe_chatter">
303
304
305
306
Page 8
capitulo-6.md 07/02/2018
339 Históricamente, las etapas se introdujeron más tarde que los estados.
Ambos han coexistido, pero la tendencia en el núcleo de Odoo es para
las etapas de reemplazar a los estados. Pero como se ve en la
explicación anterior, los estados todavía proporcionan algunas
características que las etapas no lo hacen.
340
341 Todavía es posible beneficiarse de lo mejor de ambos mundos, mapeando
las etapas en estados. Esto fue lo que hicimos en el capítulo anterior,
añadiendo un campo de estado en el modelo de etapas de tareas y
haciéndolo disponible también en los documentos de Tarea pendiente a
través de un campo computado, permitiendo el uso del atributo de campo
de estado.
342
343 En el archivo `views/todo_view.xml` ahora podemos expandir el
encabezado básico para agregar una barra de estado:
344
345 ```
346 <header>
347 <field name="state" invisible="True" />
348 <button name="do_toggle_done" type="object"
349 attrs="{'invisible':[('state','in',['draft'])]}"
350 string="Toggle Done"
351 class="oe_highlight" />
352 <field name="stage_id"
353 widget="statusbar"
354 clickable="True"
355 options="{'fold_field': 'fold'}" />
356 </header>
357 ```
358
359 Aquí añadimos `state` como un campo oculto. Necesitamos esto para
obligar al cliente a incluir también ese campo en las solicitudes de
datos enviadas al servidor. De lo contrario, no estará disponible para
su uso en expresiones.
360
361 #### Tip
362
363 Es importante recordar que cualquier campo que desees utilizar, en un
dominio o expresión `attrs`, debe cargarse en la vista, por lo que los
campos quedarán invisibles en cualquier momento que los necesite, pero
no es necesario que los usuarios los vean.
364
365 A continuación, se agrega un botón a la barra de estado, para permitir
al usuario cambiar el indicador de tarea realizada **Done**.
366
367 Los botones que aparecen en la barra de estado deben cambiarse en
función del lugar del ciclo de vida del documento actual.
368
369 Utilizamos el atributo `attrs` para ocultar el botón cuando el
documento está en estado `draft`. La condición para hacer esto utiliza
el campo `state`, no mostrado en el formulario, por lo que tuvimos que
añadirlo como un campo oculto.
370
371 Si tenemos un campo de selección `state`, podemos usar el atributo
`states`. En este caso lo hacemos, y el mismo efecto podría lograrse
usando `states="open, done"`. Aunque no es tan flexible como el
atributo `attrs`, es más conciso.
372
373 Estas características de visibilidad también se pueden utilizar en
otros elementos de vista, como campos. Los exploraremos con más detalle
más adelante en este capítulo.
Page 10
capitulo-6.md 07/02/2018
374
375 El atributo `clickable` permite al usuario cambiar la etapa del
documento haciendo clic en la barra de estado. Normalmente queremos
habilitar esto, pero también hay casos en los que no lo hacemos, como
cuando necesitamos más control sobre el flujo de trabajo, y requerimos
que los usuarios progresen a través de las etapas usando sólo los
botones de acción disponibles, para que éstos puedan realizar
validaciones antes de moverse entre etapas.
376
377 Cuando se utiliza un widget de barra de estado con etapas, podemos
tener ocultas las etapas raramente usadas en un grupo de Más etapas.
Para esto, el modelo de etapas debe tener una bandera para configurar
las que ocultar, usualmente llamado `fold`. Y el widget de `statusbar`
debe utilizar un atributo `options`, como se muestra en el código
anterior, para proporcionar ese nombre de campo a la opción `fold_field`.
378
379 Cuando se utiliza el widget de barra de estado con un campo de estado,
se puede lograr un efecto similar con el atributo `statusbar_visible`,
utilizado para enumerar estados que deben estar siempre visibles y
ocultar estados de excepción necesarios para casos menos comunes. Por
ejemplo:
380
381 ```
382 <field name="stage_id" widget="statusbar"
383 clickable="True"
384 statusbar_visible="draft,open" />
385 ```
386
387 #### La hoja
388
389 El lienzo de hoja es el área principal del formulario donde se colocan
los elementos de datos reales. Está diseñado para que parezca un
documento en papel real, y es común ver que los registros en Odoo se
conocen como **documents**.
390
391 Normalmente, una estructura de hoja de documento tendrá estas áreas:
392
393 + Un título de documento y un subtítulo en la parte superior.
394 + Un cuadro de botones en la esquina superior derecha.
395 + Otros campos de encabezado del documento.
396 + Un cuaderno para campos adicionales organizados en pestañas o
páginas. Las líneas de documento también irían aquí, generalmente en la
primera página del cuaderno.
397
398 Vamos a pasar por cada una de estas áreas.
399
400 #### Título y subtítulos
401
402 Los campos fuera de un elemento `<group>` no tienen automáticamente
etiquetas renderizadas para ellos. Este será el caso de los elementos
de título, por lo que el elemento `<label for "..." />` debe ser
utilizado para procesarla. A expensas de un trabajo extra, esto tiene
la ventaja de dar más control sobre la pantalla de etiquetas.
403
404 El HTML regular, incluyendo elementos del estilo CSS, también se puede
utilizar para hacer brillar el título. Para obtener mejores resultados,
el título debe estar dentro de un `<div>` con la clase `oe_title`.
405
406 Aquí está el elemento `<sheet>` expandido para incluir el título además
de algunos campos adicionales como subtítulos:
407
Page 11
capitulo-6.md 07/02/2018
408 ```
409 <sheet>
410 <div class="oe_title">
411 <label for="name" class="oe_edit_only"/>
412 <h1><field name="name"/></h1>
413 <h3>
414 <span class="oe_read_only>By</span>
415 <label for="user_id" class="oe_edit_only"/>
416 <field name="user_id" class="oe_inline" />
417 </h3>
418 </div>
419 <!-- More elements will be added from here... -->
420 </sheet>
421 ```
422 Aquí podemos ver que usamos elementos HTML normales, como `div`,
`span`, `h1` y `h2`. El elemento `<label>` nos permite controlar cuándo
y dónde se mostrará. El atributo `for` identifica el campo del que
deberíamos obtener el texto de la etiqueta. Otra posibilidad es
utilizar el atributo de `string` para proporcionar un texto específico
para utilizar en la etiqueta. Nuestro ejemplo también utiliza el
atributo `class="oe_edit_only"` para que sea visible sólo en modo de
edición.
423
424 En algunos casos, como Socios o Productos, se muestra una imagen
representativa en la esquina superior izquierda. Suponiendo que
teníamos un campo binario `my_image`, podríamos añadir antes de la
línea `<div class="oe_title">`, usando:
425
426 ```
427 <field name="my_image" widget="image" class="oe_avatar"/>
428 ```
429
430 #### Área de botones inteligentes
431
432 El área superior derecha puede tener una caja invisible donde se pueden
colocar los botones. La versión 8.0 introdujo botones inteligentes,
mostrados como rectángulos con un indicador estadístico que se puede
seguir cuando se hace clic.
433
434 Podemos agregar la caja de botones justo después del final del DIV
`oe_title`, con lo siguiente:
435 ```
436 <div name="buttons" class="oe_right oe_button_box">
437 <!-- Smart buttons here … -->
438 </div>
439 ```
440
441 El contenedor de los botones es un `div` con la clase `oe_button_box` y
también `oe_right`, para alinearlo al lado derecho del formulario.
Estaremos discutiendo los botones con más detalle en una sección
posterior, así que esperaremos hasta entonces para agregar botones
reales en esta caja.
442
443 #### Agrupando contenido en un formulario
444
445 El contenido principal del formulario debe organizarse mediante
etiquetas `<group>`. La etiqueta `group` inserta dos columnas en el
lienzo y, en su interior, por defecto, los campos se mostrarán con
etiquetas.
446
447 Un valor de campo y una etiqueta de campo toman dos columnas, por lo
Page 12
capitulo-6.md 07/02/2018
565
566 + `email` se utiliza para hacer que el texto del correo electrónico sea
una dirección de correo electrónico que se pueda ejecutar.
567 + `url` se utiliza para formatear el texto como una URL a la que se
puede hacer clic.
568 + `html` se utiliza para representar el texto como contenido HTML; en
el modo de edición, cuenta con un editor WYSIWYG para permitir el
formato del contenido sin necesidad de utilizar la sintaxis HTML.
569
570 Para los campos numéricos, tenemos los siguientes widgets:
571
572 + `handle` está diseñado específicamente para campos de secuencia en
vistas de lista y muestra un identificador que le permite arrastrar
líneas a una orden personalizada.
573 + `float_time` formatea un campo flotante con cantidades de tiempo como
horas y minutos.
574 + `monetary`muestra un campo flotante como el monto de la moneda.
Espera un campo asociado `currency_id`, pero otro nombre de campo puede
ser proporcionado con `options="{'currency_field': 'currency_id'}"`.
575 + `progressbar` presenta un flotante como un porcentaje de progreso y
puede ser útil para los campos que representan una tasa de finalización.
576
577 Para campos relacionales y de selección, tenemos estos widgets
adicionales:
578
579 + `many2many_tags` muestra los valores como una lista de etiquetas tipo
botón.
580 + `seleccion` utiliza el widget de campo `selection` para un campo de
varios a uno.
581 + `radio` muestra las opciones del campo `selection` utilizando los
botones de opción.
582 + `kanban_state_selection` muestra una luz de semáforo para la lista de
selección de estado kanban. El estado normal se representa en gris,
hecho se representa en verde, y cualquier otro estado se representa en
rojo.
583 + `priority` representa el campo de selección como una lista de
estrellas seleccionables. Las opciones de selección suelen ser un
dígito numérico.
584
585 ### Botones
586
587 Los botones admiten estos atributos:
588
589 + `icon` es para la imagen del icono a utilizar en el botón para
mostrar; A diferencia de los botones inteligentes, los iconos
disponibles para los botones normales están limitados a los disponibles
en `addons/web/static/src/img/icons`.
590 + `string` es la etiqueta del texto del botón o el texto HTML `alt`
cuando se utiliza un icono.
591 + `type` es el error tipográfico de la acción a realizar. Los valores
posibles son:
592 - `workflow` se utiliza para activar una señal de motor de flujo de
trabajo;
593 - `object` se utiliza para llamar a un método Python;
594 - `action` se utiliza para ejecutar una acción de ventana.
595 + `name` identifica la acción específica que se debe realizar, según el
tipo elegido: un nombre de señal de flujo de trabajo, un nombre de
método de modelo o el ID de base de datos de la acción de ventana que
se va a ejecutar. La fórmula% (xmlid) d se puede utilizar para traducir
el ID XML en el ID de base de datos requerido.
596 + `args` gs se utiliza cuando el tipo es objeto, para pasar parámetros
Page 16
capitulo-6.md 07/02/2018
adicionales al método.
597 + `context` añade valores al contexto, que pueden tener efectos después
de ejecutar la acción de windows o en los métodos de código Python
llamados.
598 + `confirm` muestra un cuadro de mensaje de confirmación, con el texto
asignado a este atributo.
599 + `pecial="cancel"` se utiliza en los asistentes, para cancelar y
cerrar el formulario del asistente.
600
601 ### Botones inteligentes
602
603 Al diseñar la estructura del formulario, incluimos un área superior
derecha para contener botones inteligentes. Ahora vamos a añadir un
botón dentro de él.
604
605 Para nuestra aplicación, tendremos un botón que muestra el número total
de tareas pendientes para el propietario de la tarea actual, y al hacer
clic en ella, navegaremos hasta la lista de esos elementos.
606
607 Primero debemos añadir el correspondiente campo computado a
`models/todo_model.py`. Agregue a la clase `TodoTask` con lo siguiente:
608
609 ```
610 def compute_user_todo_count(self):
611 for task in self:
612 task.user_todo_count = task.search_count(
613 [('user_id', '=', task.user_id.id)])
614
615 user_todo_count = fields.Integer(
616 'User To-Do Count',
617 compute='compute_user_todo_count')
618
619 ```
620 A continuación, agregamos la caja del botón y el botón dentro de ella.
Justo después de terminar el DIV `oe_title`, reemplaza el marcador de
posición de cuadro de botones que agregamos antes, con lo siguiente:
621
622 ```
623 <div name="buttons" class="oe_right oe_button_box">
624 <button class="oe_stat_button"
625 type="action" icon="fa-tasks"
626 name="%(action_todo_task_button)d"
627 context="{'default_user_id': user_id}"
628 help="All to-dos for this user" >
629 <field string="To-Dos" name="user_todo_count"
630 widget="statinfo"/>
631 </button>
632 </div>
633 ```
634
635 Este botón muestra el número total de Tareas pendientes para la persona
responsable de esta tarea, computada por el campo `user_todo_count`.
636
637 Estos son los atributos que podemos usar al agregar botones inteligentes:
638
639 + `class="oe_stat_button"` representa un rectángulo en lugar de un
botón regular.
640 + `icon` establece el icono a utilizar, elegido en el conjunto de
fuentes Awesome. Los iconos disponibles se pueden consultar en
http://fontawesome.io.
641 + `type` y `name` son el tipo de botón y el nombre de la acción a
Page 17
capitulo-6.md 07/02/2018
701 model="ir.ui.view">
702 <field name="model">todo.task</field>
703 <field name="arch" type="xml">
704 <tree decoration-muted="is_done"
705 decoration-bf="state=='open'"
706 delete="false">
707 <field name="name"/>
708 <field name="user_id"/>
709 <field name="is_done"/>
710 <field name="state" invisible="1"/>
711 </tree>
712 </field>
713 </record>
714 ```
715
716 El color y la fuente del texto de fila pueden cambiar dinámicamente
dependiendo de los resultados de una evaluación de expresión de Python.
Esto se hace a través de los atributos `decoration-NAME`, con la
expresión para evaluar basada en atributos de campo. La parte `NAME`
puede ser `bf` o `it`, para fuentes en negrita y cursiva, o cualquier
color contextual de texto Bootstrap: `peligro, información, silenciado,
primario, éxito` o `advertencia`. La documentación Bootstrap contiene
ejemplos sobre cómo se presentan:
http://getbootstrap.com/css/#helper-classes-colors.
717
718 #### Tip
719
720 Los atributos `colors` y `fonts`, disponibles en la versión 8.0,
estaban obsoletos en la versión 9.0. Los nuevos atributos de decoración
deben ser utilizados en su lugar.
721
722 Recuerde que los campos utilizados en las expresiones deben declararse
en un elemento `<field>`, de modo que el cliente web sepa que esa
columna debe recuperarse del servidor. Si no queremos que se muestre al
usuario, debemos usar el atributo `invisible="1"` en él.
723
724 Otros atributos relevantes del elemento árbol son:
725
726 + `default_order` permite anular el orden de clasificación
predeterminado del modelo y su valor sigue el mismo formato que en el
atributo de orden utilizado en las definiciones de modelo.
727 + `create, delete` y `edit`, si se establece en `false` (en minúsculas)
deshabilita la acción correspondiente en la vista de lista.
728 + `editable` hace que los registros sean editables directamente en la
vista de lista. Los valores posibles son `top` y low`, la ubicación
donde se añadirán los nuevos registros.
729
730 Una vista de lista puede contener campos y botones, y la mayoría de sus
atributos para formularios también son válidos aquí.
731
732 En las vistas de lista, los campos numéricos pueden mostrar valores de
resumen para su columna. Para ello, agregue al campo uno de los
atributos de agregación disponibles, `sum`, `avg`, `min` o `max` y
asigne el texto de etiqueta para el valor de resumen. Por ejemplo:
733
734 ```
735 <field name="amount" sum="Total Amount" />
736 ```
737
738 ## Vistas de Búsqueda
739
Page 20
capitulo-6.md 07/02/2018
822 ```
823
824 También debe añadirse al formulario Tarea pendiente, de modo que
podamos añadir valores a los registros existentes y poder comprobar
esta nueva vista.
825
826 Ahora vamos a añadir la vista gráfica de las Tareas pendientes:
827
828 ```
829 <record id="view_graph_todo_task" model="ir.ui.view">
830 <field name="model">todo.task</field>
831 <field name="arch" type="xml">
832 <graph type="bar">
833 <field name="stage_id" />
834 <field name="effort_estimate" type="measure" />
835 </graph>
836 </field>
837 </record>
838 ```
839
840 El elemento de vista `graph` puede tener un atributo `type` que puede
establecerse en `bar` (el valor predeterminado),` pie o `line`. En el
caso de `bar`, el adicional `stacked="True"` se puede utilizar para
hacer un gráfico de barras apiladas.
841
842 Los datos también se pueden ver en una tabla dinámica, una matriz de
análisis dinámico. Para ello, tenemos la vista pivote, introducida en
la versión 9.0. Las tablas pivote ya estaban disponibles en la versión
8.0, pero en 9.0, se movieron a su propio tipo de vista. Junto con
esto, mejoró las características de interfaz de usuario de tablas de
pivote, y optimizó la recuperación de datos de tabla dinámica en gran
medida.
843
844 Para agregar también una tabla dinámica a las Tareas pendientes,
utilice este código:
845
846 ```
847 <record id="view_pivot_todo_task" model="ir.ui.view">
848 <field name="arch" type="xml">
849 <pivot>
850 <field name="stage_id" type="col" />
851 <field name="user_id" />
852 <field name="date_deadline" interval="week" />
853 <field name="effort_estimate" type="measure" />
854 </pivot>
855 </field>
856 </record>
857 ```
858 Las vistas de gráfico y pivote deben contener elementos de campo que
describen el eje y las medidas a utilizar. La mayoría de los atributos
disponibles son comunes a ambos tipos de vista:
859
860 + `name` identifica el campo que se va a utilizar en el gráfico, al
igual que en otras vistas
861 + `type` es cómo se usará el campo, como un grupo `row`
(predeterminado), un `measure` o como `col` (sólo para tablas
dinámicas, úsalo para grupos de columnas)
862 + `interval` es significativo para los campos de fecha y es el
intervalo de tiempo utilizado para agrupar datos de tiempo por `day`,
`week`, `month`, `quarter` o `año`.
863
Page 23
capitulo-6.md 07/02/2018
Page 24
capitulo-7.md 07/02/2018
36 ```
37
38 A continuación, necesitamos describir el modelo de datos que soporta
nuestro asistente.
39
40 ### El modelo del asistente
41
42 Un asistente muestra una vista de formulario para el usuario,
normalmente como una ventana de diálogo, con algunos campos que se
rellenan. Estos serán utilizados por la lógica del asistente.
43
44 Esto se implementa utilizando la misma arquitectura de modelo/vista que
para vistas regulares, pero el modelo de soporte se basa en
`models.TransientModel` en lugar de `models.Model`.
45
46 Este tipo de modelo también tiene una representación de base de datos y
almacena el estado allí, pero se espera que estos datos sean útiles
sólo hasta que el asistente termine su trabajo. Un trabajo programado
limpia regularmente los datos antiguos de las tablas de la base de
datos del asistente.
47
48 El archivo `models/todo_wizard_model.py` definirá los campos que
necesitamos para interactuar con el usuario: la lista de tareas a
actualizar, el usuario responsable y la fecha límite para establecerlas.
49
50 Primero agregua el archivo`models/__init__.py` con la siguiente línea
de código:
51
52 ```
53 from . import todo_wizard_model
54
55 ```
56
57 Luego, crea el archivo actual `models/todo_wizard_model.py`:
58
59 ```
60 # -*- coding: utf-8 -*-
61 from odoo import models, fields, api
62
63 class TodoWizard(models.TransientModel):
64 _name = 'todo.wizard'
65 _description = 'To-do Mass Assignment'
66 task_ids = fields.Many2many('todo.task',
67 string='Tasks')
68 new_deadline = fields.Date('Deadline to Set')
69 new_user_id = fields.Many2one(
70 'res.users',string='Responsible to Set')
71
72 ```
73
74 Vale la pena señalar que las relaciones uno-a-muchos con los modelos
regulares no deben usarse en modelos transitorios. La razón de esto es
que requeriría que el modelo regular tuviera la relación inversa de
muchos a uno con el modelo transitorio, pero esto no está permitido, ya
que podría haber la necesidad de recolectar basura a los registros de
modelos regulares junto con el modelo Registros transitorios.
75
76 ### El formulario de asistente
77
78 Las vistas del formulario del asistente son las mismas que para los
modelos regulares, excepto en dos elementos específicos:
Page 2
capitulo-7.md 07/02/2018
79
80 + Se puede utilizar una sección `<footer>` para colocar los botones de
acción
81 + Un botón especial `type= "cancel"` disponible para interrumpir el
asistente sin realizar ninguna acción
82
83 Este es el contenido de nuestro archivo `views/todo_wizard_view.xml`:
84
85 ```
86 <odoo>
87 <record id="To-do Task Wizard" model="ir.ui.view">
88 <field name="name">To-do Task Wizard</field>
89 <field name="model">todo.wizard</field>
90 <field name="arch" type="xml">
91
92 <form>
93 <div class="oe_right">
94 <button type="object" name="do_count_tasks"
95 string="Count" />
96 <button type="object" name="do_populate_tasks"
97 string="Get All" />
98 </div>
99
100 <field name="task_ids">
101 <tree>
102 <field name="name" />
103 <field name="user_id" />
104 <field name="date_deadline" />
105 </tree>
106 </field>
107
108 <group>
109 <group> <field name="new_user_id" /> </group>
110 <group> <field name="new_deadline" /> </group>
111 </group>
112
113 <footer>
114 <button type="object" name="do_mass_update"
115 string="Mass Update" class="oe_highlight"
116 attrs="{'invisible':
117 [('new_deadline','=',False),
118 ('new_user_id', '=',False)]
119 }" />
120 <button special="cancel" string="Cancel"/>
121 </footer>
122 </form>
123 </field>
124 </record>
125
126 <!-- More button Action -->
127 <act_window id="todo_app.action_todo_wizard"
128 name="To-Do Tasks Wizard"
129 src_model="todo.task" res_model="todo.wizard"
130 view_mode="form" target="new" multi="True" />
131 </odoo>
132
133 ```
134 La acción de ventana `<act_window>` que vemos en el XML añade una
opción al botón **Más "More"** del formulario Tarea pendiente mediante
el atributo `src_model`. El atributo `target="new"` lo hace abrir como
una ventana de diálogo.
Page 3
capitulo-7.md 07/02/2018
135
136 También puedes haber notado que `attrs` se utiliza en el botón de
**actualización masiva "Mass Update"**, para añadir el toque agradable
de hacerlo invisible hasta que se seleccione un nuevo plazo o un
usuario responsable.
137
138 ### La lógica empresarial del asistente
139
140 A continuación, tenemos que implementar las acciones a realizar en los
botones de formulario. Excluyendo el botón **Cancelar "Cancel"**,
tenemos tres botones de acción para implementar, pero ahora nos
enfocaremos en el botón de **actualización masiva "Mass Update"**.
141
142 El método llamado por el botón es `do_mass_update` y debe definirse en
el archivo `models/todo_wizard_model.py`, como se muestra en el código
siguiente:
143
144 ```
145 from odoo import exceptions
146 import logging
147 _logger = logging.getLogger(__name__)
148
149 # ...
150 # class TodoWizard(models.TransientModel):
151 # ...
152
153 @api.multi
154 def do_mass_update(self):
155 self.ensure_one()
156 if not (self.new_deadline or self.new_user_id):
157 raise exceptions.ValidationError('No data to update!')
158 _logger.debug('Mass update on Todo Tasks %s',
159 self.task_ids.ids)
160 vals = {}
161 if self.new_deadline:
162 vals['date_deadline'] = self.new_deadline
163 if self.new_user_id:
164 vals['user_id'] = self.new_user_id
165 # Mass write values on all selected tasks
166 if vals:
167 self.task_ids.write(vals)
168 return True
169
170 ```
171
172 Nuestro código debe manejar una instancia de asistente a la vez, por lo
que usamos `self.ensure_one ()` para que quede claro. Aquí `self`
representa el registro de exploración para los datos en el formulario
del asistente.
173
174 El método comienza validando si se ha dado una nueva fecha límite o un
usuario responsable, y si no se produce un error. A continuación,
tenemos un ejemplo de cómo escribir un mensaje de depuración en el
registro del servidor.
175
176 A continuación, el diccionario `vals` se construye con los valores a
establecer con la actualización masiva: la nueva fecha, nuevo
responsable o ambas. A continuación, el método de escritura `write` se
utiliza en un conjunto de registros para realizar la actualización
masiva. Esto es más eficiente que un bucle que realiza escrituras
individuales en cada registro.
Page 4
capitulo-7.md 07/02/2018
177
178 Es una buena práctica para los métodos de siempre devolver algo. Es por
eso que devuelve el valor `True` al final. La única razón de esto es
que el protocolo XML-RPC no admite valores `None`, por lo que esos
métodos no se podrán utilizar mediante ese protocolo. En la práctica,
es posible que no conozcas el problema porque el cliente web utiliza
JSON-RPC, no XML-RPC, pero sigue siendo una buena práctica.
179
180 A continuación, vamos a tener una mirada más cercana al registro, y
luego trabajaremos en la lógica detrás de los dos botones en la parte
superior: Contar y obtener todo.
181
182 ### Logging
183
184 Estas actualizaciones masivas podrían ser mal utilizadas, por lo que
podría ser una buena idea registrar alguna información cuando se
utiliza. El código anterior inicializa el `_logger` en las dos líneas
antes de la clase `TodoWizard`, utilizando la biblioteca estándar de
registro de Python. La variable interna Python `__name__` es para
identificar los mensajes como procedentes de este módulo.
185
186 Para escribir mensajes de registro en el código del método podemos usar:
187
188 ```
189 _logger.debug('A DEBUG message')
190 _logger.info('An INFO message')
191 _logger.warning('A WARNING message')
192 _logger.error('An ERROR message')
193 ```
194
195 Al pasar valores para usar en el mensaje de registro, en lugar de usar
interpolación de cadena, deberíamos proporcionarlos como parámetros
adicionales. Por ejemplo, en lugar de `_logger.info('Hello% s' %
'World')` deberíamos usar `_logger.info('Hello% s', 'World')`. Puedes
notar que lo hicimos en el método `do_mass_update ()`.
196
197 #### Nota
198
199 Una cosa interesante a notar sobre el registro, es que las entradas del
registro imprimen siempre la marca de tiempo en UTC. Esto puede ser una
sorpresa para los nuevos administradores, pero se debe al hecho de que
el servidor maneja internamente todas las fechas en UTC.
200
201
202 ### Aumentando las excepciones
203
204 Cuando algo no está bien, querremos interrumpir el programa con un
mensaje de error. Esto se hace levantando una excepción. Odoo ofrece
algunas clases de excepción adicionales a las disponibles en Python.
Estos son ejemplos para los más útiles:
205
206 ```
207 from odoo import exceptions
208 raise exceptions.Warning('Warning message')
209 raise exceptions.ValidationError('Not valid message')
210 ```
211
212 El mensaje de advertencia `Warning` también interrumpe la ejecución,
pero puede sonar menos grave que un mensaje `ValidationError`. Aunque
no es la mejor interfaz de usuario, aprovechamos eso en el botón de
conteo **Count** para mostrar un mensaje al usuario:
Page 5
capitulo-7.md 07/02/2018
213
214 ```
215 @api.multi
216 def do_count_tasks(self):
217 Task = self.env['todo.task']
218 count = Task.search_count([('is done', '=', False)])
219 raise exceptions.Warning(
220 'There are %d active tasks.' %count)
221 ```
222
223 Como una nota de lado, parece que podríamos haber utilizado el
decorador `@api.model`, ya que este método no funciona en el conjunto
de registros `self`. Pero en este caso no podemos porque el método
tiene que ser llamado desde un botón.
224
225 ### Acciones de ayuda en asistentes
226
227 Ahora supongamos que queremos un botón para recoger automáticamente
todas las tareas pendientes para evitar que el usuario los escoge uno
por uno. Ese es el punto de tener el botón **Obtener Todo "Get All"**
en el formulario. El código detrás de este botón obtendrá un conjunto
de registros con todas las tareas activas y lo asignará a las tareas
del campo muchos-a-muchos.
228
229 Pero hay una trampa aquí. En las ventanas de diálogo, cuando se pulsa
un botón, la ventana del asistente se cierra automáticamente. No
enfrentamos este problema con el botón **Count** porque utiliza una
excepción para mostrar su mensaje; Por lo que la acción no tiene éxito
y la ventana no está cerrada.
230
231 Afortunadamente, podemos evitar este comportamiento pidiendo al cliente
que vuelva a abrir el mismo asistente. Los métodos de modelo pueden
devolver una acción de ventana que debe realizar el cliente web, en
forma de un objeto de diccionario. Este diccionario utiliza los mismos
atributos que se utilizan para definir acciones de ventana en archivos
XML.
232
233 Vamos a definir una función de ayuda para el diccionario de acción de
la ventana para reabrir la ventana del asistente, para que pueda
reutilizarse fácilmente en varios botones:
234
235 ```
236 @api.multi
237 def _reopen_form(self):
238 self.ensure_one()
239 return {
240 'type': 'ir.actions.act_window',
241 'res_model': self._name, # this model
242 'res_id': self.id, # the current wizard record
243 'view_type': 'form',
244 'view_mode': 'form',
245 'target': 'new'}
246 ```
247
248 Vale la pena señalar que la acción de la ventana podría ser algo más,
como saltar a un formulario de asistente diferente para solicitar la
entrada de usuario adicional y que se puede utilizar para implementar
asistentes de varias páginas.
249
250 Ahora, el botón **Get All** puede hacer su trabajo y mantener al
usuario trabajando en el mismo asistente:
Page 6
capitulo-7.md 07/02/2018
251
252 ```
253 @api.multi
254 def do_populate_tasks(self):
255 self.ensure_one()
256 Task = self.env['todo.task']
257 open_tasks = Task.search([('is_done', '=', False)])
258 # Fill the wizard Task list with all tasks
259 self.task_ids = all_tasks
260 # reopen wizard form on same wizard record
261 return self._reopen_form()
262 ```
263
264 Aquí podemos ver cómo trabajar con cualquier otro modelo disponible:
primero usamos `self.env[]` para obtener una referencia al modelo,
`todo.task` en este caso, y luego podemos realizar acciones en él, como
`search()` para recuperar registros que cumplan algunos criterios de
búsqueda.
265
266 El modelo transitorio almacena los valores en los campos de formulario
del asistente y puede leerse o escribirse como cualquier otro modelo.
La variable `all_tasks` se asigna al campo uno-a-muchos `task_ids`.
Como puede ver, esto se hace como lo haríamos para cualquier otro tipo
de campo.
267
268 ## Trabajando con la API ORM
269
270 De la sección anterior, ya tenemos una idea de cómo es usar la API de
ORM. A continuación veremos qué más podemos hacer con él.
271
272 ### Decoradores de métodos
273
274 Durante nuestro viaje, los varios métodos que encontramos utilizaron
decoradores API como `@api.multi`. Estos son importantes para el
servidor para saber cómo manejar el método. Vamos a recapitular los
disponibles y cuando deben ser utilizados.
275
276 El decorador `@api.multi` se utiliza para manejar conjuntos de
registros con la nueva API y es el más utilizado. Aquí `self` es un
conjunto de registros, y el método normalmente incluirá un bucle `for`
para iterarlo.
277
278 En algunos casos, el método se escribe para esperar un singleton: un
conjunto de registros que no contenga más de un registro. El decorador
de `@api.one` fue descontinuado desde 9.0 y debe ser evitado. En su
lugar debemos seguir utilizando `@api.multi` y añadir al código del
método una línea con `self.ensure_one()`, para asegurar que es un
singleton.
279
280 Como se mencionó, el decorador `@api.one` es obsoleto, pero todavía es
compatible. Para completar, puede ser que valga la pena saber que
envuelve el método decorado, alimentándolo de un registro a la vez,
haciendo la iteración del conjunto de registros. En nuestro método
`self` está garantizado para ser un singleton. Los valores de retorno
de cada llamada de método individual se agregan como una lista y se
devuelven.
281
282 El `@api.model` decora un método estático de nivel de clase y no
utiliza datos de conjunto de registros. Para la consistencia,
`self`sigue siendo un conjunto de registros, pero su contenido es
irrelevante. Ten en cuenta que este tipo de método no se puede utilizar
Page 7
capitulo-7.md 07/02/2018
592
593 ```
594
595 ### Singletons
596
597 El caso especial de un conjunto de registros con sólo un registro se
llama un conjunto de registros **singleton**. Los singletons siguen
siendo un conjunto de registros y se pueden utilizar donde se espera un
conjunto de registros.
598
599 Pero a diferencia de los conjuntos de registros de elementos múltiples,
los singletons pueden acceder a sus campos usando la notación de
puntos, como se muestra aquí:
600
601 ```
602 >>> print self.name
603 Administrator
604 ```
605
606
607 En el siguiente ejemplo, podemos ver que el mismo conjunto de registros
`self` singleton también se comporta como un conjunto de registros, y
podemos iterarlo. Tiene sólo un registro, por lo que solo se imprime un
nombre:
608
609 ```
610 >>> for rec in self:
611 print rec.name
612 Administrator
613 ```
614
615 Intentar acceder a valores de campo en conjuntos de registros con más
de un registro generará error, por lo que este puede ser un problema en
los casos en los que no estamos seguros si estamos trabajando con un
conjunto de registros singleton. En los métodos diseñados para trabajar
sólo con singleton, podemos comprobar esto usando `self.ensure_one()`
al principio. Se planteará un error si `self` no es singleton.
616
617
618 #### Tip
619
620 Ten en cuenta que un registro vacío también es un singleton.
621
622 ### Escribir en los registros
623
624 Los conjuntos de registros implementan el patrón de registro activo.
Esto significa que podemos asignar valores a ellos, y estos cambios se
harán persistentes en la base de datos. Esta es una manera intuitiva y
conveniente de manipular datos, como se muestra aquí:
625
626 ```
627
628 >>> admin = self.env['res.users'].browse(1)
629
630
631
632
633
634 >>> print admin.name
635
636
Page 16
capitulo-7.md 07/02/2018
637
638
639
640 Administrator
641
642
643
644
645
646 >>> admin.name = 'Superuser'
647
648
649
650
651
652 >>> print admin.name
653
654
655
656
657
658 Superuser
659
660
661 ```
662
663 Los conjuntos de registros también tienen tres métodos para actuar en
sus datos: `create()`, `write()` y `unlink()`.
664
665 El método `create()` toma un diccionario para asignar los campos a los
valores y devuelve el registro creado. Los valores predeterminados se
aplican automáticamente como se espera, lo que se muestra aquí:
666
667 ```
668
669 >>> Partner = self.env['res.partner']
670
671
672
673
674
675 >>> new = Partner.create({'name': 'ACME', 'is_company': True})
676
677
678
679
680
681 >>> print new
682
683
684
685
686
687 res.partner(72,)
688
689
690 ```
691
692 El método `unlink()` borra los registros en los conjuntos de registros,
como se muestra aquí:
693
Page 17
capitulo-7.md 07/02/2018
694 ```
695
696 >>> rec = Partner.search([('name', '=', 'ACME')])
697
698
699
700
701
702 >>> rec.unlink()
703
704
705
706
707
708 True
709
710 ```
711
712 El método `write()` toma un diccionario para asignar campos a valores.
Éstos se actualizan en todos los elementos del conjunto de registros y
no se devuelve nada, como se muestra aquí:
713
714 ```
715 >>> Partner.write({'comment': 'Hello!'})
716 ```
717
718 El uso del patrón de registro activo tiene algunas limitaciones;
Actualiza sólo un campo a la vez. Por otro lado, el método `write()`
puede actualizar varios campos de varios registros al mismo tiempo
utilizando una sola instrucción de base de datos. Estas diferencias
deben tenerse en cuenta para los casos en que el rendimiento puede ser
un problema.
719
720 También vale la pena mencionar `copy()` para duplicar un registro
existente; Toma eso como un argumento opcional y un diccionario con los
valores para escribir en el nuevo registro. Por ejemplo, para crear una
nueva copia de usuario desde el usuario de demostración:
721
722 ```
723
724 >>> demo = self.env.ref('base.user_demo')
725
726
727
728
729
730 >>> new = demo.copy({'name': 'Daniel', 'login': 'dr', 'email':''})
731
732 ```
733
734 #### Nota
735
736 Recuerda que los campos con el atributo `copy=False` no se copiarán.
737
738
739 ### Trabajando con el tiempo y las fechas
740
741 Por razones históricas, los conjuntos de registros ORM controlan los
valores `date` y `datetime` utilizando sus representaciones de cadenas,
en lugar de los objetos `Date` y `Datetime` reales de Python. En la
base de datos se almacenan en los campos de fecha, pero las fechas se
Page 18
capitulo-7.md 07/02/2018
840
841 >>> print rs1
842
843
844
845
846
847 res.partner(8, 7, 19, 30, 3)
848
849
850
851
852
853 >>> rs2 = rs1.filtered('is_company')
854
855
856
857
858
859 >>> print rs2
860
861
862
863
864
865 res.partner(8, 7)
866
867
868
869
870
871 >>> rs2.mapped('name')
872
873
874
875
876
877 [u'Agrolait', u'ASUSTeK']
878
879
880
881
882
883 >>> rs2.mapped(lambda r: (r.id, r.name))
884
885
886
887
888
889 [(8, u'Agrolait'), (7, u'ASUSTeK')]
890
891
892
893
894
895 >> rs2.sorted(key=lambda r: r.id, reverse=True)
896
897
898
899
900
Page 21
capitulo-7.md 07/02/2018
901 res.partner(8, 7)
902
903 ```
904
905 ### Manipulando conjuntos de registros
906
907 Seguramente queremos añadir, eliminar o reemplazar los elementos en
estos campos relacionados, y esto nos lleva a la pregunta: ¿cómo se
pueden manipular los conjuntos de registros?
908
909 Los conjuntos de registros son inmutables, lo que significa que sus
valores no pueden modificarse directamente. En su lugar, modificar un
conjunto de registros significa componer un nuevo conjunto de registros
basado en los existentes.
910
911 Una forma de hacerlo es utilizando las operaciones de conjunto
compatibles:
912
913 + `rs1 | rs2` es la operación de conjunto de **unión** y da como
resultado un conjunto de registros con todos los elementos de ambos
conjuntos de registros.
914 + `rs1 + rs2` es la operación de conjunto de **adiciones**, para
concatenar ambos conjuntos de registros en uno. Puede resultar en un
conjunto con registros duplicados.
915 + `rs1 & rs2` es la operación de conjunto de **intersección** y resulta
en un conjunto de registros con sólo los elementos presentes en ambos
conjuntos de registros.
916 + `rs1 - rs2` es la operación de conjunto de **diferencias** y resulta
en un conjunto de registros con los elementos rs1 no presentes en rs2
917
918 La notación de corte también se puede utilizar, como se muestra en
estos ejemplos:
919
920 + `rs[0]` y `rs[-1]` recuperan el primer elemento y el último elemento,
respectivamente.
921 + `rs[1:]` da como resultado una copia del conjunto de registros sin el
primer elemento. Esto produce los mismos registros que `rs - rs[0]`
pero conserva su orden.
922
923 #### Nota
924
925 En Odoo 10, la manipulación del conjunto de registros conserva el
orden. Esto es diferente a las versiones anteriores de Odoo, donde la
manipulación del conjunto de registros no estaba garantizada para
conservar el orden, aunque se sabe que la adición y el corte mantienen
el orden de los registros.
926
927 Podemos utilizar estas operaciones para cambiar un conjunto de
registros mediante la eliminación o la adición de elementos. Aquí hay
unos ejemplos:
928
929 + `self.task_ids |= task1` agrega el registro `task1`, si no en el
conjunto de registros
930 + `self.task_ids -= task1` elimina el registro específico `task1`, si
está presente en el conjunto de registros
931 + `self.task_ids = self.task_ids[:-1]` elimina el último registro
932
933 Los campos relacionales contienen valores de conjunto de registros.
Campos muchos-a-uno pueden contener un conjunto de registros singleton
y campos a-muchos contienen conjuntos de registros con cualquier número
de registros. Establecemos valores en ellos mediante una sentencia de
Page 22
capitulo-7.md 07/02/2018
981
982
983
984
985
986 >>> self.company_id.currency_id.name
987
988
989
990
991
992 u'EUR'
993
994 ```
995
996 Convenientemente, un conjunto de registros vacío también se comporta
como singleton, y el acceso a sus campos no devuelve un error, pero
simplemente devuelve `False`. Debido a esto, podemos recorrer registros
usando la notación de puntos sin preocuparnos por errores de valores
vacíos, como se muestra aquí:
997
998 ```
999
1000 >>> self.company_id.country_id
1001
1002
1003
1004
1005
1006 res.country()
1007
1008
1009
1010
1011
1012 >>> self.company_id.country_id.name
1013
1014
1015
1016
1017
1018 False
1019
1020 ```
1021
1022 ### Trabajando con campos relacionales
1023
1024 Mientras se utiliza el patrón de registro activo, se pueden asignar
conjuntos de registros a los campos relacionales.
1025
1026 Para campos muchos-a-uno, el valor asignado debe ser un registro único
(un conjunto de registros singleton).
1027
1028 Para los campos a-muchos, su valor también se puede asignar con un
conjunto de registros, reemplazando la lista de registros vinculados,
si los hay, por uno nuevo. Aquí se permite un conjunto de registros con
cualquier tamaño.
1029
1030 Al utilizar los métodos `create()` o `write()`, donde los valores se
asignan mediante diccionarios, no se pueden asignar campos relacionales
a los valores del conjunto de registros. Debe utilizarse el ID o la
Page 24
capitulo-7.md 07/02/2018
Page 25
capitulo-8.md 07/02/2018
143 ```
144 -
145 I click on the "Make Payment" wizard to pay the PoS order
146 -
147 !record {model: pos.make.payment, id: pos_make_payment_2, context:
'{"active_id": ref("pos_order_pos1"), "active_ids":
[ref("pos_order_pos1")]}' }:
148 amount: !eval >
149 (450*2 + 300*3*1.05)*0.95
150 -
151 I click on the validate button to register the payment.
152 -
153 !python {model: pos.make.payment}: |
154 self.check(cr, uid, [ref('pos_make_payment_2')],
context={'active_id': ref('pos_order_pos1')} )
155 ```
156
157 Las líneas que comienzan con un `!` Son etiquetas YAML, equivalentes a
los elementos de la etiqueta que encontramos en los archivos XML. En el
código anterior podemos ver una etiqueta `¡ record`, equivalente a la
etiqueta XML `<record>`, y una etiqueta `!pyython`, que nos permite
ejecutar código Python en un modelo, `pos.make.payment` en el ejemplo.
158
159 Como puedes ver, las pruebas YAML utilizan una sintaxis específica de
Odoo que necesita aprendizaje. En comparación, las pruebas de Python
utilizan el framework `unittest` existente, solo añadiendo clases
envolventes específicas de Odoo como `TransactionCase`.
160
161 ## Herramientas de desarrollo
162
163 Hay algunas técnicas que el desarrollador debe aprender para ayudar en
su trabajo. En el Capítulo 1, *Iniciando con el desarrollo Odoo*, ya
hemos introducido la interfaz de usuario del modo de desarrollador
**Developer Mode**. También tenemos disponible una opción de servidor
que proporciona algunas características amigables para desarrolladores.
Lo describiremos más detalladamente a continuación. Después de eso
vamos a discutir otro tema relevante para los desarrolladores: cómo
depurar el código del lado del servidor.
164
165
166 ### Opciones de desarrollo del servidor
167
168 El servidor Odoo proporciona la opción `--dev` para permitir que
algunas funciones del desarrollador aceleren nuestro ciclo de
desarrollo, como por ejemplo:
169
170 + Introducir el depurador cuando se encuentre una excepción en un
módulo de complemento
171 + Recargar código Python automáticamente, una vez que se guarda un
archivo Python, evitando un reinicio manual del servidor
172 + Lee las definiciones de vista directamente desde los archivos XML,
evitando las actualizaciones manuales de módulos
173
174 La opción `--dev` acepta una lista de opciones separada por comas,
aunque la opción todo será adecuada la mayor parte del tiempo. También
podemos especificar el depurador que preferimos usar. De forma
predeterminada, se utiliza el depurador Python, `pdb`. Algunas personas
pueden preferir instalar y usar depuradores alternativos. Aquí también
se admiten `ipdb` y `pudb`.
175
176
Page 5
capitulo-8.md 07/02/2018
212
213 ```
214 import pdb; pdb.set_trace()
215
216 ```
217
218 Ahora reinicia el servidor para que se cargue el código modificado. Tan
pronto como la ejecución del programa llegue a esa línea, se mostrará
un prompt de Python (`pdb`) en la ventana de terminal donde se está
ejecutando el servidor, esperando por nuestra entrada.
219
220 #### Nota
221
222 La opción `--dev` no es necesaria para usar puntos de interrupción de
depurador Python manualmente configurados.
223
224 Este prompt funciona como un shell de Python, donde puede ejecutar
cualquier expresión o comando en el contexto de ejecución actual. Esto
significa que las variables actuales pueden ser inspeccionadas e
incluso modificadas. Estos son los comandos de acceso directo más
importantes disponibles:
225
226 + `h` (ayuda) muestra un resumen de los comandos de pdb disponibles
227 + `p` (imprimir) evalúa e imprime una expresión
228 + `pp` (impresión bonita) es útil para imprimir estructuras de datos
como diccionarios o listas
229 + `l` (lista) enumera el código alrededor de la instrucción siguiente a
ejecutar
230 + `n`(siguiente) pasos hacia la siguiente instrucción
231 + `s`(paso) pasos en la instrucción actual
232 + `c`(continuar) continúa la ejecución normalmente
233 + `u` (arriba) sube la pila de ejecución
234 + `d` (hacia abajo) se mueven hacia abajo en la pila de ejecución
235
236 El servidor Odoo también admite la opción `dev=all`. Si se activa,
cuando se produce una excepción, el servidor entra en un modo *post
mortem* en la línea correspondiente. Este es un prompt `pdb`, como el
descrito anteriormente, que nos permite inspeccionar el estado del
programa en el momento en que se encontró el error.
237
238 Mientras que `pdb` tiene la ventaja de estar disponible fuera de la
caja, puede ser bastante concisa, y existen algunas opciones más cómodas.
239
240 #### Un ejemplo de sesión de depuración
241
242 Veamos cómo se ve una simple sesión de depuración. Podemos comenzar
añadiendo punto de interrupción del depurador en la primera línea del
método del asistente `do_populate_tasks`:
243
244 ```
245 def do_populate_tasks(self):
246 import pdb; pdb.set_trace()
247 self.ensure_one()
248 # ...
249 ```
250
251 Ahora reinicia el servidor, abre un formulario **Asistente para tareas
pendientes "To-do Tasks Wizard"** y haz clic en el botón **Obtener todo
"Get All"**. Esto activará el método del asistente `do_populate_tasks`
en el servidor y el cliente web permanecerá en un estado de carga
**Loading...**, esperando la respuesta del servidor. Observando la
Page 7
capitulo-8.md 07/02/2018
370
371
372 ```
373
374 ## Resumen
375
376 Las pruebas automatizadas son una práctica valiosa, tanto para las
aplicaciones empresariales en general, como para garantizar la robustez
del código en un lenguaje de programación dinámico, como Python.
377
378 Aprendimos los principios básicos de cómo agregar y ejecutar pruebas
para un módulo addon. También discutimos algunas técnicas para
ayudarnos a depurar nuestro código.
379
380 En el próximo capítulo, profundizaremos en la capa de vistas, y
discutiremos las vistas kanban.
Page 11
capitulo-9.md 07/02/2018
28
29 El diseño de la vista kanban es bastante flexible, por lo que haremos
todo lo posible para prescribir una manera sencilla de crear
rápidamente sus vistas kanban. Un buen enfoque es encontrar una vista
kanban existente similar a lo que usted necesita, e inspeccionarla para
ideas sobre cómo construir la suya.
30
31 Podemos ver dos formas diferentes de usar las vistas de kanban. Una es
una lista de tarjetas. Se utiliza en lugares como contactos, productos,
directorios de empleados o aplicaciones.
32
33 Así es como se ve la vista de kanban de **Contactos "Contacts"**:
34
35 
36
37 Pero esto no es un tablero kanban verdadero. Se espera que un tablero
kanban tenga las tarjetas organizadas en columnas y, por supuesto, la
vista kanban también apoya ese diseño. Podemos ver ejemplos en **Sales
| My Pipeline** o en **Tareas de Proyecto "Project Tak"**.
38
39 Aquí está como se ve ** Sales | My Pipeline**:
40
41 
42
43 La diferencia más llamativa entre los dos es la organización de las
tarjetas en columna del tablero de kanban. Esto se logra mediante la
función **Agrupar por "Group By"**, similar a lo que proporcionan las
vistas de lista. Por lo general, el agrupamiento se realiza en un campo
**Etapa "Stage"**. Una característica muy útil de las vistas de kanban
es que soporta arrastrar y soltar tarjetas entre columnas, asignando
automáticamente el valor correspondiente al campo que agrupa la vista.
44
45 Mirando las tarjetas en ambos ejemplos, podemos ver algunas
diferencias. De hecho, su diseño es bastante flexible, y no hay una
sola manera de diseñar una tarjeta kanban. Pero estos dos ejemplos
pueden proporcionar un punto de partida para sus diseños.
46
47 Las tarjetas de **Contacto "Contact"** básicamente tienen una imagen en
el lado izquierdo, y un título en negrita en el área principal, seguido
de una lista de valores. Las tarjetas **My Pipeline** tienen un poco
más de estructura. El área de la tarjeta principal también tiene un
título seguido por una lista de información relevante, así como un área
de pie de página, en este caso con un widget de prioridad en el lado
izquierdo, y el usuario responsable en el lado derecho. No es visible
en la imagen, pero las tarjetas también tienen un menú de opciones en
la parte superior derecha, que se muestra al pasar el puntero del ratón
sobre él. Este menú permite, por ejemplo, cambiar el color de fondo de
la tarjeta.
48
49 Vamos a utilizar esta estructura más elaborada como un modelo para las
tarjetas en nuestro tablero kanban de tareas pendientes.
50
51 ## Diseñando vistas kanban
52
53 Vamos a agregar la vista kanban a las tareas pendientes con un nuevo
módulo addon. Sería más sencillo añadirlo directamente al módulo
`todo_ui`. Sin embargo, para una explicación más clara, usaremos un
nuevo módulo y evitaremos muchos cambios, posiblemente confusos, en los
archivos ya creados.
54
55 Nombraremos este nuevo módulo addon como `todo_kanban` y crearemos los
Page 2
capitulo-9.md 07/02/2018
193
194 #### Tip
195
196 Odoo utiliza la biblioteca de estilo web de Twitter Bootstrap 3, por lo
que esas clases de estilo están generalmente disponibles dondequiera
que se pueda renderizar HTML. Puedes obtener más información sobre
Bootstrap en https://getbootstrap.com
197
198 Ahora vamos a ver más de cerca las plantillas de QWeb para usarlas en
las vistas kanban.
199
200
201 ### El diseño de la tarjeta kanban
202
203 El área de contenido principal de una tarjeta kanban se define dentro
de la plantilla `kanban-box`. Este área de contenido también puede
tener un sub-contenedor de pie de página.
204
205 Para un único pie de página, usaríamos un elemento `<div>` en la parte
inferior del cuadro kanban, con la clase `oe_kanban_footer` CSS. Esta
clase dividirá automáticamente sus elementos internos con espacios
flexibles, haciendo explícita la alineación izquierda y derecha dentro
de ella superflua.
206
207 Un botón que abre un menú de acción también puede aparecer en la
esquina superior derecha de la tarjeta. Como alternativa, el Bootstrap
proporciona las clases `pull-left` y `pull-right` para añadir elementos
alineados a la izquierda oa la derecha en cualquier parte de la
tarjeta, incluso en el pie `oe_kanban_footer`.
208
209 Aquí está nuestra primera iteración en la plantilla QWeb para nuestra
tarjeta kanban:
210
211 ```
212 <!-- Define the kanban-box template -->
213 <t t-name="kanban-box">
214 <!-- Set the Kanban Card color: -->
215 <div t-attf-class="#{kanban_color(record.color.raw_value)}
216 oe_kanban_global_click">
217 <div class="o_dropdown_kanban dropdown">
218
219 <!-- Top-right drop down menu here... -->
220
221
222
223
224 </div>
225 <div class="oe_kanban_content">
226 <div class="oe_kanban_footer">
227 <div>
228
229 <!-- Left hand footer... -->
230
231
232
233
234 </div>
235 <div>
236
237 <!-- Right hand footer... -->
238
Page 6
capitulo-9.md 07/02/2018
239
240
241
242 </div>
243 </div>
244 </div> <!-- oe_kanban_content -->
245 <div class="oe_clear"/>
246 </div> <!-- kanban color -->
247 </t>
248
249 ```
250
251 Esto establece la estructura general de la tarjeta kanban. Puedes notar
que el campo `color` se utiliza en el elemento superior `<div>` para
establecer dinámicamente el color de la tarjeta. Explicaremos más
detalladamente la directiva Qweb `t-attf` en una de las siguientes
secciones.
252
253 Ahora vamos a trabajar en el área de contenido principal, y elegir qué
colocar allí:
254
255 ```
256
257 <!-- Content elements and fields go here... -->
258
259
260
261
262 <div>
263 <field name="tag_ids" />
264 </div>
265
266 <div>
267 <strong>
268 <a type="open"><field name="name" /></a>
269 </strong>
270 </div>
271
272 <ul>
273 <li><field name="user_id" /></li>
274 <li><field name="date_deadline" /></li>
275 </ul>
276 ```
277
278 La mayor parte de esta plantilla es HTML normal, pero también vemos el
elemento `<field>` usado para renderizar los valores de los campos, y
el atributo `type` utilizado en los botones regulares de vista de
formulario, usados aquí en una etiqueta de anclaje `<a>`.
279
280 En el pie de página izquierdo, vamos a insertar el widget de prioridad:
281
282 ```
283 <div>
284
285 <!-- Left hand footer... -->
286
287
288
289
290 <field name="priority" widget="priority"/>
291 </div>
Page 7
capitulo-9.md 07/02/2018
292 ```
293 Aquí podemos ver el campo `priority` añadido, tal como lo haríamos en
una vista de formulario.
294
295 En el pie derecho colocaremos el widget de estado kanban y el avatar
para el propietario de la tarea:
296
297 ```
298 <div>
299
300 <!-- Right hand footer... -->
301
302
303
304
305 <field name="kanban_state" widget="kanban_state_selection"/>
306 <img t-att- t-att-src="kanban_image(
307 'res.users', 'image_small', record.user_id.raw_value)"
308 width="24" height="24" class="oe_kanban_avatar pull-right" />
309 </div>
310 ```
311 El estado kanban se agrega utilizando un elemento `<field`, al igual
que en las vistas de formulario normales. La imagen del avatar del
usuario se inserta mediante la etiqueta HTML `<img>`. El contenido de
la imagen se genera dinámicamente con la directiva QWeb `t-att-`, que
explicaremos en un momento.
312
313 A veces queremos tener una pequeña imagen representativa que se
mostrará en la tarjeta, como en el ejemplo de **Contactos "Contacts"**.
Para hacer referencia, esto se puede hacer agregando lo siguiente como
el primer elemento de contenido:
314
315 ```
316 <img t-att-src="kanban_image( 'res.partner', 'image_medium',
317 record.id.value)" class="o_kanban_image"/>
318 ```
319
320 ### Añadiendo un menú de opciones de tarjeta kanban
321
322 Las tarjetas Kanban pueden tener un menú de opciones, situado en la
parte superior derecha. Las acciones habituales son editar o eliminar
el registro, pero es posible tener cualquier acción que se puede llamar
desde un botón. También tenemos un widget para configurar el color de
la tarjeta.
323
324 El siguiente es un código HTML de línea de base para el menú de
opciones que se agregará en la parte superior del elemento
`oe_kanban_content`:
325
326 ```
327 <div class="o_dropdown_kanban dropdown">
328
329 <!-- Top-right drop down menu here... -->
330
331
332
333
334 <a class="dropdown-toggle btn" data-toggle="dropdown" href="#">
335 <span class="fa fa-bars fa-lg"/>
336 </a>
337 <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
Page 8
capitulo-9.md 07/02/2018
338
339 <!-- Edit and Delete actions, if available: -->
340
341
342
343
344 <t t-if="widget.editable">
345 <li><a type="edit">Edit</a></li>
346 </t>
347 <t t-if="widget.deletable">
348 <li><a type="delete">Delete</a></li>
349 </t>
350
351 <!-- Call a server-side Model method: -->
352
353
354
355
356 <t t-if="!record.is_done.value">
357 <li><a name="do_toggle_done" type="object">Set as Done</a>
358 </li>
359 </t>
360
361 <!-- Color picker option: -->
362
363
364
365
366 <li>
367 <ul class="oe_kanban_colorpicker" data-field="color"/>
368 </li>
369 </ul>
370 </div>
371 ```
372 Observa que lo anterior no funcionará a menos que tengamos `<field
name="is_done" />` en algún lugar de la vista, porque se usa en una de
las expresiones. Si no necesitamos usarla dentro de la plantilla,
podemos declararla antes del elemento `<templates>`, como lo hicimos al
definir la vista `<kanban>`.
373
374 El menú desplegable es básicamente una lista HTML de los elementos
`<a>`. Algunas opciones, como **Editar "Edit"** y **Eliminar
"Delete"**, sólo están disponibles si se cumplen ciertas condiciones.
Esto se hace con la directiva QWeb `t-if`. Más adelante en este
capítulo, explicamos estas y otras directivas QWeb con más detalle.
375
376 La variable global `widget` representa el objeto `KanbanRecord()`
JavaScript actual responsable de la representación de la tarjeta kanban
actual. Dos propiedades particularmente útiles son `widget.editable` y
`widget.deletable` para inspeccionar si las acciones están disponibles.
377
378 También podemos ver cómo mostrar u ocultar una opción dependiendo de
los valores del campo de registro. La opción **Establecer como Hecho
"Set as Done"** solo se mostrará si el campo `is_done` no está
establecido.
379
380 La última opción agrega el widget especial del selector de color usando
el campo de datos `color` para seleccionar y cambiar el color de fondo
de la tarjeta.
381
382 ### Acciones en vistas kanban
Page 9
capitulo-9.md 07/02/2018
383
384 En las plantillas de QWeb, la etiqueta `<a>` para los vínculos puede
tener un atributo `type`. Establece el tipo de acción que el enlace
realizará para que los enlaces puedan actuar igual que los botones en
formas regulares. Así, además de los elementos `<button>`, las
etiquetas `<a>` también se pueden usar para ejecutar acciones Odoo.
385
386 Al igual que en las vistas de formulario, el tipo de acción puede ser
`action` u `object`, y debe ir acompañado de un atributo `name`, que
identifica la acción específica a ejecutar. Además, también están
disponibles los siguientes tipos de acción:
387
388 + `open` abre la vista de formulario correspondiente
389 + `edit` abre la vista de formulario correspondiente directamente en
modo de edición
390 + `delete` elimina el registro y remueve el elemento de la vista kanban
391
392 ## El lenguaje de plantillas QWeb
393
394 El analizador QWeb busca directivas especiales en las plantillas y las
reemplaza con HTML generado dinámicamente. Estas directivas son
atributos de elementos XML y pueden utilizarse en cualquier etiqueta o
elemento válido, como `<div>`, `<span>` o `<campo>`.
395
396 A veces queremos usar una directiva QWeb pero no queremos colocarla en
ninguno de los elementos XML de nuestra plantilla. Para esos casos,
tenemos un elemento especial `<t>` que puede tener directivas QWeb,
como un `t-if` o un `t-foreach`, pero es silencioso y no tendrá ningún
resultado en el XML / HTML final producido.
397
398 Las directivas QWeb usan frecuentemente expresiones evaluadas para
producir resultados diferentes dependiendo de los valores de registro
actuales. Existen dos implementaciones QWeb diferentes: JavaScript en
el lado del cliente y Python en el lado del servidor.
399
400 Los informes y las páginas del sitio web utilizan la implementación
Python del lado del servidor. Por otro lado, las vistas kanban utilizan
la implementación JavaScript del lado del cliente. Esto significa que
la expresión QWeb utilizada en las vistas kanban debe escribirse
utilizando la sintaxis JavaScript, no Python.
401
402 Al mostrar una vista de kanban, los pasos internos son aproximadamente
como sigue:
403
404 1. Obten el XML de las plantillas para procesar.
405 1. Llama al método del servidor `read()` para obtener los datos de los
campos de las plantillas.
406 1. Busca la plantilla `kanban-box` y analizala utilizando QWeb para
generar los fragmentos HTML finales.
407 1. Inyecta el HTML en la pantalla del navegador (el DOM).
408
409 Esto no pretende ser técnicamente exacto. Es sólo un mapa mental que
puede ser útil para entender cómo funcionan las cosas en las vistas
kanban.
410
411 A continuación, aprenderemos acerca de la evaluación de expresiones de
QWeb y exploraremos las directivas QWeb disponibles, usando ejemplos
que mejoren nuestra tareas pendientes de la tarjeta kanban.
412
413 ### La evaluación del contexto Qweb Javascript
414
Page 10
capitulo-9.md 07/02/2018
415 Muchas de las directivas QWeb utilizan expresiones que se evalúan para
producir algún resultado. Cuando se utiliza desde el lado del cliente,
como es el caso de las vistas kanban, estas expresiones están escritas
en JavaScript. Se evalúan en un contexto que tiene algunas variables
útiles disponibles.
416
417 Un objeto `record` está disponible, representando el registro que se
procesa, con los campos solicitados desde el servidor. Se puede acceder
a los valores de campo utilizando los atributos `raw_value` o `value`:
418
419 + `raw_value` es el valor devuelto por el método de servidor `read()`,
por lo que es más adecuado para usar en expresiones de condición.
420 + `value` se formatea de acuerdo con la configuración del usuario, y
está destinado a ser utilizado para mostrar en la interfaz de usuario.
Esto es típicamente relevante para los campos date/datetime y
float/monetary.
421
422 La evaluación de contexto QWeb también tiene referencias disponibles
para la instancia de cliente web JavaScript. Para hacer uso de ellos,
se necesita una buena comprensión de la arquitectura de cliente web,
pero no vamos a ser capaces de entrar en eso en detalle. Para fines de
referencia, los siguientes identificadores están disponibles en la
evaluación de la expresión QWeb:
423
424 + `widget` es una referencia al objeto de widget `KanbanRecord()`
actual, responsable de la representación del registro actual en una
tarjeta kanban. Expone algunas funciones auxiliares útiles que podemos
usar.
425 + `record` es un atajo para `widget.records` y proporciona acceso a los
campos disponibles, usando la notación de puntos.
426 + `read_only_mode` indica si la vista actual está en modo de lectura (y
no en el modo de edición). Es un acceso directo para
`widget.view.options.read_only_mode`.
427 + `instance` es una referencia a la instancia completa del cliente web.
428
429 También es de destacar que algunos caracteres no están permitidos
dentro de las expresiones. El signo inferior a (`<`) es tal caso. Esto
es debido al estándar XML, donde dichos caracteres tienen un
significado especial y no deben usarse en el contenido XML. Un `>=`
negado es una alternativa válida, pero la práctica común es utilizar
los siguientes símbolos alternativos que están disponibles para las
operaciones de desigualdad:
430
431 + `lt` es por menos que
432 + `lte` es por menor o igual que
433 + `gt` es por mayor que
434 + `gte`es por mayor o igual que
435
436 ### Utilizando t-attf para la sustitución de atributos de cadena
437
438 Nuestra tarjeta kanban utiliza la directiva QWeb `t-attf` para
establecer dinámicamente una clase en el elemento superior `<div>` para
que la tarjeta se coloree dependiendo del valor del campo `color`. Para
ello, se utilizó la directiva QWeb `t-attf-`.
439
440 La directiva `t-attf-` genera dinámicamente atributos de etiqueta
mediante sustitución de cadena. Esto se permite para las partes de
cadenas más grandes generadas dinámicamente, como una dirección URL o
nombres de clase CSS.
441
442 La directiva busca bloques de expresión que serán evaluados y
Page 11
capitulo-9.md 07/02/2018
480 ```
481 t-att-src="kanban_image('res.users', 'image_small',
482 record.user_id.raw_value)"
483 ```
484
485 Los parámetros de la función son: el modelo para leer la imagen, el
nombre del campo a leer y el ID del registro. Aquí usamos `.raw_value`,
para obtener el ID de la base de datos del usuario en lugar de su texto
de representación.
486
487 No se detiene allí, y `t-att-NAME` y `t-attf-NAME` se pueden hacer para
renderizar cualquier atributo, ya que el nombre del atributo generado
se toma del sufijo `NAME` utilizado.
488
489 ### Usando t-foreach para bucles
490
491 Un bloque de HTML se puede repetir iterando a través de un bucle.
Podemos usarlo para agregar los avatares de los seguidores de tareas a
la tarjeta kanban de la tarea.
492
493 Comencemos por representar sólo los ID de socio de la tarea, de la
siguiente manera:
494
495 ```
496 <t t-foreach="record.message_partner_ids.raw_value" t-as="rec">
497 <t t-esc="rec" />;
498 </t>
499 ```
500
501 La directiva `t-foreach` acepta una expresión JavaScript que evalúa una
colección para iterar. En la mayoría de los casos, este será sólo el
nombre de un campo de relación *a-muchos*. Se utiliza con una directiva
`t-as` para establecer el nombre a utilizar para referirse a cada
elemento de la iteración.
502
503 La directiva `t-esc` utilizada a continuación evalúa la expresión
proporcionada, sólo el nombre de la variable `rec` en este caso, y lo
hace como código HTML con seguridad.
504
505 En el ejemplo anterior, pasamos por los seguidores de tareas,
almacenados en el campo `message_parter_ids`. Puesto que hay espacio
limitado en la tarjeta kanban, podríamos haber usado la función
JavaScript `slice()` para limitar el número de seguidores a mostrar,
como se muestra en lo siguiente:
506
507 ```
508 t-foreach="record.message_partner_ids.raw_value.slice(0, 3)"
509 ```
510
511 La variable `rec`contine cada valor de iteración, una ID de Socio en
este caso. Con esto, podemos reescribir los loops de los seguidores
como se muestra a continuación:
512
513 ```
514 <t t-foreach="record.message_parter_ids.raw_value.slice(0, 3)"
515 t-as="rec">
516 <img t-att-src="kanban_image('res.partner', 'image_small', rec)"
517 class="oe_avatar" width="24" height="24" />
518 </t>
519 ```
520
Page 13
capitulo-9.md 07/02/2018
521 Por ejemplo, esto podría agregarse junto a la imagen del usuario
responsable, en el pie de página derecho.
522
523 Algunas variables auxiliares también están disponibles. Su nombre tiene
como prefijo el nombre de variable definido en `t-as`. En nuestro
ejemplo, utilizamos `rec`, por lo que las variables auxiliares
disponibles son las siguientes:
524
525 + `rec_index` es el índice de iteración, a partir de cero
526 + `rec_size` es el número de elementos de la colección
527 + `rec_first` es verdadero en el primer elemento de la iteración
528 + `rec_last` es verdadero en el último elemento de la iteración
529 + `rec_even` es verdadero en índices pares
530 + `rec_odd` es verdadero en índices impares
531 + `rec_parity` es impar `odd` o par `even`, dependiendo del índice actual
532 + `rec_all` representa el objeto sobre el que se está iterando
533 + `rec_value` al iterar a través de un diccionario, `{key: value}`,
contiene el valor (`rec` contiene el nombre de la clave)
534
535 Por ejemplo, podríamos hacer uso de lo siguiente para evitar una coma
de arrastre en nuestra lista de ID:
536
537 ```
538 <t t-foreach="record.message_parter_ids.raw_value.slice(0, 3)"
539 t-as="rec">
540 <t t-esc="rec" />
541 <t t-if="!rec_last">;</t>
542 </t>
543 ```
544
545 ### Usando t-if para renderizado condicional
546
547 Nuestra vista kanban utilizó la directiva `t-if` en el menú de opciones
de la tarjeta para hacer que algunas opciones estén disponibles
dependiendo de algunas condiciones. La directiva `t-if` espera que una
expresión se evalúe en JavaScript al representar vistas kanban en el
lado del cliente. La etiqueta y su contenido se renderizan sólo si la
condición se evalúa como verdadera.
548
549 Como otro ejemplo, para mostrar la estimación del esfuerzo de tarea en
la tarjeta kanban, sólo si tiene un valor, agregue lo siguiente después
del campo date_deadline:
550
551 ```
552 <t t-if="record.effort_estimate.raw_value gt 0">
553 <li>Estimate <field name="effort_estimate"/></li>
554 </t>
555 ```
556
557 Utilizamos un elemento `<t t-if="...">` para que si la condición es
falsa, el elemento no produce salida. Si es cierto, sólo el elemento
`<li>` contenido se renderiza a la salida. Observe que la expresión de
condición usó el símbolo `gt` en lugar de `>`, para representar el
operador *mayor que*.
558
559 ### Utilizando t-esc y t-raw para generar valores
560
561 Utilizamos el elemento `<field>` para representar el contenido del
campo. Pero los valores de campo también se pueden presentar
directamente sin una etiqueta `<field>`.
562
Page 14
capitulo-9.md 07/02/2018
606
607
608 ### Usando t-call para insertar otras plantillas
609
610 Las plantillas de QWeb pueden ser fragmentos de HTML reutilizables, que
se pueden insertar en otras plantillas. En lugar de repetir los mismos
bloques HTML una y otra vez, podemos diseñar bloques de construcción
para componer vistas de interfaz de usuario más complejas.
611
612 Las plantillas reutilizables se definen dentro de la etiqueta
`<templates>` y se identifican por un elemento superior con un `t-name`
distinto de `kanban-box`. Estas otras plantillas pueden ser incluidas
usando la directiva `t-call`. Esto es cierto para las plantillas
declaradas junto a la misma vista kanban, en otro lugar del mismo
módulo addon o en un addon diferente.
613
614 La lista de avatar del seguidor es algo que podría aislarse en un
fragmento reutilizable. Vamos a volver a trabajar para usar una
sub-plantilla. Debemos comenzar agregando otra plantilla a nuestro
archivo XML, dentro del elemento `<templates>`, después del nodo `<t
t-name="kanban-box">`, como se muestra a continuación:
615
616
617 ```
618 <t t-name="follower_avatars">
619 <div>
620 <t t-foreach="record.message_parter_ids.raw_value.slice(0, 3)"
621 t-as="rec">
622 <img t-att-src="kanban_image('res.partner', 'image_small', rec)"
623 class="oe_avatar" width="24" height="24" />
624 </t>
625 </div>
626 </t>
627 ```
628
629 Llamarlo desde la plantilla `kanban-box` principal es bastante
sencillo. En lugar del elemento `<div>` que contiene el parámetro para
la directiva `for each`, debemos utilizar lo siguiente:
630
631 ```
632 <t t-call="follower_avatars" />
633 ```
634
635 Para llamar a las plantillas definidas en otros módulos addon,
necesitamos usar el identificador completo `module.name`, como lo
hacemos con las otras vistas. Por ejemplo, este fragmento puede
referirse utilizando el identificador completo
`todo_kanban.follower_avatars`.
636
637 La plantilla llamada se ejecuta en el mismo contexto que la persona que
llama, por lo que cualquier nombre de variable disponible en la persona
que llama también está disponible al procesar la plantilla llamada.
638
639 Una alternativa más elegante es pasar argumentos a la plantilla
llamada. Esto se hace mediante el establecimiento de variables dentro
de la etiqueta `t-call`. Éstos se evaluarán y se pondrán a disposición
en el contexto de la sub-plantilla solamente, y no existirán en el
contexto de la persona que llama.
640
641 Podríamos utilizar esto para tener el número máximo de avatares de
seguidores establecidos por la persona que llama en lugar de ser
Page 16
capitulo-9.md 07/02/2018
735
736
737
738
739 $ xpath -e "//record[@id='res_partner_kanban_view']" -e
"//field[@name='display_name']]" /path/to/*.xml
740
741
742 ```
743
744 ## Activos personalizados CSS y JavaScript
745
746 Como hemos visto, las vistas kanban son en su mayoría HTML y hacen uso
intensivo de las clases CSS. Hemos introducido algunas clases de CSS
utilizadas con frecuencia por el producto estándar. Pero para obtener
mejores resultados, los módulos también pueden agregar su propio CSS.
747
748 No vamos a entrar en detalles aquí sobre cómo escribir código CSS, pero
es relevante para explicar cómo un módulo puede agregar sus propios
activos web CSS (y JavaScript). Los activos Odoo para el backend se
declaran en la plantilla `assets_backend`. Para agregar nuestros
activos de módulo, debemos ampliar esa plantilla. El archivo XML para
esto generalmente se coloca dentro de un subdirectorio `views/module`.
749
750 A continuación se muestra un archivo XML de ejemplo para agregar un
archivo CSS y JavaScript al módulo `todo_kanban`, y podría estar en
`todo_kanban/views/todo_kanban_assets.xml`:
751
752 ```
753 <?xml version="1.0" encoding="utf-8"?>
754 <odoo>
755 <template id="assets_backend" inherit_id="web.assets_backend"
756 name="Todo Kanban Assets" >
757 <xpath expr="." position="inside">
758 <link rel="stylesheet"
759 href="/todo_kanban/static/src/css/todo_kanban.css"/>
760 <script type="text/javascript"
761 src="/todo_kanban/static/src/js/todo_kanban.js">
762 </script>
763 </xpath>
764 </template>
765 </odoo>
766 ```
767 Como de costumbre, se debe hacer referencia en el archivo descriptor
`__manifest__.py`. Observa que los activos se encuentran dentro de un
subdirectorio `/static/src`. Aunque esto no es necesario, es una
convención generalmente usada.
768
769 ## Resúmen
770
771 Aprendiste acerca de los tableros kanban y cómo construir las vistas
kanban para implementarlas. También introdujimos el modelo QWeb y cómo
se puede utilizar para diseñar tarjetas kanban. QWeb es también el
motor de renderizado que potencia el sitio web CMS, por lo que está
creciendo en importancia en el conjunto de herramientas de Odoo. En el
próximo capítulo, seguiremos usando QWeb, pero en el lado del servidor,
para crear nuestros informes personalizados.
772
773
Page 19
capitulo-10.md 07/02/2018
29 ```
30
31 a continuación necesitamos descargar el paquete apropiado para nuestro
sistema e instalarlo. Comprueba el nombre de archivo correcto en
http://download.gna.org/wkhtmltopdf/0.12/0.12.1. Para Ubuntu 14.04 LTS
(Trusty) 64 bits, el comando de descarga sería así:
32
33 ```
34 $ wget
http://download.gna.org/wkhtmltopdf/0.12/0.12.1/wkhtmltox-0.12.1_linux-tr
usty-amd64.deb -O /tmp/wkhtml.deb
35 ```
36
37 Luego debemos instalarlo. La instalación de un archivo `deb` local no
instala automáticamente las dependencias, por lo que se necesitará un
segundo paso para hacerlo y completar la instalación:
38
39 ```
40
41 $ sudo dpkg -i wkhtml.deb
42
43
44
45
46
47 $ sudo apt-get -f install
48
49 ```
50
51 Ahora podemos comprobar si la biblioteca `wkhtmltopdf` está
correctamente instalada y confirmar que es el número de versión que
queremos:
52
53
54 ```
55
56 $ wkhtmltopdf --version
57
58
59
60
61
62 wkhtmltopdf 0.12.1 (with patched qt)
63
64 ```
65
66 Después de esto, la secuencia de inicio del servidor Odoo no mostrará
el mensaje de información **Necesitas un archivo Wkhtmltopdf para
imprimir una versión en pdf del reporte "You need Wkhtmltopdf to print
a pdf version of the report's"**.
67
68 ## Creando informes empresariales
69
70 Por lo general, implementaríamos el informe en nuestro módulo de
complemento de la aplicación de tareas pendientes. Pero para propósitos
de aprendizaje, crearemos un nuevo módulo addon solo para nuestro
informe.
71
72 Nuestro informe se verá así:
73
74 
Page 2
capitulo-10.md 07/02/2018
75
76 Nombraremos este nuevo módulo addon `todo_report`. Lo primero que debe
hacer es crear un archivo `__init__.py` vacío y el archivo de
manifiesto `__manifest__.py`:
77
78 ```
79 {
80 'name': 'To-Do Report',
81 'description': 'Report for To-Do tasks.',
82 'author': 'Daniel Reis',
83 'depends': ['todo_kanban'],
84 'data': ['reports/todo_report.xml'] }
85
86 ```
87
88 El archivo `reports/todo_report.xml` puede comenzar declarando el nuevo
informe de la siguiente manera:
89
90 ```
91 <?xml version="1.0"?>
92 <odoo>
93 <report id="action_todo_task_report"
94 string="To-do Tasks"
95 model="todo.task"
96 report_type="qweb-pdf"
97 name="todo_report.report_todo_task_template"
98 />
99 </odoo>
100
101 ```
102
103 La etiqueta `<report>` es un acceso directo para escribir datos al
modelo `ir.actions.report.xml`, que es un tipo particular de acción del
cliente. Sus datos están disponibles en las opciones de menú
**Configuración | Técnico | Informes " Settings | Technical | Reports
"**.
104
105 #### Tip
106
107 Durante el diseño del informe, es posible que prefieras dejar
`report_type="qweb-html"`, y cambiarlo de nuevo al archivo `qweb-pdf`
una vez terminado. Esto hará que sea más rápido generar y más fácil
inspeccionar el resultado HTML de la plantilla de OWeb.
108
109 Después de instalarlo, la vista de formulario de tareas pendientes
mostrará un botón **Imprimir "Print"** en la parte superior, a la
izquierda del botón **Más "More"**, que contiene esta opción para
ejecutar el informe.
110
111 No funcionará en este momento, ya que todavía no hemos definido el
informe. Este será un informe QWeb, por lo que utilizará una plantilla
QWeb. El atributo `name` identifica la plantilla que se va a utilizar.
A diferencia de otras referencias de identificador, se requiere el
prefijo del módulo en el atributo `name`. Debemos usar la referencia
completa `<module_name>.<identifier_name>`.
112
113 ## Plantillas de informes QWeb
114
115 Los informes generalmente seguirán un esqueleto básico, como se muestra
a continuación. Esto se puede agregar al archivo
`reports/todo_report.xml`, justo después del elemento `<report>`.
Page 3
capitulo-10.md 07/02/2018
116
117 ```
118 <template id="report_todo_task_template">
119 <t t-call="report.html_container">
120 <t t-call="report.external_layout">
121 <div class="page">
122 <!-- Report page content -->
123 </div>
124 </t>
125 </t>
126 </template>
127 ```
128
129 Los elementos más importantes aquí son las directivas `t-call` que
utilizan estructuras de informe estándar. La plantilla
report.html_container realiza la configuración básica para admitir un
documento HTML. La plantilla `report.external_layout` maneja el
encabezado y pie de página del informe, utilizando la configuración
correspondiente de la empresa adecuada. Como alternativa, podemos usar
la plantilla `report.internal_layout`, que utiliza sólo un encabezado
básico.
130
131 Ahora tenemos en su lugar, el esqueleto básico para nuestro módulo y
vista de informe. Ten en cuenta que, dado que los informes son sólo
plantillas QWeb, se puede aplicar la herencia, al igual que en las
otras vistas. Las plantillas QWeb utilizadas en los informes se pueden
ampliar utilizando las vistas heredadas normales con expresiones
**XPATH**.
132
133 ## Presentando datos en informes
134
135 A diferencia de las vistas Kanban, las plantillas de QWeb en los
informes se representan en el lado del servidor y utilizan una
implementación Python QWeb. Podemos ver esto como dos implementaciones
de la misma especificación, y hay algunas diferencias que necesitamos
tener en cuenta.
136
137 Para empezar, las expresiones QWeb se evalúan utilizando la sintaxis de
Python, no JavaScript. Para las expresiones más simples, puede haber
poca o ninguna diferencia, pero las operaciones más complejas
probablemente serán diferentes.
138
139 La forma en que se evalúan las expresiones también es diferente. Para
los informes, tenemos disponibles las siguientes variables:
140
141 + `docs` es una colección iterable con los registros a imprimir
142 + `doc_ids es una lista de los IDs de los registros a imprimir
143 + `doc_model` identifica el modelo de los registros, `todo.task` por
ejemplo
144 + `time is` es una referencia a la biblioteca de tiempo de Python
145 + `user` es el registro para el usuario que ejecuta el informe
146 + `res_company` es el registro de la empresa del usuario actual
147
148 El contenido del informe está escrito en HTML, los valores de los
campos pueden referenciarse mediante el atributo `t-field` y puede
complementarse con el atributo `t-field-options` para utilizar un
widget específico para representar el contenido del campo.
149
150 Ahora podemos empezar a diseñar el contenido de la página de nuestro
informe:
151
Page 4
capitulo-10.md 07/02/2018
152 ```
153 <!-- Report page content
154 <div class="row bg-primary">
155 <div class="col-xs-3">
156 <span class="glyphicon glyphicon-pushpin" />
157 What
158 </div>
159 <div class="col-xs-2">Who</div>
160 <div class="col-xs-1">When</div>
161 <div class="col-xs-3">Where</div>
162 <div class="col-xs-3">Watchers</div>
163 </div>
164
165 <t t-foreach="docs" t-as="o">
166 <div class="row">
167
168 <!-- Data Row Content -->
169
170
171
172
173 </div>
174 </t>
175
176 ```
177
178 El diseño del contenido puede utilizar el sistema de cuadrícula HTML de
Bootstrap de Twitter. En pocas palabras, Bootstrap tiene un diseño de
cuadrícula con 12 columnas. Se puede añadir una nueva fila usando `<div
class="row">`. Dentro de una fila, tenemos celdas, cada una de ellas
abarcando un cierto número de columnas, que debe ocupar las 12
columnas. Cada celda se puede definir con la fila `<div
class="col-xs-N">`, donde N es el número de columnas que abarca.
179
180 #### Nota
181
182 Una referencia completa para Bootstrap, que describe estos y otros
elementos de estilo, se puede encontrar en http://getbootstrap.com.
183
184 Aquí estamos agregando una fila de encabezado con títulos, y luego
tenemos un bucle `t-foreach`, iterando a través de cada registro, y
representando una fila para cada uno.
185
186 Dado que la renderización se realiza en el lado del servidor, los
registros son objetos y podemos usar la notación de puntos para acceder
a los campos de los registros de datos relacionados. Esto hace que sea
fácil de seguir a través de campos relacionales para acceder a sus
datos. Ten en cuenta que esto no es posible en el Qweb renderado del
lado del cliente, vistas, como las vistas kanban del cliente web.
187
188 Este es el XML para el contenido de las filas de registros:
189
190 ```
191 <div class="col-xs-3">
192 <h4><span t-field="o.name" /></h4>
193 </div>
194 <div class="col-xs-2">
195 <span t-field="o.user_id" />
196 </div>
197 <div class="col-xs-1">
198 <span t-field="o.date_deadline" />
Page 5
capitulo-10.md 07/02/2018
página horizontal. Así que tenemos que añadir este formato de papel.
287
288 En la parte superior del archivo XML, agregua este registro:
289
290 ```
291 <record id="paperformat_euro_landscape"
292 model="report.paperformat">
293 <field name="name">European A4 Landscape</field>
294 <field name="default" eval="True" />
295 <field name="format">A4</field>
296 <field name="page_height">0</field>
297 <field name="page_width">0</field>
298 <field name="orientation">Landscape</field>
299 <field name="margin_top">40</field>
300 <field name="margin_bottom">23</field>
301 <field name="margin_left">7</field>
302 <field name="margin_right">7</field>
303 <field name="header_line" eval="False" />
304 <field name="header_spacing">35</field>
305 <field name="dpi">90</field>
306 </record>
307 ```
308
309 Es una copia del formato A4 europeo, definido en el archivo
`report_paperformat.xml`, efinido en `addons/report/data`, pero
cambiando la orientación de Vertical a Horizontal. Los formatos de
papel definidos se pueden ver desde el cliente web a través del menú
**Configuración | Técnico | Informes | Formato del papel "Settings |
Technical | Reports | Paper Format"**.
310
311 Ahora podemos usarlo en nuestro informe. El formato de papel
predeterminado se define en la configuración de la empresa, pero
también podemos especificar el formato de papel que utilizará un
informe específico. Esto se hace utilizando un atributo `paperfomat` en
la acción del informe.
312
313 Vamos a editar la acción utilizada para abrir nuestro informe, para
agregar este atributo:
314
315 ```
316 <report id="action_todo_task_report"
317 string="To-do Tasks"
318 model="todo.task"
319 report_type="qweb-pdf"
320 name="todo_report.report_todo_task_template"
321 paperformat="paperformat_euro_landscape"
322 />
323 ```
324 #### Tip
325
326 El atributo `paperformat` en la etiqueta `<report>` se agregó en la
versión 9.0. Para 8.0 necesitamos usar un elemento `<record>` para
agregar una acción de informe con un valor `paperformat`.
327
328 ## Habilitando la traducción de idiomas en los informes
329
330 Para habilitar las traducciones de un informe, debe ser llamado desde
una plantilla, usando un elemento `<t t-call>` con un atributo `t-lang`.
331
332 El atributo `t-lang` debe evaluarse a un código de idioma, como `es` o
`en_US`. Necesita el nombre del campo donde se puede encontrar el
Page 8
capitulo-10.md 07/02/2018
idioma a utilizar. Este será con frecuencia el idioma del socio al que
se enviará el documento, almacenado en el campo `partner_id.lang`. En
nuestro caso, no tenemos un campo de socio, pero podemos utilizar el
usuario responsable, y la preferencia de idioma correspondiente está en
`user_id.lang`.
333
334 La función espera un nombre de plantilla, y lo procesará y traducirá.
Esto significa que necesitamos definir el contenido de la página de
nuestro informe en una plantilla separada, como se muestra a
continuación:
335
336 ```
337 <report id="action_todo_task_report_translated"
338 string="Translated To-do Tasks"
339 model="todo.task"
340 report_type="qweb-pdf"
341 name="todo_report.report_todo_task_translated"
342 paperformat="paperformat_euro_landscape"
343 />
344
345 <template id="report_todo_task_translated">
346 <t t-call="todo_report.report_todo_task_template"
347 t-lang="user.lang" >
348 <t t-set="docs"
349 t-value="docs" />
350 </t>
351 </t>
352 </template>
353
354 ```
355
356 ## Informes basados en SQL personalizado
357
358 El informe que construimos se basó en un conjunto de registros
regulares. Pero en algunos casos necesitamos transformar o agregar
datos de una manera que no sea fácil al procesar datos sobre la marcha,
como al procesar el informe.
359
360 Un enfoque para esto es escribir una consulta SQL para construir el
conjunto de datos que necesitamos, exponer esos resultados a través de
un modelo especial y tener nuestro trabajo de informe basado en un
conjunto de registros.
361
362 Para ello, crearemos un archivo `reports/todo_task_report.py` con este
código:
363
364 ```
365 # -*- coding: utf-8 -*-
366 from odoo import models, fields
367
368 class TodoReport(models.Model):
369 _name = 'todo.task.report'
370 _description = 'To-do Report'
371 _sql = """
372 CREATE OR REPLACE VIEW todo_task_report AS
373 SELECT *
374 FROM todo_task
375 WHERE active = True
376 """
377 name = fields.Char('Description')
378 is_done = fields.Boolean('Done?')
Page 9
capitulo-10.md 07/02/2018
431
432 </div>
433 </t>
434 </t>
435 </template>
436
437 </odoo>
438
439 ```
440
441 Para casos aún más complejos, podemos utilizar una solución diferente:
un asistente. Para esto debemos crear un modelo transitorio con líneas
relacionadas, donde el encabezado incluya parámetros de informe,
introducidos por el usuario, y las líneas tendrán los datos generados
que usará el informe. Estas líneas se generan mediante un método modelo
que puede contener cualquier lógica que podamos necesitar. Se
recomienda encarecidamente inspirarse en un informe similar existente.
442
443 ## Resúmen
444
445 En el capítulo anterior aprendimos sobre QWeb y cómo usarlo para
diseñar una vista Kanban. En este capítulo aprendimos sobre el motor de
informes QWeb y sobre las técnicas más importantes para generar
informes con el lenguaje de plantillas QWeb.
446
447 En el próximo capítulo, seguiremos trabajando con QWeb, esta vez para
construir páginas web. También aprenderemos a escribir controladores
web, proporcionando funciones más completas a nuestras páginas web.
Page 11
capitulo-11.md 07/02/2018
37
38 Primero agrega un archivo `todo_website/__ init__.py` con la siguiente
línea:
39
40 ```
41 from . import controllers
42 ```
43 A continuación, agregue un archivo `todo_website/controllers/__
init__.py` con la siguiente línea:
44
45 ```
46 from . import main
47 ```
48 Ahora agregue el archivo como tal para el controlador,
`todo_website/controllers/main.py`, con el siguiente código:
49
50 ```
51 # -*- coding: utf-8 -*-
52 from odoo import http
53
54 class Todo(http.Controller):
55
56 @http.route('/helloworld', auth='public')
57 def hello_world(self):
58 return('<h1>Hello World!</h1>')
59 ```
60
61
62 El módulo `odoo.http` proporciona las funciones web relacionadas con
Odoo. Nuestros controladores, responsables de la representación de
páginas, deben ser objetos heredados de la clase
`odoo.http.Controller`. El nombre real utilizado para la clase no es
importante; Aquí elegimos utilizar `Main`.
63
64 Dentro de la clase del controlador tenemos métodos, que coinciden
rutas, hace algún procesamiento, y luego devuelve un resultado; La
página que se mostrará al usuario.
65
66 El decorador `odoo.http.route` es lo que une un método a una ruta de
URL. Nuestro ejemplo utiliza la ruta `/hello`. Navegue hasta
http://localhost:8069/hello y recibirá un mensaje de `Hello World`. En
este ejemplo, el procesamiento realizado por el método es bastante
trivial: simplemente devuelve una cadena de texto con el marcado HTML
para el mensaje Hello World.
67
68 Probablemente haya notado que hemos añadido el argumento auth =
'public' a la ruta. Esto es necesario para que la página esté
disponible para usuarios no autenticados. Si lo eliminamos, sólo los
usuarios autenticados pueden ver la página. Si no hay sesión activa, en
su lugar se mostrará la pantalla de inicio de sesión.
69
70 ## Hola Mundo! Con una plantilla Qweb
71
72 El uso de cadenas de Python para construir HTML se tornará aburrido muy
rápido. Las plantillas QWeb hacen un trabajo mucho mejor en eso. Así
que vamos a mejorar nuestra página web de **Hola Mundo** para usar una
plantilla en su lugar.
73
74 Las plantillas de QWeb se agregan a través de archivos de datos XML, y
técnicamente son un tipo de vista, junto con vistas de formulario o
árbol. En realidad se almacenan en el mismo modelo, ir.ui.view.
Page 2
capitulo-11.md 07/02/2018
75
76 Como de costumbre, los archivos de datos que se cargan deben declararse
en el archivo de manifiesto, por lo que debes editar el archivo
`todo_website/__ manifest__.py` para agregar la clave:
77
78 ```
79 'data': ['views/todo_templates.xml'],
80 ```
81 A continuación, agrega el archivo de data, `views/todo_web.xml, con el
siguiente contenido:
82
83 ```
84 <odoo>
85 <template id="hello" name="Hello Template">
86 <h1>Hello World !</h1>
87 </template>
88 </odoo>
89 ```
90
91 ### Nota
92
93
94 > El elemento `<template>` es en realidad un acceso directo para
declarar un `<record>` para el modelo `ir.ui.view`, usando `type =
"qweb"`, y una plantilla `<t>` dentro de él.
95
96 Ahora necesitamos que nuestro método de controlador use esta plantilla:
97
98 ```
99 from odoo.http import request
100 # ...
101 @http.route('/hello', auth='public')
102 def hello(self, **kwargs):
103 return request.render('todo_website.hello')
104 ```
105 La representación de la plantilla se proporciona mediante `request`, a
través de su función `render()`.
106
107 ### Consejo
108
109 > Observa que agregamos `**kwargs` a los argumentos del método. Con
esto, si cualquier parámetro adicional proporcionado por la petición
HTTP, como una consulta de cadenas de texto o parámetros POST, puede
ser capturado por el diccionario kwargs. Esto hace que nuestro método
sea más robusto, ya que proporcionar parámetros inesperados no causará
errores.
110
111 ## Ampliación de funciones web
112
113 Extensibilidad es algo que esperamos en todas las características de
Odoo, y las características de la web no son una excepción. Y de
hecho podemos extender los controladores y plantillas existentes. Por
ejemplo, extenderemos nuestra página web de **Hola Mundo** para que
tome un parámetro con el nombre para saludar: usando la
`URL/hello?name=John` devolvería un saludo **Hello John!** .
114
115 La extensión se hace generalmente desde un módulo distinto, pero
funciona de igual manera dentro del mismo módulo. Para mantener las
cosas concisas y sencillas, lo haremos sin crear un nuevo módulo.
116
117 Vamos añadir un nuevo archivo `todo_website/controllers/extend.py` con
Page 3
capitulo-11.md 07/02/2018
el siguiente código:
118
119 ```
120 # -*- coding: utf-8 -*-
121 from odoo import http
122 from odoo.addons.todo_website.controllers.main import Todo
123
124 class TodoExtended(Todo):
125 @http.route()
126 def hello(self, name=None, **kwargs):
127 response = super(TodoExtended, self).hello()
128 response.qcontext['name'] = name
129 return response
130 ```
131 Aquí podemos ver lo que necesitamos hacer para extender un controlador.
132
133 Primero usamos una importación de Python para obtener una referencia a
la clase de controlador que queremos extender. Comparados con los
modelos, tienen un registro central, proporcionado por el objeto `env`,
donde se puede obtener una referencia a cualquier clase de modelo, sin
necesidad de conocer el módulo y el archivo que los implementa. Con los
controladores no tenemos eso, y necesitamos conocer el módulo y el
archivo que implementa el controlador que queremos ampliar.
134
135 A continuación tenemos que (re)definir el método desde el controlador
que se está extendiendo. Tiene que estar decorado con al menos el
simple `@http.route()` para que su ruta se mantenga activa.
Opcionalmente, podemos proporcionar parámetros a `route()`, y luego
estaremos reemplazando y redefiniendo sus rutas.
136
137 El método extendido `hello()` ahora tiene un parámetro de nombre. Los
parámetros pueden obtener sus valores de segmentos de la URL de la
ruta, de los parámetros de consulta de una cadena o de los parámetros
POST. En este caso, la ruta no tiene variable extraíble (lo
demostraremos en un momento), y como estamos manejando peticiones GET,
no POST, el valor del parámetro name será extraído de la consulta de
cadena en la URL. Una URL de prueba podría ser
`http://localhost:8069/hello?name=John`.
138
139 Dentro del método `hello()` ejecutamos el método heredado para obtener
su respuesta, y luego podemos modificarlo de acuerdo a nuestras
necesidades. El patrón común para los métodos de controlador es que
terminen con una instrucción para renderizar una plantilla. En nuestro
caso:
140
141 ```
142 return request.render('todo_website.hello')
143
144 ```
145
146 Esto genera un objeto `http.Response`, pero la representación real se
retrasa hasta el final del despacho.
147
148 Esto significa que el método de herencia todavía puede cambiar la
plantilla QWeb y el contexto a utilizar para la representación.
Podríamos cambiar la plantilla modificando `response.template`, pero no
lo necesitaremos. Preferimos modificar `response.qcontext` para agregar
la clave `name al contexto de renderizado.
149
150 No olvides agregar el nuevo archivo Python en
`todo_website/controllers/__init__.py:`
Page 4
capitulo-11.md 07/02/2018
151
152 ```
153 from . import main
154 from . import extend
155 ```
156
157 Ahora necesitamos modificar la plantilla `QWeb`, Para que haga uso de
esta información adicional. Agrega lo que sigue a
`todo/website/views/todo_extend.xml`:
158
159 ```
160 <odoo>
161 <template id="hello_extended"
162 name="Extended Hello World"
163
164 inherit_id="todo_website.hello">
165 <xpath expr="//h1" position="replace">
166 <h1>
167 Hello <t t-esc="name or 'Someone'" />!
168 </h1>
169 </xpath>
170 </template>
171 </odoo>
172 ```
173 Las plantillas de páginas web son documentos XML, al igual que los
otros tipos de vista Odoo, y podemos usar xpath para localizar
elementos y luego manipularlos, tal como podríamos con los otros tipos
de vista. La plantilla heredada se identifica en el elemento
`<template>` por el atributo `inherited_id`.
174
175 No debemos olvidar declarar este archivo de datos adicional en nuestro
manifiesto, `todo_website/__ manifest__.py`:
176
177 ```
178 'data': [
179 'Views/todo_web.xml',
180
181 'views/todo_extend.xml'
182 ],
183 ```
184 Después de esto, accediendo a `http://localhost:8069/hello?name=John
debe mostrarnos un mensaje **Hello John!**.
185
186 También podemos proporcionar parámetros a través de segmentos de URL.
Por ejemplo, podríamos obtener exactamente el mismo resultado de la URL
`http://localhost:8069/hello/John usando esta implementación alternativa:
187
188 ```
189 class TodoExtended(Todo):
190
191 @ Http.route (['/ hello', '/hello/<name>])
192 def hello(self, name=None, **kwargs):
193 response = super(TodoExtended, self).hello()
194 response.qcontext['name'] = name
195 return response
196 ```
197
198 Como puedes ver, las rutas pueden contener **placeholders**
correspondientes a los parámetros que se van a extraer y luego pasar al
método. Los **placeholders** también pueden especificar un convertidor
para implementar una asignación de tipo específica. Por ejemplo, `<int:
Page 5
capitulo-11.md 07/02/2018
234 ],
235 ```
236
237 Para utilizar el sitio web, también debemos modificar el controlador y
la plantilla.
238
239 El controlador necesita un argumento adicional `website=True` en la ruta:
240
241 ```
242 @http.route('/hello', auth='public', website=True)
243 def hello(self, **kwargs):
244 return request.render('todo_website.hello')
245 ```
246 Y la plantilla debe insertarse dentro del diseño general del sitio web:
247
248 ```
249 <template id="hello" name="Hello World">
250
251 <t t-call="website.layout">
252 <h1>Hello World!</h1>
253 </t>
254 </template>
255 ```
256 Con esto, el ejemplo **Hola Mundo** que utilizamos antes debe ahora ser
mostrado dentro de una página del sitio web de Odoo.
257
258 ## Adición de elementos CSS y JavaScript
259
260 Nuestras páginas web pueden necesitar algunos recursos adicionales de
CSS o JavaScript. Este aspecto de las páginas web es gestionado por el
sitio web, por lo que necesitamos una forma de decirle que también
utilice nuestros archivos.
261
262 Vamos a añadir un poco de CSS para agregar un efecto de tachado simple
para las tareas realizadas. Para ello, crea el archivo
`todo_website/static/src/css/index.css` con este contenido:
263
264 ```
265 .todo-app-done {
266 text-decoration: line-through;
267 }
268 ```
269 Luego tenemos que incluirlo en las páginas del sitio web. Esto se hace
agregándolos en la plantilla `website.assets_frontend` responsable de
cargar los activos específicos del sitio web. Edite el archivo de datos
todo_website / views / todo_templates.xml, para ampliar esa plantilla:
270
271 ```
272 <Odoo>
273 <Template id = "assets_frontend"
274 Name = "todo_website_assets"
275
276 Inherit_id = "website.assets_frontend">
277 <Xpath expr = "." Position = "inside">
278 <Link rel = "stylesheet" type = "text / css"
279 Href = "/ todo_website / static / src / css / index.css" />
280 </ Xpath>
281
282
283
284
Page 7
capitulo-11.md 07/02/2018
323
324 ```
325 <template id="index" name="Todo List">
326 <t t-call="website.layout">
327 <div id="wrap" class="container">
328 <h1>Todo Tasks</h1>
329
330 <!-- List of Tasks -->
331 <t t-foreach="tasks" t-as="task">
332 <div class="row">
333 <input type="checkbox" disabled="True"
334 t-att-checked=" 'checked' if task.is_done else {}" />
335 <a t-attf-href="/todo/{{slug(task)}}">
336 <span t-field="task.name"
337 t-att-class="'todo-app-done' if task.is_done
338 else ''" />
339 </a>
340 </div>
341 </t>
342
343 <!-- Add a new Task -->
344 <div class="row">
345 <a href="/todo/add" class="btn btn-primary btn-lg">
346 Add
347 </a>
348 </div>
349
350 </div>
351 </t>
352 </template>
353 ```
354
355 El código anterior utiliza la directiva `t-foreach` para mostrar una
lista de tareas. La directiva `t-att` usada en la casilla de
verificación de entrada nos permite agregar o no el atributo *checked*
dependiendo del valor `is_done`.
356
357 Tenemos una entrada de casilla de verificación, y queremos que se
compruebe si la tarea se realiza. En HTML, se comprueba una casilla de
verificación en función de que tenga o no el atributo *checked*. Para
ello usamos la directiva `t-att-NAME` para renderizar dinámicamente el
atributo checked dependiendo de una expresión. En este caso, la
expresión se evalúa como `None`, QWeb omitirá el atributo, lo cual es
conveniente para este caso.
358
359 Al procesar el nombre de la tarea, se utiliza la directiva `t-attf`
para crear dinámicamente la URL para abrir el formulario de detalle
para cada tarea específica. Utilizamos la función especial `slug()`
para generar una URL legible por humanos para cada registro. El enlace
no funcionará por ahora, ya que todavía estamos por crear el
controlador correspondiente.
360
361 En cada tarea también usamos la directiva `t-att` para establecer el
estilo `todo-app-done` solo para las tareas que se realizan.
362
363 Finalmente, tenemos un botón Añadir para abrir una página con un
formulario para crear una nueva Tarea. Lo usaremos para introducir el
manejo de formularios web a continuación.
364
365 ## La página de detalles de las tareas por hacer
366
Page 9
capitulo-11.md 07/02/2018
para enviar una nueva tarea por hacer y los campos disponibles serán el
nombre de la tarea, una persona (usuario) responsable de la tarea y un
archivo adjunto.
408
409 Debemos comenzar agregando la dependencia `website_form` a nuestro
módulo. Podemos reemplazar el módulo `website`, ya que mantenerlo
explícitamente sería redundante. En el `todo_website/__ manifest__.py`
edite la clave `depends` a:
410
411 ```
412 'Depende': ['todo_kanban',
413 'Website_form'
414
415
416
417 ],
418 ```
419 Ahora vamos a añadir la página con el formulario.
420
421 ## La página formulario
422
423 Podemos comenzar implementando el método del controlador para soportar
la renderización del formulario, en el archivo
`todo_website/controllers/ main.py`:
424
425 ```
426 @http.route('/todo/add', website=True)
427 def add(self, **kwargs):
428 users = request.env['res.users'].search([])
429 return request.render(
430 'todo_website.add', {'users': users})
431 ```
432
433 Se trata de un controlador sencillo, que muestra la plantilla
`todo_website.add` y que le proporciona una lista de usuarios, de modo
que pueda utilizarse para crear un cuadro de selección.
434
435 Ahora para la plantilla QWeb correspondiente. Podemos añadirlo al
archivo de datos `todo_website/views/todo_web.xml`:
436
437 ```
438 <template id="add" name="Add Todo Task">
439 <t t-call="website.layout">
440 <t t-set="additional_title">Add Todo</t>
441 <div id="wrap" class="container">
442 <div class="row">
443 <section id="forms">
444 <form method="post"
445 class="s_website_form
446 container-fluid form-horizontal"
447
448 action="/website_form/"
449 data-model_name="todo.task"
450 data-success_page="/todo"
451 enctype="multipart/form-data" >
452 <!-- Form fields will go here! -->
453 <!-- Submit button -->
454 <div class="form-group">
455 <div class="col-md-offset-3 col-md-7
456 col-sm-offset-4 col-sm-8">
457 <a
Page 11
capitulo-11.md 07/02/2018
458 class="o_website_form_send
459 btn btn-primary btn-lg">
460 Save
461 </a>
462
463 <span id="o_website_form_result"></span>
464 </div>
465 </div>
466
467 </form>
468 </section>
469 </div> <!-- rows -->
470 </div> <!-- container -->
471 </t> <!-- website.layout -->
472 </template>
473 ```
474
475 Como es de esperar, podemos encontrar el elemento `<t
t-call="website.layout"` específico de Odoo, responsable de insertar la
plantilla dentro del diseño del sitio web, y `<t
t-set="additional_title">` que establece un titulo adicional, esperado
por el layout del sitio web.
476
477 Para el contenido, la mayoría de lo que podemos ver en esta plantilla
se puede encontrar en un típico formulario Booststrap CSS. Pero también
tenemos algunos atributos y clases de CSS que son específicos para los
formularios del sitio web. Los marcamos en negrita en el código, por lo
que es más fácil identificarlos.
478
479 Las clases CSS son necesarias para que el código JavaScript pueda
realizar correctamente su lógica de manejo de formularios. Y luego
tenemos algunos atributos específicos en el elemento `<form>`:
480
481 + `action` es un atributo de formulario estándar, pero debe tener el
valor "/website_form/". Se requiere la barra diagonal.
482 + `Data-model_name` identifica el modelo al que escribir y se pasará al
controlador `/website_form.
483 + `Data-success_page` es la URL a redireccionar después de una
presentación de formulario correcta. En este caso, se nos enviará de
nuevo a la lista de tareas.
484
485 No necesitaremos proporcionar nuestro propio método de controlador para
manejar el envío de formularios. La ruta `/website_form` lo hará por
nosotros. Toma toda la información que necesita del formulario,
incluyendo los atributos específicos que acabamos de describir, y luego
realiza validaciones esenciales en los datos de entrada, y crea un
nuevo registro en el modelo de destino.
486
487 Para casos de uso avanzado, podemos forzar que se use un método de
controlador personalizado. Para ello debemos añadir un atributo
`data-force_action` al elemento `<form>`, con la palabra clave para que
el controlador objetivo a utilizar. Por ejemplo,
`data-force_action="todo-custom"` tendría la solicitud para llamar a la
URL `/website_form/todo-custom`. Entonces deberíamos proporcionar un
método de controlador adjunto a esa ruta. Sin embargo, hacer esto
quedará fuera de nuestro alcance aquí.
488
489 Todavía tenemos que terminar nuestro formulario, agregando los campos
para obtener las entradas del usuario. Dentro del elemento `<form>`,
añade:
490
Page 12
capitulo-11.md 07/02/2018
491 ```
492 <!-- Description text field, required -->
493 <div class="form-group form-field">
494 <div class="col-md-3 col-sm-4 text-right">
495 <label class="control-label" for="name">To do*</label>
496 </div>
497 <div class="col-md-7 col-sm-8">
498 <input name="name" type="text" required="True"
499
500 class="o_website_from_input
501
502 form-control" />
503 </div>
504 </div>
505
506 <!-- Add an attachment field -->
507 <div class="form-group form-field">
508 <div class="col-md-3 col-sm-4 text-right">
509 <label class="control-label" for="file_upload">
510 Attach file
511 </label>
512 </div>
513 <div class="col-md-7 col-sm-8">
514 <input name="file_upload" type="file"
515
516 class="o_website_from_input
517
518
519
520 form-control" />
521 </div>
522 </div>
523 ```
524 Aquí estamos agregando dos campos, un campo de texto regular para la
descripción y un campo de archivo, para cargar un archivo adjunto. Todo
el marcado se puede encontrar en los formularios regulares Bootstrap, a
excepción de la clase o_website_from_input, necesarios para la lógica
de formulario de `website` para preparar los datos para enviar.
525
526 La lista de selección de usuario no es muy diferente excepto que
necesita usar una directiva t-foreach QWeb para representar la lista de
usuarios seleccionables. Podemos hacer esto porque el controlador
recupera ese conjunto de registros y lo pone a disposición de la
plantilla bajo el nombre users:
527
528 ```
529 <!-- Select User -->
530 <div class="form-group form-field">
531 <div class="col-md-3 col-sm-4 text-right">
532 <label class="control-label" for="user_id">
533 For Person
534 </label>
535 </div>
536 <div class="col-md-7 col-sm-8">
537 <select name="user_id"
538
539 class="o_website_from_input
540
541 form-control" >
542
543 <t t-foreach="users" t-as="user">
Page 13
capitulo-11.md 07/02/2018
634
635 Ahora debes tener un buen entendimiento sobre lo esencial de las
características del sitio web. Hemos visto cómo usar controladores web
y plantillas QWeb para renderizar páginas web dinámicas. A
continuación, aprendimos a utilizar el complemento de sitio web y crear
nuestras propias páginas para ello. Por último, hemos introducido el
complemento de formularios de sitios web que nos ayudó a crear un
formulario web. Estos deben proporcionarnos las habilidades básicas
necesarias para crear las características del sitio web.
636
637 A continuación, aprenderemos cómo tener aplicaciones externas
interactuar con nuestras aplicaciones Odoo.
Page 16
capitulo-12.md 07/02/2018
30
31 > **nota**
32
33 > Los usuarios de Windows pueden encontrar un instalador para Python y
también ponerse rápidamente al día. Los paquetes oficiales de
instalación se pueden encontrar en https://www.python.org/downloads/.
34
35 > Si es un usuario de Windows y tiene Odoo instalado en su máquina, es
posible que se pregunte por qué no tiene un intérprete de Python, y se
necesita una instalación adicional. La respuesta corta es que la
instalación de Odoo tiene un intérprete de Python incrustado que no se
utiliza fácilmente fuera.
36
37
38
39 ## Llamadas a la API de Odoo utilizando XML-RPC
40
41 El método más sencillo para acceder al servidor es utilizar XML-RPC.
Podemos usar la biblioteca xmlrpclib de la biblioteca estándar de
Python para esto. Recuerde que estamos programando un cliente para
conectarnos a un servidor, por lo que necesitamos una instancia de
servidor Odoo en la que se ejecute para conectarse. En nuestros
ejemplos, asumiremos que una instancia de servidor Odoo se ejecuta en
la misma máquina (localhost), pero puede utilizar cualquier dirección
IP o nombre de servidor accesible si el servidor se ejecuta en una
máquina diferente.
42
43 ## Abriendo una conexión XML-RPC
44
45 Vamos a tener un primer contacto con la API externa de Odoo. Inicie una
consola de Python y escriba lo siguiente:
46
47 ```
48 >>> import xmlrpclib
49 >>> srv = 'http://localhost:8069'
50 >>> common = xmlrpclib.ServerProxy('%s/xmlrpc/2/common' % srv)
51 >>> common.version()
52 {'server_version_info': [10, 0, 0, 'final', 0, ''], 'server_serie':
'10.0', 'server_version': '10.0', 'protocol_version': 1}
53 ```
54 Aquí, importamos la biblioteca xmlrpclib y luego configuramos una
variable con la información para la dirección del servidor y el puerto
de escucha. Siéntete libre de adaptarlos a tu configuración específica.
55
56 A continuación, configuramos el acceso a los servicios públicos del
servidor (que no requieren un inicio de sesión), expuestos en el
`/xmlrpc/2/common` endpoint. Uno de los métodos que está disponible es
`version ()`, que inspecciona la versión del servidor. Lo usamos para
confirmar que podemos comunicarnos con el servidor.
57
58 Otro método público es `authenticate()`. De hecho, esto no crea una
sesión, como usted puede ser llevado a creer. Este método sólo confirma
que el nombre de usuario y la contraseña se aceptan y devuelve el ID de
usuario que se debe utilizar en las solicitudes en lugar del nombre de
usuario, como se muestra aquí:
59
60 ```
61 >>> db = 'todo'
62 >>> user, pwd = 'admin', 'admin'
63 >>> uid = common.authenticate(db, user, pwd, {})
64 >>> print uid
Page 2
capitulo-12.md 07/02/2018
65 ```
66
67 Si las credenciales de inicio de sesión no son correctas, se devuelve
un valor False, en lugar de un ID de usuario.
68
69 ## Lectura de datos desde el servidor
70
71 Con XML-RPC, no se mantiene ninguna sesión y las credenciales de
autenticación se envían con cada solicitud. Esto agrega algo de
sobrecarga al protocolo, pero lo hace más fácil de usar.
72
73 A continuación, establecemos el acceso a los métodos de servidor que
necesitan un inicio de sesión para acceder. Éstos se exponen en el
endpoint `/xmlrpc2/object`, como se muestra a continuación:
74
75 ```
76 >>> api = xmlrpclib.ServerProxy('%s/xmlrpc/2/object' % srv)
77 >>> api.execute_kw(db, uid, pwd, 'res.partner', 'search_count'[[]])
78 40
79 ```
80
81 Aquí, estamos haciendo nuestro primer acceso a la API del servidor,
realizando un conteo en los registros de **Partner**. Los métodos se
llaman usando el método `execute_kw()` que toma los siguientes
argumentos:
82
83 + El nombre de la base de datos para conectarse a
84 + El ID de usuario de la conexión
85 + La contraseña de usuario
86 + El nombre del identificador del modelo de destino
87 + El método para llamar
88 + Una lista de argumentos posicionales
89 + Un diccionario opcional con argumentos de palabra clave
90
91 El ejemplo anterior llama al método `search_count` del modelo
`res.partner` con un argumento de posición, [], y sin argumentos de
palabra clave. El argumento posicional es un dominio de búsqueda; Ya
que estamos proporcionando una lista vacía, cuenta todos los Partners.
92
93 Las acciones frecuentes son buscar y leer. Cuando se llama desde el
RPC, el método `search` devuelve una lista de identificadores que
coinciden con un dominio. El método `browse` no está disponible en el
RPC, y leer debe ser utilizado en su lugar para dar una lista de IDs de
registro y recuperar sus datos, como se muestra en el código siguiente:
94
95 ```
96 >>> api.execute_kw(db, uid, pwd, 'res.partner', 'search',
[[('country_id', '=', 'be'), ('parent_id', '!=', False)]])
97 [18, 33, 23, 22]
98 >>> api.execute_kw(db, uid, pwd, 'res.partner', 'read', [[18]],
{'fields': ['id', 'name', 'parent_id']})
99 [{'parent_id': [8, 'Agrolait'], 'id': 18, 'name': 'Edward Foster'}]
100 ```
101
102 Ten en cuenta que para el método `read`, estamos utilizando un
argumento de posición para la lista de IDs, [18] y un argumento de
palabra clave, `fields`. También podemos observar que los campos
relacionales many-to-one se recuperan como un par, con el ID del
registro relacionado y el nombre para mostrar. Eso es algo que debe
tener en cuenta al procesar los datos en su código.
103
Page 3
capitulo-12.md 07/02/2018
134 
135
136
137 Para mayor claridad, lo dividiremos en dos archivos: uno relacionado
con las interacciones con el servidor backend, `note_api.py` y otro con
la interfaz gráfica de usuario, `note_gui.py`.
138
139 ## Capa de comunicación con Odoo
140
141 Crearemos una clase para configurar la conexión y almacenar su
información. Debe exponer dos métodos: `get()` para recuperar datos de
tareas y `set()` para crear o actualizar tareas.
142
143 Seleccione un directorio para alojar los archivos de la aplicación y
cree el archivo `note_api.py`. Podemos comenzar agregando el
constructor de la clase, de la siguiente manera:
144
145 ```
146 import xmlrpclib
147 class NoteAPI():
148 def __init__(self, srv, db, user, pwd):
149 common = xmlrpclib.ServerProxy(
150 '%s/xmlrpc/2/common' % srv)
151 self.api = xmlrpclib.ServerProxy(
152 '%s/xmlrpc/2/object' % srv)
153 self.uid = common.authenticate(db, user, pwd, {})
154 self.pwd = pwd
155 self.db = db
156 self.model = 'note.note'
157
158 ```
159 Aquí almacenamos toda la información necesaria en el objeto creado para
ejecutar llamadas en un modelo: la referencia de la API, uid,
contraseña, nombre de la base de datos y el modelo a utilizar.
160
161 A continuación, definiremos un método auxiliar para ejecutar las
llamadas. Se aprovecha de los datos almacenados del objeto para
proporcionar una firma de función más pequeña, como se muestra a
continuación:
162
163 ```
164 def execute(self, method, arg_list, kwarg_dict=None):
165 return self.api.execute_kw(
166 self.db, self.uid, self.pwd, self.model,
167 method, arg_list, kwarg_dict or {})
168 ```
169
170 Ahora podemos usarlo para implementar los métodos `get()` y `set()` de
nivel superior.
171
172 El método `get()` aceptará una lista opcional de ID para recuperar. Si
no aparece ninguno, se devolverán todos los registros:
173
174 ```
175 def get(self, ids=None):
176 domain = [('id',' in', ids)] if ids else []
177 fields = ['id', 'name']
178 return self.execute('search_read', [domain, fields])
179 ```
180
181 El método `set()` tendrá el texto de la tarea a escribir y un ID
Page 5
capitulo-12.md 07/02/2018
363
364 La instancia `erppeek.Client` utilizada para la conexión también está
disponible a través de la variable cliente.
365
366 En particular, proporciona una alternativa al cliente web para
administrar los módulos complementarios instalados:
367
368 + `client.modules()`: lista los módulos disponibles o instalados
369 + `client.install()`: realiza la instalación del módulo
370 + `client.upgrade()`: realiza actualizaciones de módulos
371 + `client.uninstall()`: desinstala los módulos
372 Por lo tanto, erppeek también puede proporcionar un buen servicio como
una herramienta de administración remota para los servidores Odoo.
373
374
375 ## Sumario
376
377 Nuestro objetivo para este capítulo fue aprender cómo funciona la API
externa y de qué es capaz. Empezamos a explorarla usando un simple
cliente XML-RPC de Python, pero la API externa se puede usar desde
cualquier lenguaje de programación. De hecho, los documentos oficiales
proporcionan ejemplos de código para Java, PHP y Ruby.
378
379 Existen varias bibliotecas para manejar XML-RPC o JSON-RPC, algunas
genéricas y otras específicas para usar con Odoo. Tratamos de no
señalar ninguna bibliotecas en particular, a excepción de erppeek, ya
que no sólo es un envoltorio probado para el Odoo / OpenERP XML-RPC,
sino porque también es una herramienta invaluable para la gestión de
servidores remotos y la inspección.
380
381 Hasta ahora, utilizamos nuestras instancias de servidor Odoo para
desarrollo y pruebas. Pero para tener un servidor de grado de
producción, hay configuraciones adicionales de seguridad y optimización
que deben hacerse. En el próximo capítulo, nos centraremos en ellos.
382
383
384
Page 10
capitulo-13.md 07/02/2018
40
41 $ sudo su -c "createuser -s $(whoami)" postgres
42 ```
43
44 Ejecutaremos Odoo desde el código fuente, pero antes de eso necesitamos
instalar las dependencias requeridas. Estos son los paquetes Debian
necesarios:
45
46 ```
47 $ sudo apt-get install git python-pip python2.7-dev -y
48
49 $ sudo apt-get install libxml2-dev libxslt1-dev
50
51 libevent-dev \
52 libsasl2-dev libldap2-dev libpq-dev
53
54
55 libpng12-dev libjpeg-dev \
56 poppler-utils
57
58
59 node-less node-clean-css -y
60
61 ```
62 No debemos olvidarnos de instalar wkhtmltox, que es necesario para
imprimir informes:
63
64 ```
65 $ wget
http://nightly.odoo.com/extra/wkhtmltox-0.12.1.2_linux-jessie-amd64.deb
66
67 $ sudo dpkg -i wkhtmltox-0.12.1.2_linux-jessie-amd64.deb
68
69 $ sudo apt-get -fy install
70 ```
71
72 Las instrucciones de instalación informarán un error de dependencias
faltantes, pero el último comando obliga a la instalación de esas
dependencias y termina correctamente la instalación.
73
74 Ahora solo nos faltan los paquetes de Python requeridos por Odoo.
Muchos de ellos también tienen paquetes para Debian / Ubuntu. El
paquete oficial de instalación de Debian los usa, y puedes encontrar
los nombres de los paquetes en el código fuente Odoo, en el archivo
`debian/control`.
75
76 Sin embargo, estas dependencias de Python también se pueden instalar
directamente desde el **Python Package Index (PyPI)**. Esto es más
amigable para aquellos que prefieren instalar Odoo en `virtualenv`. La
lista de paquetes requerida se encuentra en el archivo
`requirements.txt` de Odoo, como es habitual en los proyectos basados
en Python. Podemos instalarlos con los siguientes comandos:
77 ```
78 $ sudo -H pip install --upgrade pip # Ensure pip latest version
79
80 $ wget https://raw.githubusercontent.com/odoo/odoo/10.0/requirements.txt
81
82 $ sudo -H pip install -r requirements.txt
83 ```
84
85 Ahora que tenemos todas las dependencias instaladas, servidor de bases
Page 2
capitulo-13.md 07/02/2018
127
128 ## Consejo
129
130 Git seguramente será una herramienta invaluable para administrar las
versiones de tus implementaciones Odoo. Acabamos de rayar la superficie
de lo que se puede hacer para administrar las versiones de código. Si
no está familiarizado con Git, vale la pena aprender más sobre él. Un
buen punto de partida es
[http://git-scm.com/doc](http://git-scm.com/doc).
131
132 Por ahora deberíamos tener todo lo necesario para ejecutar Odoo desde
la fuente. Podemos comprobar que se inicia correctamente y luego salir
de la sesión del usuario dedicado:
133
134 ```
135 $ /home/odoo/odoo-10.0/odoo-bin --help
136
137 $ exit
138 ```
139
140
141 A continuación, configuraremos algunos archivos y directorios a nivel
de sistema para ser usado por el servicio del sistema.
142
143 ## Configuración del archivo de configuración
144
145 Si se agrega la opción `--save` al iniciar un servidor Odoo, se guarda
la configuración utilizada en el archivo `~/.odoorc`. Podemos utilizar
el archivo como punto de partida para nuestra configuración de
servidor, que se almacenará en `/etc/odoo`, como se muestra en el
siguiente código:
146
147 ```
148 $ sudo su -c "~/odoo-10.0/odoo-bin -d odoo-prod --save
--stop-after-init" odoo
149 ```
150
151 Esto tendrá los parámetros de configuración que usará nuestra instancia
del servidor.
152
153 ## Consejo
154
155 El archivo de configuración anterior .openerp_serverrc sigue siendo
compatible y se utiliza si se encuentra. Esto puede causar cierta
confusión al configurar Odoo 10 en una máquina que también se utilizó
para ejecutar versiones anteriores de Odoo. En este caso, la opción
`--save` podría estar actualizando el archivo .openerp_serverrc en vez
de .odoorc.
156
157 A continuación, tenemos que colocar el archivo de configuración en la
ubicación esperada:
158
159 ```
160 $ sudo mkdir /etc/odoo
161
162 $ sudo cp /home/odoo/.odoorc /etc/odoo/odoo.conf
163
164 $ sudo chown -R odoo /etc/odoo
165 ```
166
167 También debemos crear el directorio donde el servicio Odoo almacenará
Page 4
capitulo-13.md 07/02/2018
282 ```
283
284 Y para verificar su status corre esto:
285
286 ```
287 $ sudo systemctl odoo status
288 ```
289 Finalmente si deses deterner el servicio puede hacerlo con este comenado:
290
291 ```
292 $ sudo systemctl odoo stop
293 ```
294
295 ## Creación de un servicio Upstart / sysvinit
296
297 Si está utilizando un sistema operativo más antiguo, como Debian 7,
Ubuntu 15.04 o incluso 14.04, es probable que su sistema sea `sysvinit`
en `Upstart`. Para este propósito, ambos deben comportarse de la misma
manera. Muchos servicios VPS en la nube todavía se basan en imágenes de
Ubuntu 14.04, por lo que este podría ser un escenario que puede
encontrar al implementar su servidor Odoo.
298
299 Muchos servicios VPS en la nube todavía se basan en imágenes de Ubuntu
14.04, por lo que este podría ser un escenario que puede encontrar al
implementar su servidor Odoo.
300
301 El código fuente de Odoo incluye un script de inicio utilizado para la
distribución empaquetada de Debian. Podemos utilizarlo como nuestro
script `init` de servicio con pequeñas modificaciones, de la siguiente
manera:
302
303 ```
304 $ sudo cp /home/odoo/odoo-10.0/debian/init /etc/init.d/odoo
305
306 $ sudo chmod +x /etc/init.d/odoo
307 ```
308
309 En este punto, puede que desees comprobar el contenido del script
`init`. Los parámetros clave se asignan a las variables en la parte
superior del archivo. Un ejemplo de esto:
310
311 ```
312 PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
313 DAEMON=/usr/bin/odoo
314 NAME=odoo
315 DESC=odoo
316 CONFIG=/etc/odoo/odoo.conf
317 LOGFILE=/var/log/odoo/odoo-server.log
318 PIDFILE=/var/run/${NAME}.pid
319 USER=odoo
320 ```
321
322 Estas variables deben ser adecuadas y prepararemos el resto de la
configuración con sus valores por defecto en mente. Pero, por supuesto,
puedes cambiarlos para que se adapten mejor a tus necesidades.
323
324 La variable `USER` es el usuario del sistema en el que se ejecutará el
servidor. Ya hemos creado el usuario odoo esperado.
325
326 La variable `DAEMON` es la ruta al ejecutable del servidor. Nuestro
ejecutable real para iniciar Odoo está en un lugar diferente, pero
Page 8
capitulo-13.md 07/02/2018
463 ```
464 Upstream backend-odoo {
465 Servidor 127.0.0.1:8069;
466 }
467 Servidor {
468 ubicación / {
469 Proxy_pass http: // backend-odoo;
470 }
471 }
472 ```
473
474 Para probar si la configuración editada es correcta, utilice el
siguiente comando:
475
476 ```
477 $ sudo nginx -t
478 ```
479
480 Si encuentra errores, confirme que el archivo de configuración está
correctamente escrito. Además, un problema común es que el HTTP
predeterminado sea tomado por otro servicio, como Apache o el sitio web
predeterminado de Nginx. Compruebe las instrucciones dadas antes para
asegurarse de que este no es el caso, luego reinicie Nginx. Después de
esto, podemos tener Nginx para recargar la nueva configuración de la
siguiente manera:
481
482 ```
483 $ sudo /etc/init.d/nginx reload
484
485 ```
486
487
488 Ahora podemos confirmar que Nginx está redirigiendo el tráfico al
servidor backend Odoo:
489
490 ```
491 $ curl http://localhost
492
493 <Html> <head> <script> window.location = '/ web' + ubicación.hash; </
script> </ head> </ html>
494 ```
495
496 ## Aplicación de HTTPS
497
498 A continuación, debemos instalar un certificado para poder usar SSL.
Para crear un certificado autofirmado, siga estos pasos:
499
500 ```
501 $ sudo mkdir /etc/nginx/ssl && cd/etc/nginx/ssl
502
503 $ sudo openssl req -x509 -nuevo rsa: 2048 -keyout key.pem -out cert.pem
-days 365 -nodos
504
505 $ sudo chmod a-wx * # hacer que los archivos sean de solo lectura
506
507
508 $ sudo chown www-data:root * # acceso sólo al grupo www-data
509 ```
510
511 Esto crea un directorio `ssl/` dentro del directorio `/etc/nginx/` y
crea un certificado SSL auto-firmado sin contraseña. Al ejecutar el
Page 12
capitulo-13.md 07/02/2018
555
556 Por razones de seguridad, es importante que Odoo se asegure de que el
parámetro proxy_mode esté establecido en True. La razón de esto es que,
cuando Nginx actúa como un proxy, toda la solicitud parecerá venir del
propio servidor en lugar de la dirección IP remota. Establecer el
encabezado X-Forwarded-For en el proxy y habilitar --proxy-mode
resuelve eso. Pero activar el modo -proxy sin forzar este encabezado en
el nivel de proxy permitiría a cualquiera falsificar su dirección remota.
557
558 Al final, la directiva de ubicación define que todas las solicitudes se
pasan al backend-odoo upstream.
559
560 Vuelve a cargar la configuración y deberíamos tener nuestro servicio
Odoo trabajando a través de HTTPS, como se muestra en los siguientes
comandos:
561
562 ```
563 $ sudo nginx -t
564
565 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
566
567 nginx: configuration file /etc/nginx/nginx.conf test is successful
568
569 $ sudo service nginx reload
570
571 * Reloading nginx configuration nginx
572
573
574 ...done.
575
576
577 $ curl -k https://localhost
578
579
580 <html><head><script>window.location = '/web' +
location.hash;</script></head></html>
581 ```
582
583 La última salida confirma que el cliente web Odoo está siendo
distribuido a través de HTTPS.
584
585 ## Consejo
586
587 Para el caso particular en el que se está usando un Odoo POSBox,
necesitamos agregar una excepción para que el /pos/URL pueda acceder a
él en modo HTTP. El POSBox se encuentra en la red local pero no tiene
SSL habilitado. Si la interfaz POS está cargada en HTTPS, no podrá
contactar al POSBox.
588
589 ## Optimizaciones de Nginx
590
591 Ahora, es tiempo para algunos ajuste fino de los ajustes de Nginx. Se
recomiendan para habilitar el búfer de respuesta y la compresión de
datos que deberían mejorar la velocidad del sitio web. También
establecemos una ubicación específica para los registros.
592
593 Las siguientes configuraciones deben agregarse dentro del servidor que
escucha en el puerto 443, por ejemplo, justo después de las
definiciones de proxy:
594
595 ```
Page 14
capitulo-13.md 07/02/2018
684 $ exit
685
686 $ sudo service odoo restart
687 ```
688
689 En cuanto a la política de lanzamiento de Odoo, no se publican
versiones menores. Se espera que las ramas de GitHub representen la
última versión estable. Las construcciones nocturnas se consideran el
último lanzamiento oficial estable.
690
691 En la frecuencia de actualización, no hay ningún motivo para actualizar
con demasiada frecuencia, pero tampoco esperar un año entre
actualizaciones. Realizar una actualización cada pocos meses debería
estar bien. Y por lo general, un reinicio del servidor será suficiente
para habilitar las actualizaciones de código, y las actualizaciones de
módulos no deberían ser necesarias.
692
693 Por supuesto, si necesita una corrección de errores específica,
probablemente debería realizarse una actualización anterior. También
recuerde tener cuidado con las divulgaciones de errores de seguridad en
los canales públicos-GitHub Issues o en la lista de correo de la
comunidad. Como parte del servicio, los clientes de odoo entreprice
pueden esperar notificaciones por correo electrónico tempranas de este
tipo de problemas.
694
695 # Sumario
696
697 En este capítulo, aprendiste sobre los pasos adicionales para
configurar y ejecutar Odoo en un servidor de producción basado en
Debian. Se visitaron las configuraciones más importantes del archivo de
configuración y se aprendió cómo aprovechar el modo de
multiprocesamiento.
698
699 Para mejorar la seguridad y la escalabilidad, también aprendiste a usar
Nginx como un proxy inverso frente a nuestros procesos de servidor Odoo.
700
701 Esto debería cubrir lo esencial de lo que se necesita para ejecutar un
servidor Odoo y proporcionar un servicio estable y seguro a sus usuarios.
702
703 Para obtener más información sobre Odoo, también debe consultar la
documentación oficial en [ https://www.odoo.com/documentation](
https://www.odoo.com/documentation). Algunos temas se tratan con más
detalle allí, y también encontrará temas no cubiertos en este libro.
704
705 También hay otros libros publicados sobre Odoo que también puede ser
útil. Pack Publishing tiene algunos en su catálogo, y en particular el
Odoo Development Cookbook proporciona material más avanzado, cubriendo
más temas no discutidos aquí.
706
707 Y finalmente, Odoo es un producto de código abierto con una comunidad
vibrante. Involucrarse, hacer preguntas y contribuir es una gran manera
no sólo para aprender, sino también para construir una red de negocios.
Sobre esto no podemos dejar de mencionar la Odoo Community Association
(OCA), promoviendo la colaboración y la calidad del código abierto.
Puede obtener más información al respecto en
[odoo-comunity.org](odoo-comunity.org).
Page 17