Beruflich Dokumente
Kultur Dokumente
Ciabay S. A.
Informtica
Fadel Damen Schreiner
Hernandarias, 15/03/2012
Table of Contents
1 INTRODUCCIN .................................................................................................V
2 BAJAR Y INSTALAR OPENERP 6.1 EN UBUNTU 11.10..............................VI
2.1
2.2
2.3
2.4
2.5
2.6
2.7
.
7.7 DOMINIOS EN CAMPOS DE LAS CLASES.................................................................................... LX
7.8 DEPURACIN DE CDIGO CON APTANA.................................................................................. LXII
7.9 UTILIZACIN DE CAMPOS TIPO FUNCIN Y OBJETOS DE ORM................................................... LXV
7.10 EVENTOS EN LAS VISTAS................................................................................................. LXVI
7.11 CONTRAINTS............................................................................................................... LXVIII
7.12 HERENCIA EN METODOS.................................................................................................. LXIX
7.13 ATRIBUYENDO VALOR DEFAULT A CAMPOS........................................................................... LXX
7.14 MANEJANDO COLORES EN EL GRID................................................................................... LXXI
7.14.1 Uso de caracteres especiales en las vistas........................................................lxxi
7.15 VISTA TIPO CALENDARIO. ............................................................................................. LXXVI
7.16 VISTAS DEL TIPO GANTT.............................................................................................. LXXVII
7.17 VISTAS DEL TIPO GRAFICOS (GRAPHS)............................................................................ LXXVIII
7.18 VISTAS TIPO SEARCH (BUSQUEDA)................................................................................... LXXIX
List of Figures
Ilustracin 1: Complementos Gedit (recortes)..........................................................xviii
Ilustracin 2: Gedit code completation.......................................................................xix
Ilustracin 3: Aptana IDE.............................................................................................xx
List of Tables
Appendices
1. Introduccin
1 Introduccin
En este manual queremos mostrar como desarrollar un modulo para openerp que
abran ja una gran parte del conocimiento necesario para administrar nuevos desarrollos. Todo va estar basado en la versin de openerp 6.1 el modulo que vamos a desarrollar va llamarse openacademy que ser un sistema para el control de los cursos ministrados en openERP con sus participantes.
Inicialmente vamos a actualizar los repositorios de nuestro linux, para tener todos
los paquetes necesarios para su utilizacin.
Tabla 1: Actualizacin de los paquetes de ubuntu
$ sudo apt-get update
$ sudo apt-get dist-upgrade
2.2
2.3
Luego sera necesario crear el usuario que va correr las nuevas bases de datos de
openerp, que son creadas automaticamente por el openerp server.
Tabla 4: Crear usuario para la base de datos
$ sudo su postgres
createuser --createdb --username postgres --no-createrole --pwprompt openerp
Enter password for new role: ******
Enter it again: ******
Shall the new role be a superuser? (y/n) y
Para que corra el openerp 6.1 en el ubuntu es necesario instalar algunas bibliotecas
de python que lo instalamos con el comando abajo:
Tabla 5: Instalando las bibliotecas python para correr openerp
$ sudo apt-get install python python-psycopg2 python-reportlab python-egenix-mxdatetime python-tz python-pychart python-mako \
python-pydot python-lxml python-vobject python-yaml python-dateutil python-pychart python-webdav python-cherrypy3 python-formencode \
python-pybabel python-simplejson python-pyparsing python-werkzeug python-openid
Bazaar es una herramienta para control de versiones utilizada por la cannica creadora de ubuntu, es una herramienta poderosa en que se utiliza para mantener los codigos fuentes de openerp como tambien es utilizada por la comunidad para mantener
tambin los cdigos de los mdulos extra-oficiales. Es una herramienta fundamental
para que trabajemos con openerp.
Para instalar utilzamos tambien el repositorio de ubuntu.
Tabla 6: Instalacin del bazaar
$ sudo apt-get install bzr
2.6
Openerp esta dividido en varias partes, como descrito abajo. Y son bajados en varios directorios separados.
addons Carpeta con los mdulos core de OpenERP
Si bien vemos que esta dividido en varias partes, pero vale resaltar que tenemos
una carpeta arriba que es de la localizacin brasilera, esto esta asi porque fue desarrollado un script por ellos para bajar todos estos componentes, si no lo utilizamos tendramos que bajar uno a uno y como no lo vamos simplemente a utilizar no nos causa
ningn problema y nos facilita para bajar e instalar todos los componentes.
Vamos crear la estructura necesario para bajar los fuentes.
7
Nos baja en la carpeta openerp-br el script para bajar todos los funtes y luego digitamos.
Tabla 9: Bajando Fuentes
$ cd openerp-br
$ ./bzr_set.py
Esto va tardar bastante. Ahora tenemos todas las carpetas que mencionamos arriba,
web -addons client etc.
2.7
En este capitulo vamos a configurar el openerp para que lea todos los archivos necesarios y tambin para que arranque en la inicializacin de nuestro sistema ubuntu.
En primer lugar vamos crear los archivos para que el openerp pueda crear los logs.
$ exit
$ cd /var/log
$ sudo mkdir openerp
$ sudo chown openerp:openerp openerp
As una vez que tenemos estos directorios ya creados vamos por la configuracin
de openerp.
El openerp necesita que tengamos un archivo conf que contenga los usuarios y
contrasea del administrador de la base de datos. Un archivo modelo basico se puede
encontrar en
/opt/openerp/v6.1/openerp-br/server/install/openerp-server.conf
2.8
Configurar el openERP para que corra como daemon es para que corra el openerp
al iniciar la maquina automaticamente, y quede como servicio.
Copiamos el script para sus lugares correctos.
$ sudo cp /opt/openerp/v6.1/openerp-br/server/debian/openerp.init /etc/init.d/openerp-server
Si todo corre bien, podremos ya poner para ejecutar en el inicio del sistema.
$ cd /etc/init.d
$ sudo update-rc.d openerp-server defaults
2.9
Tenemos un script utilizado para bajar el codigo fuente de openerp de diversas versiones, incluso la versin de dearrollo que podriamos ver las mejorias en la trama
trunk.
Yo personalmente me gusta usar esta versin que normalmente esta mas estable, y
queda facil correr el openerp-server para el desarrollo de modulos. Esto se da ya que
al desarrollar modulos siempre es necesario bajar y volver a subir openerp-server para
volver a cargar el modulo desarrollado.
10
$ sudo su - openerp
$ mkdir /opt/openerp/dev
$ cd /opt/openerp/dev
$ bzr cat -d lp:~openerp-dev/openerp-tools/trunk setup.sh | sh
De esta forma podremos bajar e ejecutar varias versiones de openerp por ejemplo
vamos bajar la versin 6.1.
$ make init-v61
11
12
Para ver lo grandioso que es el openERP vamos instalar todos los modulos para eso
solamente tendremos que clicar en instalar en cada uno de los arriba mostrados. Pero
Porque al clicar por em crm automaticamente OpenERP nos brinda ya una pantalla,
con algunas listas y graficos y ya no tenemos disponibles los otros modulos para istalar ? Esto es normal y para mostrar su funcionamiento cada modulo tenemos una herramiente espetacular, que se llama dashboard que puede ser customizada por el usuario y muestra los dados para seguimiento del modulo.
13
Destacado en azul tenemos las distintas vistas que el modulo de modulos posee,
como deberas saber el modulo que instala mdulos en openerp es un modulo del sistema como cualquier otro. Veamos los tipos que tenemos disponible para este modulo
de modulos.
Vista kanban que puede mostrar iconos, tarjetas e varios otros tipos de dados
como veremos luego.
14
En esta Vista tipo lista, todavia tenemos la posibilidad de ir instalando uno a uno
los modulos utilizando el icono amarillo a lado de la columna ESTADO pero al mostrar la ventana de confirmacin de la instalacin podremos dar cancelar y el ESTADO
va quedar en azul escrito A ser instado vamos clicando uno a uno los modulos y al
ultimo damos actualizar.
Luego de un rato nos aparece otra ventana para configurar los modulos clicamos en
START CONFIGURATIONS
Y en la barra superior ya nos muestra los menus de los modulos instalado.
Al clicar en cada uno de estos modulos nos mostrara el dashboard de cada modulos.
Las otras 2 vistas veremos al momento de trabajar con desarrollo que son las vistas
formulario y paginas.
Es importante salientar que para que quede fcil de empezar a utilizar openerp muchas configuraciones quedan como estandart asumiento una optima forma de trabajo,
pero tenemos la posibilidad de definir como va trabajar openerp en cada modulo. Para
esto vamos volver al modulo setting, en este modulo que es de responsabilidade del
administrador del sistema el dashboard nos muestra una pantalla con la opcin de configurar el sistema en general y a su lado una lista de usuario ya creados en el sistema.
15
Podremos ir seleccionando las opciones que queramos configurar que nos va mostrar al final un wizard para las configuraciones.
16
17
Luego de bajar tendramos un directorio llamado openobject-gedit, y a dentro tendramos los archivos: js.xml python.xml README.txt xml.xml, para que funcione en
ubuntu 10.04 a 11.04 tendramos que instalar inicialmente el soporte y multiplos plugins de gedit con el comando :
sudo apt-get install gedit-plugins
Lluego de haber instalado tendramos que tener en nuestro gedit el plugins snippets
que en espaol se llama recortes en portugues se llama trechos. Para que todo funcione es necesario que en la carpeta de nuestro usuario linux agregar los archivos xml
bajado de bazaar en el diretorio /tmp con el comando.
$ sudo su
#mkdir ~/.gnome2/gedit/snippets
#cp /tmp/openobject-gedit/*.xml ~/.gnome2/gedit/snippets
18
En linux no fue posible hacer la importacin de los xml, lluego abajo haremos la
instalacin del gedit para windows Si WINDOWS. Para instalar el Gedit en windows
bajamos
el
mismo
de
esta
direc-
19
El entorno es muy parecido a Eclipse con una zona central para desarrollo, un explorador de ficheros a la izquierda y la posibilidad de elegir diferentes perspectivas segn el entorno de programacin que necesitemos. El siguiente paso consiste en activar
el plugin que permite programar en python (pydev). Desde el men Help ir a Install
20
21
22
Despus de la instalacin del plugin debemos proceder a la configuracin del entorno de Python que se usar para debugear las aplicaciones. Comenzaremos con la
creacin de un proyecto PyDev. Elegir el tipo de proyecto Python y luego la versin
del interprete. Antes de avanzar ser necesario configurar el interprete indicando dnde se encuentra ubicado python.
Luego despues de definir que el proyecto es de pydev, nos mostrara la ventana para
crear el proyecto y su workspace (espacio de trabajo), es importante definir el espacio
de trabajo en el directorio del openerp que fue instalado en el capitulo 1 que fue
/opt/openerp y dejar el interpretador de python el la versin 2.6.
23
24
Casi listo ahora tenemos que configurar el entorno de ejecucin o sea como va ejecutar el openerp. Vamos al menu run run configurations.
25
En main module clicamos en browse y seleccionamos el archivo renombrando dentra de la carpeta server que lo hicimos anteriormente.
Por ultimo y no menos importante como OpenERP se compone de mucho cdigo
repetitivo sobre todo a la creacin de las vistas. Por esta razn se crearon para el eclipse una serie de macros o snippets que facilitan introducir de estas partes de cdigos.
Vamos a prepara las pantillas para el uso con aptana. Hay dos archivos XML una
para ayuda en el desarrollo de python http://openerp-eclipse-template.googlecode.com/svn/trunk/templates-openerp.xml y otro para el desarrollo de XML http://openerp-eclipse-template.googlecode.com/svn/trunk/Openerp-eclipse-xml-template.xml
La primera diferencia es la ubicacin del editor de XML. En Eclipse se encuentra ubicados en distintos directorios por esa razn es necesario indicar correctamente el atributo content de los ficheros de XML.
26
En seguida vamos a agregar el archivo para completar los xml. Usando la misma ventana pero en la configuracion de XML. Entrando em xml xml files editor templates.
27
Con esto sera agregado en el menu principal en herramientas el pgadmin3 o en algunas distribuciones en el menu aplicaciones y luego en desarrollo.
Una vez arrancado, lo primero que debe hacerse es conectarse a una base de datos.
Para ello, se hace clic sobre el icono del enchufe y se abrir una nueva ventana para
introducir los datos del servidor PosgreSQL . En el campo Nombre debe introducirse
el nombre que se quiera dar a la conexin. En el servidor debe introducirse la direccin IP del servidor, o el nombre del Host (si se dispone de un servidor DNS que facilita la direccin IP), en este caso, puesto que el servidor y pgAdmin estn instalados
sobre la misma mquina, la direccin IP es localhost.
28
Una vez introducidos los datos, aparecer una pantalla advirtiendo del riesgo de almacenar la contrasea
29
Para acceder a las tablas, debe efectuarse doble clic sobre el servidor, en este caso
Empresa/Bases de Datos/empresa/Esquemas/public/Tablas y aparecern todas las tablas que contiene la base de datos a la que se ha conectado pgAdmin III.
Haciendo clic con el botn derecho del men sobre Tablas, se abrir un men desplegable que permite, entre otras cosas, crear una Nueva tabla.
30
Si se efecta el clic derecho sobre una tabla concreta, el men desplegable muestra
unas opciones distintas, entre la que cabe destacar la de Propiedades.
Si se hace clic sobre dicho men, se abrir una nueva ventana que ofrece informacin muy til relativa a la tabla en cuestin. En el presente ejemplo , la tabla que se
est consultando es res_partner_address de OpenERP, que contiene las direcciones de
las empresas. La pestaa Columnas ofrece una lista de los campos que componen dicha tabla.
31
32
Y aparecer una nueva ventana que mostrar los datos contenidos en la tabla en
cuestin. En la figura abajo se muestran los datos de la tabla res_partner_address
33
Arquitetura MVC (Modelo (classe python), Vista (XML tipo html) y Controlador (controlador basado directamente en la base de datos)
34
Cliente GTK, KOO (KDE) y web que funcionan con el mismo codigode desarrollo de mdulos que estn en el servidor.
Todos los datos de Openerp son accesibles a travs de objetos. Por exemplo,
existe un objeto res.partner para acceder la informacin concerniente a parceros
(Partners)
Para un desarrollador que venga de la programacin orientada a objetos cabe sealar que en OpenERP el concepto objeto tiene un significado distinto a la de programacin orientada a objetos:
OpenERP
Objeto
Clase
Recurso
Obsrvese que existe un objeto para cada tipo de recurso, y no un objeto por recurso. As tenemos un nico objeto res.partner para manejar todas las empresas y no un
res.partner para cada empresa. Este objeto res.partner de OpenERP se programa como
una clase del lenguaje Python.
35
Una definicin de este patrn podra ser la que ofrece Wikipedia: Modelo Vista
Controlador (MVC) es un patrn de arquitectura de software que separa en tres componentes distintos:
Los
La
La
pgina.
El
El
ta, consultar datos del modelos, realizar los clculos necesarios y solicitar nuevas vistas.
En aplicaciones complejas que presentan multitud de datos para el usuario, a menudo es deseable separar los datos (modelo) y la interfaz de usuario (vista), de manera
que los cambios en la interfaz de usuario no afectan a la manipulacin de datos, y que
los datos pueden ser reorganizados sin cambiar la interfaz de usuario. El modelo-vistacontrolador resuelve este problema mediante la disociacin del acceso a datos y la lgica de negocio de la presentacin de los datos y la interaccin del usuario, mediante
la introduccin de un componente intermedio: el controlador.
Por ejemplo, en el diagrama anterior, las flechas continuas que van desde el controlador a la vista y al modelo significan que el controlador tiene acceso completo tanto a
36
Modelo (model): Los objetos de OpenERP con sus columnas que normalmente se guardan en las tablas de PostgresSQL con sus campos. Permite la creacin/actualizacin automtica de las tablas y acceder a las tablas sin usar SQL.
Vista (view): Listas, formularios, calendarios, grficos, ... definidas en archivos XML. En estos archivos tambin se definen mens, acciones, informes,
asistentes, ...
37
Composicin de un modulo
Business objects (objetos de negocios): Son declaraciones de clases de python que son heredadas de la clase osv.osv de openobject, la persistencia de
esta clase es manejada completamente por openobject.
Data (Datos): XML/CSV archivos metadatos (vistas e declaraciones de workflows), datos de configuraciones (parametrizacin de mdulos) y datos de demos (opcionales pero recomendable para pruebas)
Reports: archivos RML (formato xml) o Openoffice reportes que genera reportes html, odt o pdf.
38
En la gran mayora de las veces los mdulos estn a dentro de carpeta en el servidor junto con los archivos de openerp-server, en la carpeta server/bin/addons. Pero es
posible tener modulos en otras carpetas desde que sean configuradas en el openerpserver.conf en la opcin addons_path que se puede agregar varios caminos diferentes
separados por coma (,).
addons/
| - ideia/
| - demo/
| - i18n/
| - report/
| - security/
| - view/
| - wizard/
| - workflow/
| - __init__.py
| - __terp__.py
| - idea.py
Los nuevos mdulos pueden programarse fcilmente y requieren un poco de prctica en XML y Python.
Todos los mdulos estn ubicados en la carpeta bin/addons del directorio de instalacin del servidor OpenERP.
39
nERP.
Crear
los archivos Python que contendrn los objetos (clases con atributos y
datos de ejemplo, ...). Estos archivos deben ser indicados dentro del archivo
__openerp__.py.
Opcionalmente,
llamada i18n y las reglas de seguridad en una carpeta llamada security dentro
del mismo mdulo.
En todos los ficheros acostumbra a aparecer una cabecera que es un comentario Python referido a la licencia del mdulo y el copyright del autor/es, esto se puede copiar
de cualquier fichero de cualquier mdulo y adaptarlo a nuestras necesidades.
7.3.1 __init__.py
El archivo __init__.py (con doble guion bajo de cada lado) es un descriptor de um
modulo. Un modulo openerp es un modulo normal de python.
Ejemplo __init__.py
# Importar todos los archivos que contengan codigo python
Import idea, wizard, report
'name': 'ideias',
'version': '0,1',
'category': 'tools',
'complexity': "easy",
'description': """modulo ideia para treinamento do modulo tecnical""",
'author': 'Fadel Damen Schreiner',
'website': 'www.qualquercoisa.com.br',
'depends': ['base_tools'],
'init_xml': [],
'update_xml': ['ideia_view.xml',
],
'demo_xml': [],
'test':[],
'installable': True,
'certificate': '',
'images': [],
41
Ejercicios 1
Crear la carpeta de un nuevo modulo llamado openacademy. Creando los archivos
__init__.py y __openerp__.py. El modulo propuesto es para control de entrenamiento trabajando con 3 tablas, Cursos (course), seccines (session) y participantes(attendee).
42
7.4.1 Menu
la entidad menuitem es un atajo para la declaracin de un registro ir.ui.menu y conectarlo con la correspondiente accin a travs de un registro ir.model.data.
<menuitem id="menu_id" parent="parent_menu_id"
name="label" action="action_id" icon="icon-code"
groups="groupname1,groupname2" seguence="10"/>
id
parent
name
action
icon
groups
Lista de grupos que van poder ver el menu (si vaco o no especificado todos
los grupos lo podran ver)
sequence
Ejercicios 2
Crear el archivo openacademy_view.xml y crear la referencia al men para nuestro modulo.
Nos referimos a un men principal que no este adentro de ningn otro menu del sistema.
Por clic en el boton en una vista, que esta conectado a una accin.
accin
<record model="ir.actions.act_window" id="action_id">
<field name="name">action.name</field>
<field name="view_id" ref="view_id"/>
<field name="domain">[lista de 3 registros (maximo 250 caracteres)]</field>
<field name="context">{diccionario de context (max 250 caracteres)}</field>
<field name="res_model">object.mode.name</field>
<field name="view_type">form|tree</field>
<field name="view_mode">tree,form,calendar,graph</field>
<field name="target">new</field>
<field name="search_view_id" ref="search_view_id"/>
</record>
43
Name
view_id
Especifica la vista a ser abierta (si no especificada, la vista con mayor prioridad es usado.
Domain
Context
res_model
view_type
Se debe establecer a form para que la accin abra una vista de tipo formulario, y a tree cuando la accin deba abrir una vista de tipo rbol.
view_mode
Slo se tiene en cuenta si view_type se ha establecido a form; en cualquier otro caso, se ignora. Lista de vistas a usar (tree, form, calendar, graph, ...). Las cuatro posibilidades ms habituales
son:
form: La vista se muestra como un formulario y no hay forma de cambiar al modo lis ta.
tree: La vista se muestra como una lista y no hay forma de cambiar al modo formula rio.
Target
search_view_id
Identificador para que una nueva vista sea llamada en substitucin a la vista default.
44
Un componente clave de openobject, es el servicio de objetos (OSV) que implementa un completo modelo relacional, liberando en la gran mayora de las veces que
se tenga que utilizar consultar sql. Incluso las DDL o alteraciones en la estructura de
las tablas.
Los Business objects (objetos de negocios), son declarados en archivos python
utilizando clases python que deben heredar de la clase osv.osv
archivo idea.py:
class idea_idea(osv.osv):
""" Idea """
_name = 'idea.idea'
_rec_name = 'name'
_columns = {
'user_id': fields.many2one('res.users', 'Creator', required=True, readonly=True),
'name': fields.char('Idea Summary', size=64, required=True, readonly=True, oldname='title',
states={'draft':[('readonly',False)]}),
'description': fields.text('Description', help='Content of the idea', readonly=True,
states={'draft':[('readonly',False)]}),
'comment_ids': fields.one2many('idea.comment', 'idea_id', 'Comments'),
'created_date': fields.datetime('Creation date', readonly=True),
'open_date': fields.datetime('Open date', readonly=True,
help="Date when an idea opened"),
'vote_ids': fields.one2many('idea.vote', 'idea_id', 'Vote'),
'my_vote': fields.function(_vote_read, fnct_inv = _vote_save, string="My Vote",
type="selection", selection=VoteValues),
'vote_avg': fields.function(_vote_avg_compute, string="Average Score", type="float"),
'count_votes': fields.function(_vote_count, string="Count of votes", type="integer"),
'count_comments': fields.function(_comment_count, string="Count of comments", type="integer"),
'category_id': fields.many2one('idea.category', 'Category', required=True, readonly=True,
states={'draft':[('readonly',False)]}),
'state': fields.selection([('draft', 'New'), ('open', 'Opened'),
('close', 'Accepted'), ('cancel', 'Refused')], 'State', readonly=True,
help='When the Idea is created the state is
\'Draft\'.\n It is \opened by the user, the state is \'Opened\'.\\n
If the idea is accepted, the state is \'Accepted\'.'),
'visibility':fields.boolean('Open Idea?', required=False),
'stat_vote_ids': fields.one2many('idea.vote.stat', 'idea_id', 'Statistics', readonly=True),
'vote_limit': fields.integer('Maximum Vote per User',
help="Set to one if you require only one Vote per user"),
}
_defaults = {
'user_id': lambda self,cr,uid,context: uid,
'my_vote': lambda *a: '-1',
'state': lambda *a: 'draft',
'vote_limit': lambda * a: 1,
'created_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'visibility': lambda *a: True,
}
_order = 'id desc'
def _check_name(self,cr,uid,ids):
for idea in self.browse(cr, uid, ids):
if 'spam' in idea.name: return False #No permitir spam
return True
_sql_constraints = [('name_uniq', 'unique(name)', 'Idea debe ser unica')]
_constraints = [_check_name, 'Por favor no haga spam', ['name'])]
idea_idea()
_columns (requerido)
_defaults
_auto
Si True (default) el ORM crea la tabla en la base de datos si esta False para crear la tabla /vista tie ne que utilizar el metodo init()
_inherit
_inherits
Herencia multiple
_constraints
45
_log_access
Si True (default) 4 campos creados, (create_uid, create_date, write_uid, write_date) puede ser acecible por la funcin osv's perm_read()
_order
Nombre del campo para ordenar los registros en la lista (default: id)
_rec_name
Campo alternativo para uso en los nombres (_name) usado por el osv's name_get() (default _name)
_sql
Codigo SQL para crear tabla/vista para este objeto (si _auto es false) puede ser substituydo por el
metodo init()
_table
Nombre de la tabla a ser creado en la base de datos (default _name) con puntos '.' substituyendo el
underscore '_'
char(string,size,translate=False,...)
text(string,translate=False,...)
usuario.
Campos del tipo texto el text es mas utilizado para campos me-
mos
float(string, digits=None, )
Punto fluctuante con valores con precisin de decimales y/o
scala.
selection(values, string, )
Campo con valores pre-selecionados
binary(string, filters=None,...)
Contenido binario o archivos
46
many2one(obj,ondelete='set null',....)
Relacionamento muitos para uno usando la clave primaria
one2many(obj, field_id,...)
Relacionamento virtual con multiplos objetos (inverso al many2one)
Campos de Funciones
function(fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, method=False,
store=False,multi=False,...)
Campo de funcion simula un campo real y es calculado por una funcion de python al vuelo
fnct: funcion que calcula el campo (requerido)
def fnct(self,cr,uid,ids,field_name,arg, context)
retorna un diccionario
fnct_inv: funcion usada para grabar el valor en el campo al vuelo
def fnct_inv(obj,cr,uid,id,name,value,fnct_inv_arg,context)
type: tipo del campo a ser simulado
fnct_search: funcion usada para buscqueda en el campo.
def fnct_search(obj,cr,uid,obj,name,args)
obj: modelo _name para simular que es un campo relacionales
store,multi usado como mecanismo de performace
47
name
Define el valor usado por default para mostrar los registros en una consulta o lista. Si ausente es necesario setar el
atributo _rec_name para especificar otro campo para este fin. Basicamente son los campos descripcin en las consultas por una clave primeria.
active
Define la visibilidad registros marcados con el campo active en Falso son escondidos por default, si no me equivoco
es utilizado al excluir un registro.
sequence
Define el orden y la habilidad de arrastras y soltar para reordenar si habilitado en las vistas.
state
parent_id
parent_left,
parent_right
create_date,
create_uid,
write_date,
wirte_uid
Usado para crear un log de dados del creador del registro, su ultima actualizacin. Estos campos son deshabilitados
si la pocion _log_access esta setada com False
obs: creado por el orm.
create(cr,uid,values,context=None)
Crear nuevo registro con los valores
especificados Retorna el id del nuevo registro.
48
read(cr,user,ids,fields=None,
self.unlink(cr,uid,[42,43])
browse(cr,ids, context=None)
Busca los registros permite el uso
del dot-notation para listar los campos y las relaciones
Retorna: un objeto con la lista de
objetos requisitados.
ideia=self.browse(cr,uid,42)
print 'Ideia descripcin', ideia.description
print 'Inventor country code:', ideia.inventor_id.address[0].country_id.code
for vote in ideia.vote_ids
print 'vote %2 2f' % vote.vote
default_get(cr,uid,fields,
fields: lista de los nombre de campos
context=none)
retorna un diccionario con los valo- defs=self.default_get(cr, uid, ['name', 'active'])
res default de los campos (seteados assert defs['active']
en la clase de objetos o por la preferencia de objetos o via context
perm_read(cr,uid,ids,details=True)
Retorna un diccionario con dados Retorna un diccionario con los object id (id), Id do criador (create_uid), fecha de la creacin
de quien creo y cuando creo los re- (create_date), id usuario que actualizo (write_uid) y fecha de la actualizacin (write_date)
gistros.
perms= self.perm_read(cr,uid,[42,43])
print 'Criador:', perms[0].get('create_uid', 'n/a')
fields_get(cr,uid, fields=none,
fields: lista de campo.
context=none
retorna un diccionario de dicciona- class ideia(osv.osv):
rios de campos, cada un con la des(...)
cripcin de los campos del objeto
_columns = {
'name': fields.char('name',size=64)
(...)
def test_fields_get(self,uid):
#assert -> tira una execcin si no retorna verdadero)
assert(self.fields_get('name')['size']=64)
fields_view_get(cr,uid,
view id=None,view_type='form',
context=None,toolbar=False)
devuelve un diccionario que describe la composicin de una vista soli- def test_fields_view_get(self,cr,uid):
citada (incluyendo las vistas hereideia_obj=self.pool.get('ideia.ideia')
dadas)
form_view=idea_obj.fields_view_get(cr,uid)
name_get(cr,uid,ids,context={})
name_search(cr,uid,name='',
args=None, operator='ilike',
context=None, limit=80)
export_data(cr, uid, ids, fields,
contex=None)
import_data(cr,uid,ids,fields,
context=None)
49
50
Para construir un modulo, el principal mecanismo es insertar registros en la declaracin de mdulos del componente de interface. La interface del usuario son registros en
la base de datos conteniendo los mens, vistas, acciones, accesos etc.
El mecanismo de OpenERP para agregar registros es usando archivos XML
con una estructura predefinida.
Para cada tipo de registro (view, menu, action) tiene soportados un conjunto de entidades y atributos, pero todos ellos comparten algunos atributos especiales:
id
ref
Utiliza en lugar del contenido del elemento para hacer referencia a otro registro (funciona entre mdulos anteponindole el
nombre del mdulo)
eval
Utiliza en el lugar del contenido y proveer dados usando expresiones python, en el ejemplo utiliza el mtodo ref() que es
utilizado para encontrar el id en la base de datos para encontrar el xml_id
51
many2one_field
many2one_field:id
many2one_field.id
ir.model.access.csv
"id"
,"name"
,"model_id:id"
,"group_id:id"
,"perm_read","perm_write","perm_create","perm_unlink"
"access_idea_idea","idea.idea","model_idea_idea","base.group_user",1
,0
,0
,0
"access_idea_vote","idea.vote","model_idea_vote","base.group_user",1
,0
,0
,0
id
name
Nombre de la vista
model
type
priority
arch
52
Elementos de un formulario
string
nolabel
colspan
col
rowspan
invisible
eval
attrs:
Mapeo en python para atribuye dinamicamente condiciones para los atributos readonly, invisible, requerid basado
en el contenido de otros campos.
Field
53
Button
widget clicable (un boton) que se puede atribuir una accin y especificar los atributos:
type: tipo del botton, workflow (default), accion o objeto.
name: sinal del workflow, nombre de una funccion, o accin dependiendo del atributo type.
confirm: texto para el mensaje de confirmacin cuando clicado.
states: lista de estados que va cambiar el boton separado por coma.
icon: opcional icon (todos los iconos del GTK ej. gtk-ok)
Separator
Linea horizontal para separa la estructura de las vistas, con label opcional
Newline
Label
Group
Usado para organizar los campos en forma de grupo con label opcional
Notebook,
page
Una de las tareas mas complejas en las vistas son ordernar y organizar los campos
(atributos) en el formulario y para eso se utiliza los atributos colspan y col son reponsables de definir con colspan la cantidad de columnas que el objeto va tomar y col
para definir cuantas colunas a dentro del objeto sera creado. Esta forma de dibujar es
muy utilizado en el desarrollo web, y como ya es muy utilizada y ya se considera una
excelente forma de tratar objetos, fue utilizado tambin por los desarrolladores de
OpenERP.
Vamos a ejemplo:
<group col="6" colspan="4">
<group colspan="5" col="6">
<field name="name" select="1" colspan="6"/>
<field name="inventor_id" select="1"/>
<field name="inventor_country_id" />
<field name="score" select="2"/>
</group>
<group colspan="1" col="2">
<field name="active"/>
<field name="invent_date"/>
</group>
54
1 2 3 4
</form>
labl4 inpt4_______________
Decimos que el campo inpt4 va ocupar 4 columnas y nos presenta el label en 1 columna y el edit en 3 columnas ya que un field contiene un label y su widget.
O tambien en diferentes ordenes.
label input
labl4 inpt4_______________
labl2 inpt2
55
lb a
lb d lb e lb f
lb b
Lo nico que tiene siempre que recordar es que los field tienen su label que ocupara siempre una columna.
Y lgicamente podremos complicar por ejemplo aadir un group dentro de outro
group.
Listas / Arboles (tree)
Otra forma de presentar los datos son a travs de las vistas tree que son posibles
presentar los dados como un grid, o como un arbol dependiendo de los tipo de datos
que tenemos. Su declaracin es:
Atributos
Elementos permitidos
56
Ejercicios 7: Notebook
Vamos ordenar la vista formulario de los cursos utilizando el notebooks para separar la
descripcin de los cursos y tener oportunidad de agregar nuevas pestaa con otros datos.
57
Ejercicios 10
En el ejercicio 6 habiamos creado en la clase seccin el campo relacional curse_id pero no
creamos la contrapartida en la clase course o sea agreguemos el campo 'session_ids' del
tipo one2many que nos va dar la posibilidade de aceder a los respectivos datos de qualquier una de las clases.
Otro cambio que podremos hacer al la clase cursos es tambien agregar un responsable al
curso que va usar el usuario del sistema como base para guardar quien agrego el curso y
va usar la clase ya existente de openerp res.user. El campo seria:
'responsible_id' del tipo many2one relacionado a la clase res.user.
Vamos agregar tambien estos campos en sus respectivas vistas siguiendo la imagen abajo.
Consejo: utilizar la tag group siempre que utiliza 2 tipos de vistas y/o notbook para que el
layout no tenga comportamientos inesperados.
Herencia en vistas
Las vistas existentes solamente deben ser modificadas a travs de vistas heredadas,
nunca directamente. En la vista heredada se hace referencia a la vista padre usando el
campo inherit_id, para luego adicionar modificar los elementos existentes en la vista
por referencia usando expresiones Xpath especificando la posicion.
Position
El formato Xpath se utiliza mas cuando tenemos campos con el mismo nombre en la vista, otra forma
es utilizando directamente el nombre del campo
58
59
7.7
Los dominios son las condiciones que modifican el resultado de una consulta. Las expresiones son indicadas con un array, que contiene las condiciones, y en ocasiones, el operador
booleano para unirlas. Veamos un ejemplo:
= , igual
<> o != , distinto
<= , menor o igual que
< , menor que
> , mayor que
>= , mayor o igual que
=like , contiene
like , contiene
not like , no contiene
ilike , contiene (ignorando las maysculas y las minsculas)
not ilike , no contiene (ignorando las maysculas y las minsculas)
in , existe en un array
not in , no existe en un array
child_of , es hijo de
60
Como en el momento de cargar un participante (attendee) que en realidade es nuestra clase de partnet seria interesante que al momento de cargar el partner ya hagamos
la carga de la seccion que el alumno va participar. Hagamos estos cambios.
61
Como vamos a trabajar con codigos en python que sale um poco del habito del framework creando funciones, metodos etc tenemos que comprender como depurar el
cdigo fuente para mejor manejo de los errores.
Este mtodo es usado en aptana o con el eclipse ya que simplesmente es la forma
de trabajo de eclipse.
Lo primero que tenemos que tener en cuenta es que si o si tenemos que tener el servidor de openerp funcionando en la maquina de desarrollo ya que para depurar tenemos que correr el servidor por el aptana incluso con parametros que veremos abajo.
Como ya lo hicimos en el capitulo de instalacin de ide aptana gran parte de la
configuracin para que todo funcione. Pero como normalmente tenemos que pasar
como argumento al servidor openerp el update para que las modificaciones que hacemos en el codigo se aplique a la base de datos, tenemos que hacer que openerp corra
por el aptana y poner los parametros de conexin.
Tenemos que al correr openerp-server passar los parametros para que levante el servidor por el aptana y permita utilizar la depuracin. Con eso entramos en el menu runrun configurations y luego seleccionamos en el arbol el openerp-run que creamos al
principio del manual, e vamos a la haba parametros. Como mostrado abajo y agregamos los argumentos.
62
Donde:
-r es el usuario del sistema que tiene permisiones de correr la base de datos
-w contrasea del usuario del parametro -r
--addons-path= lugar donde estn guardados los addons puede contener una
lista de carpetas separados por coma.
--update utilizado para decir al servidor que levante o actualize el codigo de un
modulo especifico o all para todos.
debug modo depuracin en los logs de openerp o en el console.
Antes de correr el servidor tenemos que poner los Breakpoints en el codigo para
que el sistema pare en determinado lugar en el codigo.
Para crear los breakpoints solamentes tenemos que dar doble click en el editor al
costado de la linea que queremos crear el breakpoint. Quedando con el aspecto abajo.
63
Ai nos pregunta si queremos cambiar a la perspectiva de depuracin que nos muestra las variables, estados etc. Respondemos que s.
Y luego entramos al sistema y nos vamos a la pantalla que contiene el breakpoint que
en nuestro ejemplo esta en la clase session la y va quedar parado y veremos la pantalla
abajo:
64
65
{'field': 'valor','field2','valor2'}
'domain': {'field':'domain'}
'warning': {'title':'mensaje'}
66
dict={'value':{campo1:valor,campo2:valor2},'warning':
{'title': 'mensaje'}}, quando openerp recibe un diccionario como el tipo 'warning' presenta una mensaje en la pantalla del usuario con el titulo y mensaje pasado por el diccionario.
Crear un evento para el campo seats que valide si el valor de seats es negativo presenta una mensaje y vuelve los valores que tenia grabado en la base de datos.
67
_sql_constraints
68
El secreto es utilizar la funcin super() de python en el ejemplo se ejecutara el metodo anterior luego se tratara algunos valores. Tambin es possible tratar algun dato
antes de enviar al otro metodo.
def _amount_line(self, cr, uid, ids, prop, unknow_none,unknow_dict):
res = {}
for line in self.browse(cr, uid, ids):
res[line.id] = round(line.price_unit * line.quantity * ((line.taux)/100.0) *(1-(line.discount or
0.0)/100.0),2)
return
super(account_invoice_line, self)._amount_line(cr, uid, ids, prop,
unknow_none,unknow_dict)
Como hicimos los contraints que verifican que no se pueda duplicar un nombre de
curso si tratarmos de utilizar el recurso de openerp de duplicar un registro nos va
generar un error acionando la constraints para eso tenemos que re-implementar el
metodo copy estandart para que por exemplo agregue al nombre nuevo por exemplo
el termino (copia).
69
lambda es una funcin annima. Se ejecuta como una funcin normal pero solo
para procesar una sola lnea de cdigo. Se le pueden pasar parametros decirle que no
ser as, escribiendo *a.
Crear en la clase session un campo nuevo llamado active del tipo boolean el mismo
es un campo de nombre reservado por openerp con la funcin automtica de definir
la visibilidad del registro en las consultas.
Vamos tambin atribuir valores por defecto en el campo start_date y cambiar su tipo
de campo de date para datetime. Para esto tenemos que hacer un import en el archivo python de un paquete llamado time que possee funciones de python para tratar
campos del tipo hora. Agreguemos tambien el valor por defecto al campo active
para true.
70
En el ejemplo cuando el campo usage sea igual a 'view' el color de la linea ser
azul y cuando el campo usage sea 'internal' sea rojo oscuro. Para definir los colores se
utiliza la misma denominacin usada en codigos html que puede ser en la representacin escrita o la representacin tipo hexadecimal #00ff00.
7.14.1 Uso de caracteres especiales en las vistas.
Un cuidado muy importante en la utilizacin de caracteres especiales en las
vistas por ejemplo &, los simbolos de > ,< etc. es que pueden y sern interpretados como xml seguramente generando error en la vista. Por eso es importante siempre
utilizar la tabla de caracteres especiales abajo siempre para evitar errores en las vistas.
71
â
Â
ã
à
é É
ê
Ã
À
Ê
í
Í
ó Ó
õ
Õ
ô
Ô
ú Ú
ü
Ü
ç
Ç
ñ
Ñ
ä
Ä
å
Å
ë
Ë
`
È
ï
Ï
ì
Ì
ö
Ö
ò
Ò
Û
ý
Ý
ÿ
Ÿ
æ
Æ
œ
Œ
Š
þ
Ð
§
ƒ
ß
µ
¡
¿
î
Î
Caracteres especiales
&
&
>
>
<
<
ˆ
˜
¨
&cute;
¸
"
"
“ e ”
‘ e ’
‹ e ›
72
« e »
º
ª
–
—
­
¯
…
¦
•
¶
§
♠
♣
♥
♦
¹
²
³
½
¼
¾
>
>
<
<
±
−
×
÷
∗
⁄
‰
∫
∑
73
∏
√
∞
≈
≅
∝
≡
≠
≤
≥
∴
⋅
·
∂
ℑ
ℜ
′
″
°
∠
⊥
∇
⊕
⊗
ℵ
ø
Ø
∈
∉
∩
∪
⊂
⊃
⊆
⊇
∃
∀
∅
74
¬
∧
∨
↵
← e →
↑ e ↓
↔
⇐ e &hrrr;
⇑ e ⇓
⇔
⌈ e ⌉
⌊ e ⌋
◊
En la vista tree de la clase session, hagamos que cuando la duracin sea menor que
5 el color de la linea sea #00ff00 y rojo si la duracin sea mayor de 15.
75
date_start: nombre del campo que contiene la fecha que empieza el evento (date/time)
date_stop: nombre del campo que contiene la fecha final del evento tipo (date/time)
OR
Allower elements
Crear una vista nueva tipo calendario para la clase session usando los parametros
abajo:
date_start
campo start_date
date_delay
campo duration
day_length
'1'
color
campo instructor_id
Campo a mostrar
name
Ejercicios 22: campo calculado sumatoria de participante de todas la sessiones del curso
Agregar en la clase cursos un campo calculado que sumen los participantes de todas
la secciones el campo sera llamado 'attendee_count'.
Tambin agregar el campo funcion 'attendee_count' en la clase session que contenga el total de participantes.
76
date_start: nombre del campo que contiene la fecha que empieza el evento (date/time)
date_stop: nombre del campo que contiene la fecha final del evento tipo (date/time)
OR
Allower elements
Field, level
level es usado para definir el grado del grafico de grantt
atributos de level:
object objeto de openerp que contiene el campo many2one
link nombre del campo del objeto actual que va linkado
con el objeto origen
domain el dominio es usado para filtrar los registros recuperados del objeto arriba mencionado.
Definir una vista tipo gantt para la clase sessin con los mismos datos de la vista
Calendar, cambiando el attributo color para que utilize course_id
77
Allower elements
Campos a tratar.
Definir una vista graphs, tipo bar en la session, que contenga como campo X el
course_id y como eje Y el campo attendee_count sumando los mismos
78
A travs del atributo context es posible pasar a la vista cuales los filtros seleccionados como estandarte, por ejemplo si queremos que el my ideas ya venga seleccionado se pasa en el contexto un parmetro especial {"search_default_XXX":1}
donde XXX es el nombre del filtro por ejemplo search_default_my_ideasA como el
de arriba en el ejemplo.
Crear una vista de busqueda (search) para la clase cursos, que tenga un filtro mis
cursos, que filtre atravs del dominio "[('responsible_id','=',uid)]" que el icono utilizado sea "terp-partner". Esto nos resume que va listar en el grid solamente los cursos que el reponsable sea el usuario que registro los cursos y quedo como responsable. Y que tambien que este filtro venja como estandarte seleccionado.
79
Crear una vista de busqueda (search) para la clase session. Es recomendable utilizar
la vista search para definir los campos de busqueda que tambien se puede utilizar el
atributo select='1' en la vista tree.
Editar la vista de cursos, que tenga el dominio de bsqueda de mas un botn, que
filtre los que no son mis cursos. Dejando como estandarte que no quede seleccionado al abrir la bsqueda o sea pasarlo por el context
Todava hablando de sobre las vistas search tenemos otra caracteristicas importante
y yo principalmente considero una de las mas espetaculares funcionalidades de OpenERP, que e la capacidad de agrupar los registros por determinados campos claves,
pero ademas de poder agrupar (group by) podremos hacerlos en diferentes agrupaciones por exemplo agrupar los cursos, por session por responsable, o cambiar el orden
de agrupacin por responsable por curso por session. Esto nos da los medios de analisar las informacines mucho mas fcil. Esto se aplica en la propria vista como los botones que ya lo vimos (filters) pero en vez de utilizar el dominio para filtrar los registros utilizamos el context para definir la agrupacin, para un mejor ordanacin separamos los mismo atraves del atributo group con el nombre group by..
En los filters juntamente con en context podremos formar entonces las agrupaciones de la siguinte forma.
<record model="ir.ui.view" id="view_idea_category_search">
<field name="name">idea.category.search</field>
<field name="model">idea.category</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Ideas Categories">
<group>
<field name="name" string="Category"/>
<field name="parent_id" widget="selection"/>
</group>
<newline/>
<group expand="0" string="Group By...">
<filter string="Parent Category" icon="terp-stock_symbol-selection" domain="[]"
</group>
</search>
</field>
</record>
context="{'group_by':'parent_id'}"/>
Podremos ver que es basicamente el mismo concepto que vimos para filtrar registros por el domains pero vemos que el domain queda vazio y luego en el context pasa-
80
Editar la vista search de cursos, que tengamos una agrupacin de los registros por
responsable y tambin agrupacin por nombre.
81
perm_read, perm_write,
name
idea.category user
idea.idea user
idea.vote user
idea.vote.stat user
idea.comment user
report.vote manager
model_id:id
model_idea_category
model_idea_idea
model_idea_vote
model_idea_vote_stat
model_idea_comment
model_report_vote
group_id:id
base.group_tool_user
base.group_tool_user
base.group_tool_user
base.group_tool_user
base.group_tool_user
base.group_tool_manager
perm_read
perm_write
1
1
1
1
1
1
perm_create
1
1
1
1
1
1
perm_unlink
1
1
1
1
1
1
1
1
1
1
1
1
Cuando queremos utilizar el csv para agregar datos de las series many2one, one2many etc tenemos que utilizar las reglas abajo para que el ORM consiga encontrar los
datos relacionados, lo que le define como el ORM va identificar los registros va de
nombre de la cabecera del csv con los nombre abajo definidos.
.id,
(=database_id)
base de datos
el numero del ID en la
partner_id,
order_line/.id,
(=database_id)
base de datos
order_line/name,
order_line/produtct_id/id,
el numero del ID en la
82
eval="[(4, ref('base.group_tool_user'))]
definir su accin para cada tipo de campo abajo tenemos los codigos utilizados para
los campos tipo many2many.
(0,0,{values})
(2, ID)
Remove y borra el registro con id=ID vinculado (llama el metodo unlink (borrar) el ID, y
borra el objeto completamente)
(3, ID)
(4, ID)
(5)
(6, 0, [IDS])
83
(2, ID)
Remove y borra los links con los registros pasados en los ID (llama el metodo unlink con
los ID, y borra los objetos por completo.
Ejemplo:
[ (0, 0, {'field_name': field_value_record1, }), (0, 0, {'field_name': field_value_record2,...})]
Para campos many2one usar el ID para hacer referencia al registro o pasar le False
para borrar la relacin (vinculo).
Crear los archivos de datos para el mtodo de seguridad del Modulo openacademy,
tendriamos 2 grupos openacademy Manager e openacademy user, y que los usuario
no puedan borrar registros.
84
85
Esta modificacin en la logica de ventas, no hace falta poner ni una linea de codigo
python para cambiar el fluxo de trabajo en openerp.
Usando el editor de workflow agregamos la validacin y luego tenemos que agregar el botn de validacin en el formulario de ordenes.
Veremos ahora como definir un workflow y sus definicines.
86
Donde:
id es el identificador de workflow debe ser nico en todo el sistema
name nombre del workflow debe mantener la relacin de nombre usando el
punto como separador.
on_create si True es disparado el workflow automaticamente cuando el resurce.model (clase) es creada.
Esta es la definicin del workflow pero em mismo como vimos arriba esta compuesto por atividades (wkf_activity) y transaciones (wkf_transaction).
87
En el modo AND la actividade espera que todos lsa transaciones sean completadas
y dispara todas juntas.
Join_mode
88
name="action">''(...)''</field>
name="signal_send">''(...)''</field>
name="flow_start">True | False</field>
name="flow_stop">True | False</field>
7.20.4 Transiciones
Transiciones de flujo de trabajo son las condiciones que deben cumplirse para pasar de una actividad a la siguiente. Estn representados por las flechas de un solo sentido que unen dos actividades.
Las condiciones son de diferentes tipos:
Los roles y las seales son evaluados antes de la expresin. Si a una funcin o una
seal que es falso, la expresin no ser evaluado.
Las pruebas de transicin no puede escribir valores en los objetos.
act_from
Actividad origen.
act_to
Actividad Destino.
89
90
Luego vamos a definir los mtodos que cambian cada estado. Estos metodos sern
llamados por los botones en la interface de usuario y manejados por el workflow.
def mymod_new(self, cr, uid, ids):
self.write(cr, uid, ids, { 'state' : 'new' })
return True
def mymod_assigned(self, cr, uid, ids):
self.write(cr, uid, ids, { 'state' : 'assigned' })
return True
def mymod_negotiation(self, cr, uid, ids):
self.write(cr, uid, ids, { 'state' : 'negotiation' })
return True
def mymod_won(self, cr, uid, ids):
self.write(cr, uid, ids, { 'state' : 'won' })
return True
def mymod_lost(self, cr, uid, ids):
self.write(cr, uid, ids, { 'state' : 'lost' })
return True
91
Luego tenemos que incluir este xml en el archivo de declaracin de modulo __openerp_.py en la sessin update_xml.
Ademas tenemos que agregar en la vista de modulo el grupo de botones que reciben el
signal de workflow para definir el titulo del mismo.
<separator string="Workflow Actions" colspan="4"/>
<group colspan="4" col="3">
<button name="mymod_assigned" string="Assigned" states="new" />
<button name="mymod_negotiation" string="In Negotiation" states="assigned" />
<button name="mymod_won" string="Won" states="negotiating" />
<button name="mymod_lost" string="Lost" states="negotiating" />
</group>
92
Crear un workflow para la clase session, que contenga los estados draft, confirm y
done.
Agregar al workflow que solamente permita cambiar al estado a confirmado si la fecha de la seccion sea igua o mayor que da fecha actual.
Nota: para ejecutar un exception en openerp usamos:
raise osv.except_osv( 'Error !', 'mensaje' )
93
94
En la vista lo que notamos de diferente so los atributos button en rojo que justamente hacen referencia a la clase mas arriba que atravs del boton Update llama el
metodo compute_hours, y si se presiona cancel lama la tag special que cancela el wizard y destruye todo el objeto en memoria.
Y no menos importante tenemos que definir el action que hace el llamado al wizard.
<record id="action_config_compute_remaining" model="ir.actions.act_window">
<field name="name">Compute Remaining Hours</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">config.compute.remaining</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
95
Corregir el wizard llamado por el men principal del modulo que genera error.
En el Wizard creamos que el mismo sea posible ser llamado cuando seleccionamos
varios registro o apenas 1 registro de la clase session cuando seleccionado varios registros en nos trae la sessin de primer registro seleccionado en el tree, haora vamos
implementar que verifique si tenemos varios registros seleccionados y ocultamos
(atributo invisible) el campo session en la vista de este wizard.
96
Tenemos en este dashboard los presupuestos lanados, las ventas por mes valorizado, un grfico de ventas por vendedor en los ltimos 90 das y un grfico de ventas
por cliente en los ltimos 90 das.
En los dashboard vamos utilizar mucho el atributo domain de la definicin de la
vista. En realidad del dashboard es basicamente la llamada de pantallas que ya existe
definida en el modulo solamente pasando algunos filtros a los mismos.
Por ejemplo los presupuestos mostrado en el dashboard es la misma vista que sales
order aplicando los filtros de que traiga apenas los que son presupuestos (darft) y que
sean del usuario conectado en el caso administrador. Veamos en la pantalla abajo.
97
As vemos que tengo seleccionados en la vista los presupuestos y vendedor filtrados. Que nos trae los mismos 3 registros.
De igual forma que todas las vistas en openerp es necesario definir las acciones de
las vistas para que sean llamadas. En el archivo xml haramos esta actin asi:
<record id="action_quotation_for_sale" model="ir.actions.act_window">
<field name="name">My Quotations</field>
<field name="res_model">sale.order</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('state','=','draft'),('user_id','=',uid)]</field>
<field name="view_id" ref="sale.view_order_tree"/>
</record>
En esta actin tenemos el domains que pasamos que sean state=draft y que
user_id=uid que es el usuario conectado en el sistema, luego pasamos el view_id
como referencia sale.view_order_tree o sea va buscar la vista en el modulo sale con
el nombre view_order_tree luego se aplicar el dominio que pasamos por la nueva actin.
Si utilizamos las vistas que ya se encuentran en el modulo y no lo vamos a pasar
ningn parmetro de filtro podremos llamar diretamente en la definicin del dashboard.
<record id="board_sales_manager_form" model="ir.ui.view">
<field name="name">board.sales.manager.form</field>
<field name="model">board.board</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Sales Manager Dashboard">
<board style="2-1">
<column>
<action name="%(sale.action_quotation_for_sale)d" string="Quotations" creatable="true"/>
<action name="%(sale.action_view_sales_by_month)d" string="Sales by Month"/>
</column>
<column>
<action name="%(sale.action_sales_by_salesman)d" string="Sales by Salesman in last 90 days"/>
<action name="%(sale.action_sales_by_partner)d" string="Sales per Customer in last 90 days"/>
<action name="%(sale.action_sales_product_total_price)d" string="Sales by Product's Category in
last 90 days"/>
</column>
</board>
</form>
</field>
</record>
98
Donde board style es la definicion de como sera el layout del dashboard, el dashboard puede tener 3 columnas, pero como en las vistas puede juntar 2 colunmas. Siguiendo mas o menos el siguente layout.
O sea tenemos como este tag las opciones abajo en la misma orden del dibujo arriba:
style=1 junta todas las 3 columnas mostrandos las vistas em 1 columna
style=1-1 divide em 2 columnas
style=1-1-1 3 columnas en este caso es importante que en la definicin de la
vista tengamos 3 divisiones de la tag column ya que si no lo tenemos no dara error la
vista.
Style= 1-2 una columna menor y outra columna ocupando 2 epacios.
Style=2-1 lo mismo de arriba pero alreves.
Column definimos el contenido de cada columna que en nuestro ejemplo es 2
columnas y teriamos presupuestos y ventas por mes en una columna a la izquierda y 3
vistas ventas por vendedor a 90 dias, clientes atendidos en 90 dias y categorias de produtos vendidos en 90 dias.
Luego tenemos el actin en que decimos cuales las vistas vamos llamar, vean que
las vistas llamados normamente por el nombre ya que no es nomalmente conocido el
99
100
101
102
Adems, debemos asegurarnos que dnde estn las llamadas a _, est disponible
la variable context. Es decir si tenemos algo del estilo:
def check(self, values):
if not values.get('field'):
raise osv.except_osv(_("Error"), _("Error!"))
def create(self, cr, uid, values, context=None):
self.mycheck(values)
103
y no
_("Value: %s" % valor)
Hacer todo lo necesario para que nuestro modulo openacademy este totalmente al
idioma espaol paraguay.
104
Campos
Bucles de listas
Idiomas
Expresiones Python
Imgenes, etc.
105
Instalacin
El plugin de diseo de informes de LibreOffice es muy fcil de instalar y utilizar.
Para su instalacin, lo primero que se debe hacer es descargar el plugin desde el propio OpenErp una vez instalado el modulo base_report_designer que esta en las categorias Extra en Openerp.
106
107
Una vez agregado el complemento nos dejara disponible, un men y tambin una
barra de herramientas.
nERP Report / Server Parameters. Aparecer una ventana que pedir que se introduzca
la direccin URL del servidor, se seleccione la base de datos a la que se desea conectar, la identificacin del usuario y su contrasea. Abajo las Imagines primero de conexin y luego de base de datos y el usuario y contrasea.
108
Se acepta y si todo va correctamente, debera aparecer una ventana como abajo que
informa de la posibilidad de realizar el informe en el documento actual.
Por ejemplo seleccionamos idea Vote. Esto lo que hace en realidade es agregar algunas propiedades en el documento, vemos esto en la propriedades del documento
una vez seleccionado.
109
una nueva ventana como la mostrada abajo, que pedir que se seleccione un objeto
para realizar el bucle, el campo sobre el que realizar el bucle, el nombre de la variable
y el nombre mostrado en el informe.
110
111
112
import openacademy
Creamos tambin el archivo __openerp__.py que es el archivo descriptor de nuestro modulo y los archivos que contengan los dados (registros) del sistema, recordando
que incluso el menu, y las vistas, son registros dentro de la base de datos, esto nos
113
__openerp__.py
{
114
Creamos dentro de la carpeta raiz de nuestro modulo mas 1 archivo el openacademy_view.xml, que va ser el archivo principal de la vista de nuestro modulo, el mismo
siempre que utilizado debe estar declarado en el descriptor del modulo com ya lo hicimos en el ejercicio anterior su declaracin. En principio un menu tambien debe ser declarado adentro del archivo de la vista.
openacademy_view.xml
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<!--Punto de men padre (del mdulo)-->
<menuitem id="menu_main_openacademy" name="OpenAcademy" />
</data>
</openerp>
115
Creamos dentro de la carpeta openacademy el archivo principal de modulo openacademy el archivo ya lo hemos declarado en el __init__ del modulo y ahora solo nos
falta crear dicho archivo.
openacademy.py
# -*- coding: utf-8 -*##########################################
# OpenERP, modulo creado por: Fadel Damen Schreiner
#from carpeta import archivo
#(archivo.clase)
##########################################
from osv import osv
from osv import fields
class openacademy_course(osv.osv):
_name = 'openacademy.course'
#_table = 'nombre_nuevo'
_columns = {
# 'nombre campo':'valor'
# 'nombre de los campos' : 'objeto de tipo de datos, su etiqueta
# y sus caractersticas de tamao, etc. en la base de datos'
'name' : fields.char('Nombre', size=128),
'descripcion' : fields.text('Descripcion')
}
#
#
#
#
116
117
Cursos form:
Aqui vemos que la pantalla no esta bien ordenada, o sea queda bastante feo la forma como se presenta el formulario.
118
119
Seccin form:
120
Editamos openacademy.py
class openacademy_session(osv.osv):
_name = 'openacademy.session'
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
}
openacademy_session()
Aqui tenemos ya con el campo cursos. Vean que como el relacionamiento del campo es one2many del lado de seccin solamente tenemos 1 curso.
121
122
123
124
class openacademy_session(osv.osv):
_name = 'openacademy.session'
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees')
}
openacademy_session()
Siempre que un campo es del tipo one2many que por logica tendramos varios participantes en una seccin tendremos que en la vista form tener una vista tree para que
nos muestre un grid con los varios participantes. Como vemos abajo.
125
Consejo: utilizar la tag group siempre que utiliza 2 tipos de vistas y/o notbook para que el
layout no tenga comportamientos inesperados.
126
127
Lo primero que tenemos que hacer ya que vamos a heredar la clase partner para
agregar un campo mas, tenemos que decir al identificador de nuestro modulo que vamos crear un nuevo archivo llamado partner.py para eso editamos el archivo
__init__.py y agregamos la declaracin del partner.py
__init__.py
#-*- coding: utf-8 -*# Aqu deben declararse los imports de TODOS los archivos py que contenga
# nuestro modulo, para que puedan ser compilados por openERP y as procesados.
# vean
import openacademy
import partner
Como vamos a crear la herencia de la vista tambien vamos tener que crear el archivo partner_view.xml que va contener la vista heredada tenemos que agregar el mismo
tambien en el archivo __openerp__.py
__openerp__.py
{
128
Vamos ahora realmente crear el archivo de herencia del partner agregando el campo is_instructor.
partner.py
# -*- coding: utf-8 -*##########################################
# OpenERP, modulo creado por: Felipe Chiu
#from carpeta import archivo
#(archivo.clase)
##########################################
from osv import osv
from osv import fields
# nombre de la clase para PYTHON:
class res_partner(osv.osv):
# mucho ojo: cuando se heredan modulos SIEMPRE asegurarse
# de escribir INHERIT, en vez de _name
_inherit = 'res.partner'
_columns = {
'is_instructor': fields.boolean('Instructor', help="Mrquelo si el partner es instructor de Open Academy"),
}
res_partner()
129
130
Lluego agregamos en la vista de session para que posamos ver los intructores relacionados, como vemos abajo.
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="course_id"/>
<field name="instructor_id"/>
<field name="attendee_ids" colspan="4">
<form string="Attendees">
<field name="partner_id"/>
</form>
<tree string="Attendees" editable="top">
<field name="partner_id"/>
</tree>
</field>
</form>
</field>
</record>
131
132
Al aplicar estos cambios le funcionamiento de nuestro modulo quedara en lo mismo. Abajos solamente comentamos la definicin del campo y dejamos como instructivo el mecanismo de como se usa.
class openacademy_session(osv.osv):
_name = 'openacademy.session'
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
# domain puede estar aqu y afecta todo el mdulo, en la vista (xml) y afecta la vista solamente
#'instructor_id': fields.many2one('res.partner', 'Instructor', domain=['&',('is_instructor','=', True),
#('supplier','=', True)])
'instructor_id': fields.many2one('res.partner', 'Instructor')
}
openacademy_session()
133
Y tambin tenemos que cambiar la vista. Que como es heredada quedara como
abajo mostrado.
<record model="ir.ui.view" id="view_res_partner_form_inh_openacademy_01">
<field name="name">view.res.partner.form.inh.openacademy.01</field>
<!--Este es el modelo original-->
<field name="model">res.partner</field>
<!--Esta es el -->
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<!--Este es el control que antecede al control que deseo insertar.
Position puede ser after, before, replace e inside
(inside solo para insertar en controles page) -->
<field name="supplier" position="after">
<!--Este es el nico campo/control que se va a agregar
a la vista original-->
<field name="is_instructor"/>
</field>
<xpath expr="/form/notebook/page[@string='Notes']" position="after">
<page string="Sessions">
<field name="session_ids" nolabel="1" colspan="4"/>
</page>
</xpath>
</field>
</record>
134
Vamos ahora explicar um poco el codigo fuente arriba, en primer lugar veremos la
declaracin del campo calculado que sigue:
'remaining_seats_percent':fields.function(_remaining_seats_percent,
method=True,type='float',
string='Remaining seats'),
Definimos un campo llamado remaining_seats_percent, que traduciendo es Percentual de lugares sobrantes. Que es un campo calculado en el primer parametro pasamos
la funcin python que va realizar el calculo, tambin pasamos el method como True
para decir que la llamada es un metodo, y no una funcin generica, que estaria a fuera
de la clase. Y luego definimos el tipo de resultado que va ser un float, e por ultimo el
nombre el campo.
135
browse_record(openacademy.session, 1)
sesion 1
10
1 wagner martinez
2 Fadel Damen Schreiner
El metodo browse estiro los datos de la tabla sessin y tambien de la tabla detalle
de participantes (attendee) y lo hacemos un recorrido o obtenemos por ejemplo la sessin 1 con 10 cupos y ya agregado 2 participantes.
136
Y su llamada:
result[session.id] = self._get_remaining_seats_percent(session.seats,
session.attendee_ids)
Notamos que lo unico diferente que hacemos es el uso de un nuevo widget llamado
progressbar.
137
Crear un evento para el campo seats que valide si el valor de seats es negativo presenta una mensaje y vuelve los valores que tenia grabado en la base de datos.
class openacademy_session(osv.osv): # nombre de la clase para PYTHON
_name = 'openacademy.session' # nombre del objeto para OPENERP
def onchange_remaining_seats(self, cr, uid, ids, seats, attendee_ids):
remaining_seats_percent = self._get_remaining_seats_percent(seats, attendee_ids)
res = {}
res.update({
'value': {
'remaining_seats_percent': remaining_seats_percent,#campo del modelo
}
})
if seats < 0:
#if remaining_seats_percent < 0:
res.update({
'warning': {
'title': 'Warning',
'message': 'You cannot have negative seats',
}
})
session_obj = self.pool.get('openacademy.session')
session = session_obj.browse(cr, uid, ids, context=None)[0]
res['value']['seats'] = session.seats
res['value']['remaining_seats_percent'] = session.remaining_seats_percent
return res
def _get_remaining_seats_percent(self,seats,attendee_list):
return seats and ((100.0 * (seats - len(attendee_list)))/ seats) or 0
def _remaining_seats_percent(self,cr,uid,ids,field,arg,context=None):
#count the percentage of remaining seats
result = {}
# browse = obtiene los objetos de la tabla para manejarlo como objeto
sessions = self.browse(cr,uid,ids,context=context)
for session in sessions :
result[session.id] = self._get_remaining_seats_percent(session.seats,
session.attendee_ids)
return result
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
# domain = filtro a aplicar a los datos de una tabla, proporcionando criterios.
# equivale a las clusulas where de una consulta SQL.
# sintaxis: domain=['operador_para_criterios', ('campo_de_bd', 'operador_logico', 'valor_buscado
boleano'), (otro criterio), (se repite las veces que sea necesario)]
# domain puede estar aqu y afecta a los datos que se muestran en todo el mdulo,
# puede declararse en la vista (xml) y afecta solamente donde se usa sta.
#'instructor_id': fields.many2one('res.partner', 'Instructor', domain=['&',('is_instructor','=', True),
#('supplier','=', True)])
'instructor_id': fields.many2one('res.partner', 'Instructor'),
# la funcin a llamar aqu, se debe declarar siempre arriba de la seccin columns
# esta funcin se ejecuta solamente cuando se abre la vista !
#'remaining_seats_percent': fields.function(_get_remaining_seats_percent,)
'remaining_seats_percent':fields.function(_remaining_seats_percent,
method=True,type='float',
string='Remaining seats'),
}
openacademy_session()
Es un mtodo que recibe 2 parametros seats y attendee donde attendee es un diccionario como en el metodo de ejercicio anterior que contiene los varios participantes.
El metodo llama a otro metodo que creamos en ejercicio pasado para calcular el
porcentaje enviando tambin los parametros seats y attendee y recibe el porcentaje correspondiente luego creamos un diccionario en blanco y luego actualizamos su valor
con un metodo update para fins didaticos muestra que en python las variables tienen
138
139
Con el codigo bastante comentado queda bastado creo que queada bien explicado
sus funcionamiento.
El que vale comentario son para el _contraints que debemos crear un metodo que
retorne solamente verdadero o falso y es pasado como primer parametro, leugo pasamos el mensaje que sera presentado al usuario y como ultimo parametro pasamos a
los campos que openerp va quedar monitoreando para dispara la constraints.
140
Como hicimos los contraints que verifican que no se pueda duplicar un nombre de
curso si tratamos de utilizar el recurso de openerp de duplicar un registro nos va generar un error acionando la constraints para eso tenemos que re-implementar el mtodo copy estandart para que por ejemplo agregue al nombre nuevo el termino (copia).
Agregamos le metodo abajo en la clase curso siempre antes de la definicin de los
campos.
Estamos utilizando el copy original de openERP y estamos cambiando su comportamiento
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
course_obj = self.pool.get('openacademy.course')
course = course_obj.browse(cr, uid, id, context=context)
new_name = course.name + ' (copy)'
list = course_obj.search(cr, uid, [('name','like',new_name)], context=context)
if len(list)>0:
new_name='%s (%s)' % (new_name,len(list)+1)
default.update({'name': new_name}):
# aqu instanciamos el copy original y le pasamos los parmetros de nuestra version
new_id = super(openacademy_course, self).copy(cr, uid, id, default, context=context)
return new_id
141
Crear en la clase session un campo nuevo llamado active del tipo boolean el mismo
es un campo de nombre reservado por openerp con la funcin automtica de definir
la visibilidad del registro en las consultas.
Vamos tambin atribuir valores por defecto en el campo start_date y cambiar su tipo
de campo de date para datetime. Para esto tenemos que hacer un import en el archivo python de un paquete llamado time que possee funciones de python para tratar
campos del tipo hora. Agreguemos tambien el valor por defecto al campo active
para true.
import time
class openacademy_session(osv.osv): # nombre de la clase para PYTHON
_name = 'openacademy.session' # nombre del objeto para OPENERP
def onchange_remaining_seats(self, cr, uid, ids, seats, attendee_ids):
# mandamos calcular el porcentaje remanente de asientos, llamando a la funcin ya existente
remaining_seats_percent = self._get_remaining_seats_percent(seats, attendee_ids)
# openERP espera que las funciones onchange devuelvan un diccionario como valor.
res = {}
# siempre hay que devolver un valor "value", ya que es la palabra reservada para regresar valores.
res.update({
'value': {
'remaining_seats_percent': remaining_seats_percent,#campo del modelo
}
})
if seats < 0:
#if remaining_seats_percent < 0:
# sin embargo se pueden devolver mensajes de advertencia al usuario,
# mediante la palabra reservada warning.
res.update({
'warning': {
'title': 'Warning',
'message': 'You cannot have negative seats',
}
})
# aqu estamos recuperando de la base de datos el valor original guardado
# de la cantidad de asientos definidos al curso.
session_obj = self.pool.get('openacademy.session')
session = session_obj.browse(cr, uid, ids, context=None)[0]
# y los asignamos nuevamente al campo en el formulario.
res['value']['seats'] = session.seats
# de la misma manera se devuelve el valor original del porcentaje.
res['value']['remaining_seats_percent'] = session.remaining_seats_percent
return res
def _get_remaining_seats_percent(self, seats, attendee_list):
return seats and ((100.0 * (seats - len(attendee_list)))/ seats) or 0
def _remaining_seats_percent(self, cr, uid, ids, field, arg, context=None):
#count the percentage of remaining seats
result = {}
# Quitar comentarios a estos campos de print para que muestre en la consola
# los valores que est pasando openERP a las funciones !
#print "uid",uid
#print "ids",ids
#print "field",field
# browse = obtiene los objetos de la tabla para manejarlo como objeto
# todas las funciones donde aparecen estos argmentos los pasa openERP en
# automtico cr= cursor, uid=userID, ids=dependiendo de la vista, regresa el id de
# del registro donde este posicionado si es una lista devuelve todos los ids de
# esa vista, context=equivale a **params (pasar todos los parmetros extras que queramos)
sessions = self.browse(cr, uid, ids, context=context)
for session in sessions :
result[session.id] = self._get_remaining_seats_percent(session.seats, session.attendee_ids)
return result
_columns = {
'name': fields.char('Name', size=128, required=True),
#'start_date': fields.date('Start Date'),
'start_date': fields.datetime('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
# domain = filtro a aplicar a los datos de una tabla, proporcionando criterios.
# equivale a las clusulas where de una consulta SQL.
# sintaxis: domain=['operador_para_criterios', ('campo_de_bd', 'operador_logico', 'valor_buscado
# boleano'), (otro criterio), (se repite las veces que sea necesario)]
# domain puede estar aqu y afecta a los datos que se muestran en todo el mdulo,
# puede declararse en la vista (xml) y afecta solamente donde se usa sta.
142
143
En la vista tree de la clase session, hagamos que cuando la duracin sea menor que
5 el color de la linea sea #00ff00 y rojo si la duracin sea mayor de 15.
En este ejercicio lo que mas tenemos que cuidar es la utilizacin de los simbolos
especiales > y < en el atributo colors. Siguiendo la tabla de caracteres especiales para
la web.
Siendo as nuestra vista quedaria como mostrado abajo:
<!--Define Vista lista-->
<record model="ir.ui.view" id="view_openacademy_session_tree">
<field name="name">view.openacademy.session.tree</field>
<field name="model">openacademy.session</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="session" colors='#00ff00:duration<5;red:duration>15'>
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
<field name="course_id"/>
<field name="remaining_seats_percent" widget="progressbar"/>
<field name="active"/>
<field name="instructor_id"/>
</tree>
</field>
</record>
144
Crear una vista nueva tipo calendario para la clase session usando los parametros
abajo:
date_start
campo start_date
date_delay
campo duration
day_length
'1'
color
campo instructor_id
Campo a mostrar
name
Una Simples implementacin ya que esta bien documentado utilizamos pocos parametros donde:
data_start es el campo que va utilizar para definir el inicio de elvento
date_delay o date_stop son tiempo de duracion en horas o date_stop cuando definimos una fecha final.
day_length cantidad de horas de un dia de trabajo estandart 8 horas.
Color se define el campo que va cambiar los colores en representacin en el calendario normalmente es un campo relacional.
<record model="ir.ui.view" id="view_openacademy_session_form">
<field name="name">view.openacademy.session.form</field>
<field name="model">openacademy.session</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="session">
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats" on_change="onchange_remaining_seats(seats, attendee_ids)"/>
<field name="course_id"/>
<field name="remaining_seats_percent" widget="progressbar"/>
<field name="active"/>
<!--Aqu se muestran subvistas dependiendo de donde
se acceda, ya sea el formulario la lista. Se
hizo porque apareca el campo de session cuando
ya estbamos en una nueva.
Hacer pruebas !!!!!-->
<field name="instructor_id" domain="['&', ('is_instructor', '=', True),
('supplier','=',True) ]"/>
<field name="attendee_ids" colspan="4" on_change="onchange_remaining_seats(seats,
attendee_ids)">
<form string="Attendees">
<field name="partner_id"/>
</form>
<tree string="Attendees" editable="top">
<field name="partner_id"/>
</tree>
</field>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_openacademy_session_calendar">
<field name="name">view.openacademy.session.calendar</field>
<field name="model">openacademy.session</field>
<field name="type">calendar</field>
<field name="arch" type="xml">
<calendar string="Session Calendar"
date_start="start_date"
date_delay="duration"
day_length="1"
color="instructor_id">
<field name="name"/>
</calendar>
</field>
</record>
145
Veamos que con solamente agregando en el action en view model la vista calendarios y como no tenemos otra vista correspondiente el ya reconoce la vista declarada
anteriormente.
146
Agregar en la clase cursos un campo calculado que sumen los participantes de todas
la secciones el campo sera llamado 'attendee_count'.
Tambin agregar el campo funcion 'attendee_count' en la clase session que contenga el total de participantes de la session.
En modo general tenemos que cambiar la clase cursos y agregar el nuevo campo y
el mtodo que va ejecutar al asesar este campo.
class openacademy_course(osv.osv): # nombre de la clase para PYTHON
_name = 'openacademy.course' # nombre del objeto para OPENERP
def _get_attendee_count(self, cr, uid, ids, field, arg, context=None):
res = {}
session_obj = self.pool.get('openacademy.session')
course_obj = self.pool.get('openacademy.course')
courses = course_obj.browse(cr, uid, ids, context=context)
for course in courses:
attendee_cont = 0
for session in course.session_ids:
attendee_cont += len( session.attendee_ids )
res[ course.id ] = attendee_cont
return res
_columns = {
'name' : fields.char('Name', size=128, required=True),
'description' : fields.text('Description'),
'responsible_id' : fields.many2one('res.users', 'Responsible'),
'session_ids': fields.one2many('openacademy.session', 'course_id', 'Sessions'),
'attendee_count': fields.function(_get_attendee_count, type='integer', string='Attendee Count', method=True),
}
def _check_description(self,cr,uid,ids,context=None):
courses = self.browse(cr,uid,ids,context=context)
check = True
for course in courses:
#print course.description==course.name
if course.name==course.description:
return False
return check
_constraints = [(_check_description, 'Please use a different description', ['name','description'])]
_sql_constraints = [('unique_name', 'unique(name)', 'Course Title must be unique'),]
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
course_obj = self.pool.get('openacademy.course')
course = course_obj.browse(cr, uid, id, context=context)
new_name = course.name + ' (copy)'
list = course_obj.search(cr, uid, [('name','like',new_name)], context=context)
if len(list)>0:
new_name='%s (%s)' % (new_name,len(list)+1)
default.update({'name': new_name})
new_id = super(openacademy_course, self).copy(cr, uid, id, default, context=context)
return new_id
openacademy_course()
147
res={}
inicializamos la variable res con un diccionario vazio.
Session_obj = self.pool.get('openacademy.session')
levanta en la variable session_obj los atributos de la clase openacademy.session
course_obj = self.pool.get('openacademy.course')
Lo mismo a lo anterior pero sobre la clase openacademy.course.
Courses = courses_obj.browse(cr,uid,ids,context=context)
trae al objeto courses un diccionario con los registros de cursos.
For course in courses:
por cada registro en el diccionario courses atribuye al objeto curses
attendee_cont=0
inicializa la variabel con 0
for session in course.session_ids:
para cada registro en el diccionario course.session_ids armazena en la variable session
sus valores.
attendee_cont += len( session.attendee_ids )
lee el contenido de session.attendee_ids y cuenta con len cuantos objetos tiene asignado o sea cuantos participantes id posee el objeto session.attendee_ids y suma a la variables attendee_cont como es un bucle va leer de todos las sessiones de los cursos.
Sumando todos los participantes.
res[ course.id ] = attendee_cont
es el retorno del metodo que nos trae el course.id referido y el valor sumado en attendee_count
148
149
Vista session_form:
150
Definir una vista tipo gantt para la clase sessin con los mismos datos de la vista
Calendar, cambiando el attributo color para que utilize course_id
Cremos mas una vista del ahora del tipo gantt como sigue abajo:
<!--Define Vista gantt-->
<record model="ir.ui.view" id="view_openacademy_session_gantt">
<field name="name">view.openacademy.session.gantt</field>
<field name="model">openacademy.session</field>
<field name="type">gantt</field>
<field name="arch" type="xml">
<gantt string="Session Gantt"
date_start="start_date"
date_delay="duration"
day_length="1"
color="course_id">
<level object="res.partner" link="instructor_id">
<field name="name"/>
</level>
</gantt>
</field>
</record>
151
Definir una vista graphs, tipo bar en la session, que contenga como campo X el
course_id y como eje Y el campo attendee_count sumando los mismos
Bastante sensillo ya que definimos en la classe session un campo calculado attendee_count que nos muestra el total de participantes por session.
<!--Define Vista graph-->
<record model="ir.ui.view" id="view_openacademy_session_graph">
<field name="name">view.openacademy.session.graph</field>
<field name="model">openacademy.session</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<graph string="Participations by Courses" type="bar">
<field name="course_id"/>
<field name="attendee_count" operator="+"/>
</graph>
</field>
</record>
Es importante salientar que todo el trabajo duro en las vistas debe siempre ser tratados, calculados y guardados, por los campos calculados (tipo funcin) en el codigo de
la clase de los modulos.
152
Crear una vista de busqueda (search) para la clase cursos, que tenga un filtro mis
cursos, que filtre atravs del dominio "[('responsible_id','=',uid)]" que el icono utilizado sea "terp-partner". Esto nos resume que va listar en el grid solamente los cursos que el responsable sea el usuario que registro los cursos y quedo como responsable. Y que tambin que este filtro venga como estandarte seleccionado.
En primero creamos la vista Search.
<!--Define Vista Search-->
<record model="ir.ui.view" id="view_openacademy_course_search">
<field name="name">view.openacademy.course.search</field>
<field name="model">openacademy.course</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Course Search">
<filter string="My Courses" icon="terp-partner"
name="my_courses"
domain="[('responsible_id','=',uid)]"
help="My own ideas"/>
<field name="name"/>
</search>
</field>
</record>
La definicin de la vista Search es basicamente igual a la vista form o tree, solamente tenemos un nuevo atributo search que se le pasa el string con el nombre del comando icon con el icono, name con el nombre para referencia el el context, luego el
domain que es el filtro propiamente dicho del botn. Leugo de definir la vista tenemos
que pasar a la action que la misma sera utilizada. Cambiando el action que ya lo tenamos.
<!--Acciones-->
<record model="ir.actions.act_window" id="action_openacademy_course_form">
<field name="name">Courses</field>
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_openacademy_course_search"/>
<field name="context">{'search_default_my_courses': 1}</field>
<!--<field name="limit">100</field>-->
</record>
153
Crear una vista de busqueda (search) para la clase session. Es recomendable utilizar
la vista search para definir los campos de busqueda que tambien se puede utilizar el
atributo select='1' en la vista tree.
En la versin anterior de openerp las vistas Tree tenan las busquedas definidas por
el atributo select=1 definidos en los fields, que tambin tiene el objetivo de mostrar
al orm que debe crear un indice para estos campos. Con la legada de las vistas tipo
Search por conversin debriamos utilizar esta vista para definir estos campos de busqueda ademas de darnos la posibilidad de crear los botones de dominios.
Abajo definimos las vista tipo search para las sessiones.
<!--Define Vista search-->
<record model="ir.ui.view" id="view_openacademy_session_search">
<field name="name">view.openacademy.session.search</field>
<field name="model">openacademy.session</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Session Search">
<field name="name"/>
<field name="instructor_id"/>
</search>
</field>
</record>
154
Editar la vista de cursos, que tenga el dominio de bsqueda de mas un botn, que
filtre los que no son mis cursos. Dejando como estandarte que no quede seleccionado al abrir la bsqueda o sea pasarlo por el context
Veamos la modificacin en la vista.
<!--Define Vista Search-->
<record model="ir.ui.view" id="view_openacademy_course_search">
<field name="name">view.openacademy.course.search</field>
<field name="model">openacademy.course</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Course Search">
<filter string="My Courses" icon="terp-partner"
name="my_courses"
domain="[('responsible_id','=',uid)]"
help="My own ideas"/>
<filter string="Not my Courses" icon="terp-partner"
name="not_my_courses"
domain="[('responsible_id','<>',uid)]"
help="Other people ideas"/>
<field name="name"/>
</search>
</field>
</record>
Lo unico que hacemos es crear un nuevo atributo filter con la nueva bsqueda, resalvando que utilizamos
'<>'
155
Editar la vista search de cursos, que tengamos una agrupacin de los registros por
responsable y tambin agrupacin por nombre.
De forma muy similar a la utilizacin de la tag filter para filtar registros con el auxilio de los dominios, podremos agrupar los registros con el auxilio del context.
<!--Define Vista Search-->
<record model="ir.ui.view" id="view_openacademy_course_search">
<field name="name">view.openacademy.course.search</field>
<field name="model">openacademy.course</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Course Search">
<filter string="My Courses" icon="terp-partner"
name="my_courses"
domain="[('responsible_id','=',uid)]"
help="My own ideas"/>
<filter string="Not my Courses" icon="terp-partner"
name="not_my_courses"
domain="[('responsible_id','<>',uid)]"
help="Other people ideas"/>
<separator orientation="vertical"/>
<field name="name"/>
<newline/>
<group expand="0" string="Group By...">
<filter string="Responsible" icon="terp-personal" domain="[]" context="{'group_by' :
'responsible_id'}"/>
<filter string="Name" icon="terp-personal" domain="[]" context="{'group_by' : 'name'}"/>
</group>
</search>
</field>
</record>
156
Crear los archivos de datos para el mtodo de seguridad del Modulo openacademy,
tendriamos 2 grupos openacademy Manager e openacademy user, y que los usuario
no puedan borrar registros.
Tenemos que crear 2 archivos, tenemos que empezar por el xml que contiene los
grupos, para mantener la organizacin del modulo creamos una carpeta llamada security
donde
tendremos
los
dados
necesarios
creamos
el
xml
security/openacademy_security.xml.
<?xml version="1.0"?>
<openerp>
<data noupdate="0">
<!-Users Groups
-->
<record model="res.groups" id="group_openacademy_manager">
<field name="name">OpenAcademy / Manager</field>
</record>
<record model="res.groups" id="group_openacademy_user">
<field name="name">OpenAcademy / User</field>
</record>
</data>
</openerp>
Aqui creamos los acesos, para las 3 clases (session, course, attendee) para los 2
grupos (openacademy manager y openacademy user), analisando los datos arriba tenemos como falso la permisin de borrar (unlink) para os usuarios en las 3 clases.
157
158
Crear un workflow para la clase session, que contenga los estados draft, confirm y
done.
Lo primero necesario para crear el workflow de cambio de estado, es crear el campo nuevo en la clase, session, que contenga los cambios de estado como sigue abajo.
_columns = {
'name': fields.char('Name', size=128, required=True),
'start_date': fields.date('Start Date'),
'duration': fields.float('Duration', help='Duration in days'),
'seats': fields.integer('Seats'),
'course_id': fields.many2one('openacademy.course', 'Course', required=True, ondelete='cascade'),
'attendee_ids': fields.one2many('openacademy.attendee', 'session_id', 'Attendees'),
'instructor_id': fields.many2one('res.partner', 'Instructor'),
'remaining_seats_percent':fields.function(_remaining_seats_percent,
method=True,type='float',
string='Remaining seats'),
'attendee_count': fields.function(_get_attendee_count, type='integer', string='Attendee Count',
method=True),
# state es una palabra reservada - se pasan tuplas como datos a mostrar
# los elementos de la tupla se definen (valor_a_guardar_en_tabla, texto_a_mostrar_al_usuario)
# al final se escribe el label del control.
'state' : fields.selection([('draft','Draft'),('confirm','Confirm'),('done','Done'),], 'State',
readonly='True') ,
'active': fields.boolean('Active', help="Un-Check this field for hidden record."),
}
Creamos un campo com en nombre state que es una palabra reservada de openerp
justamente usada siempre en los workflow, no debriamos utilizar otro nombre. Este
campo sera de tipo selection que va contener los 3 estados Draft (borrador), confirm
(confirmado), done (terminado), el campo sera readonly ya que va ser administrado
por el workflow.
Tambin tenemos que cambiar el parametro del codigo _defaults para que al guardar el registro se grabe con draft.
_defaults = {
'active': True,
'state': 'draft',
'start_date': lambda *a: time.strftime('%Y-%m-%d'),
}
Ademas tenemos que crear los metodos que van ser disparados por el workflow
que va definitivamente cambiar los estados.
def signal_confirm(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
return True
def signal_done(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'done'}, context=context)
return True
El codigo arriba simplemente utilizando del metodo write cambia el estado de Draft para confirm en el primer metodo y en el segundo cambia de de confirm a done esto
siguiendo la logica que vamos implementar en el xml del workflow.
159
En la declaracin tenemos el id que debe ser unico en todo el sistema luego la declaracin de model que siempre va ser workflow, Como queremos que el workflow
sea instanciado al agregamos un registro, definimos en on_create como 1 (true) luego definos el nombre del workflow y lo mas importante definimos en el field OSV
cual clase el workflow va estar relacionado.
Luego tenemos que definir las activity (actividades) del workflow que son los circulos con los procesos que sern ejecutados, lo importante en las definicines de las
actividades es que el action de la misma no es obligatorio y como nuestro workflow
va tratar apenas tratar transado de estado en primer nodo (activity) na va tener definido un accin. Esto se da porque definimos que en los defaults ya ponga como valor
estandart en el campo state como draft caso contrario tambin se podra cambiar por
una atividad del workflow. Veamos el codigo:
<!--activity-->
<record id="workflow_activity_draft0" model="workflow.activity">
<field name="kind">dummy</field>
<field name="name">draft</field>
<field name="join_mode">XOR</field>
<field model="workflow" name="wkf_id" ref="workflow_openacademysessionworkflow0"/>
<field eval="0" name="flow_stop"/>
<field name="split_mode">XOR</field>
<field eval="1" name="flow_start"/>
</record>
<record id="workflow_activity_confirm0" model="workflow.activity">
<field name="kind">function</field>
<field name="name">confirm</field>
<field name="join_mode">XOR</field>
<field model="workflow" name="wkf_id" ref="workflow_openacademysessionworkflow0"/>
<field eval="0" name="flow_stop"/>
<field name="split_mode">XOR</field>
<field name="action">signal_confirm()</field>
<field eval="0" name="flow_start"/>
</record>
<record id="workflow_activity_done0" model="workflow.activity">
<field name="kind">function</field>
<field name="name">done</field>
<field name="join_mode">XOR</field>
<field model="workflow" name="wkf_id" ref="workflow_openacademysessionworkflow0"/>
<field eval="1" name="flow_stop"/>
<field name="split_mode">XOR</field>
<field name="action">signal_done()</field>
<field eval="0" name="flow_start"/>
</record>
160
los
standarts
que
tenga
el
nombre
<field name=flow_stop/>False</field>
que po-
Como en la definicin de actividades tambin pusimos todos los atributos existentes para las transacciones. Las transacciones de este ejemplo es bastante simples definimos los atributos id y el model el id solamente no puede ya existir en todo el sistema y el model debe ser si o si workflow.transition. Luego definimos el signal que va
ser enviado en nuestro caso por los botones disponible en el form presionado por el
usuario que va decir cuando ejecutar la transicin, en la primeira el signal enviado
161
En el botn tenemos el type que dice workflow el name que es la senal que va pasar al transaction del workflow y el estado que tiene el objeto antes del click o sea
arriba el manda la senha signal_confirm si el states es draft o manda signal_done si el
states es confirm.
Ya tenemos nuestro workflow ja implementado pero todavia no va funcionar si no
declaramos el xml del workflow en el modulo. Para eso editamos el archivo __openerp__.py, y agregamos el xml la parametro update_xml.
162
163
Agregar al workflow que solamente permita cambiar al estado a confirmado si la fecha de la seccion sea igua o mayor que da fecha actual.
Nota: para ejecutar un exception en openerp usamos:
raise osv.except_osv( 'Error !', 'mensaje' )
Apenas cambiamos el metodo que es llamado por primero en nuestro workflow
que es el metodo signal_confirm.
def signal_confirm(self, cr, uid, ids, context={}):
for session in self.browse(cr, uid, ids, context=context):
if session.start_date < time.strftime('%Y-%m-%d'):
raise osv.except_osv( 'Error !', 'Start Date minor to now' )
self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
return True
Aqu ya es importante relacionar algunos conceptos importantes openerp permite trabajar con varios registros a la vez, por exemplo si tenemos este metodo llamado por
un actin general los mtodos debern estar preparado para tratar los varios registros
seleccionados por el usuario, por eso for ejemplo tratamos siempre con un bucle for
que va funcionar con 1 o varios registros. En nuestro caso arriba agarra todos los registros seleccionados y que son enviados al workflow verifica uno a uno si la fecha es
valida o sea si la fecha de la seccion es menor que la fecha actual si es asi envia un
raise que e una excepcin del python y cancela y hace el rowback de la transation.
164
165
Tenemos los mismos import que en la definicin de la clase osv.osv, luego la definicion de 2 clases heredadas de osv.osv_memory pero como si fueran amb.
Lo mas importante en esta clase son los 2 metodos declarados _get_action_session
y action_add_attendee.
Analisando el codigo de la clase
En esta
clase openerp utiliza de context como parametro que en nuestro caso va traer los registros selecionado o posicionado en el momento de llamar el wizard. En la clase para
facilitar la comprensin en el codigo pasamos un print context que nos va listas el
contenido del context un exemplo seria:
context de defaults {'lang': u'en_US', u'active_ids': [6], 'tz': False, 'uid': 1, u'active_model':
u'openacademy.session', 'project_id': False, u'active_id': 6}
Vamos esclarecer algunas cosas, en openerp es posible tener traducciones no solamente del campo label como tambin del contenido del campo, por ejemplo, un registro de de un producto puede tener su descripcin diferente para cada idioma definido al usuario. Si vemos en el context nos muestra que el registro es del lang en_US.
Luego tambin nos pasa que los registros activos si son multiplos (varias selecciones),
si el contendio de datos esta compactado, el uid que es el ID del usuario del sistema
que en nuestro ejemplo es el admin, active_model que es el nombre del modulo, si
tiene codigo de projecto el modulo, y el registro actual posicionado en la base de datos.
En los campos tipo string notamos el u antes de las descripciones esto es como python maneja para definir que el string es uft-8.
En la llamada del metodo context.get pasamos el registro que queremos que es el
valor de active_id, y tenemos un en el codigo de pasando como parametro luego la expresin false como abajo:
context.get('active_id', False)
Esto va traer el valor de este campo que esta en el contexto y el segundo parametro
es si se trata del registro actual o podriamos pasar el id del registro como queremos
que sea el registro actual pasamos false.
166
verifica si tenemos datos en el context si esta vazio quiere decir que no esta seleccionado ningun registro verifica si es llamado por la clase openacademy.session e retorna false, que genera una except del orm y presenta una mensaje que es necesario
seleccionar algun registro.
Si un registro esta activo trae el campo active_id que es el ID del registro selecionado como este metodo es llamado por un _defaults completa el campo session_id
con el ID retornado.
El segundo metodo se ejecuta una vez clicado en el OK el mismo agarra los participantes cargados en el wizard y agrega en los registro seleccionados o en el registro
actual.
Luego vamos a definir la vista, action, y la definicin de este wizard en los 2 menus diferentes.
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_openacademy_create_attendee_wizard_form">
<field name="name">view.openacademy.create.attendee.wizard.form</field>
<field name="model">openacademy.create.attendee.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Add Attendee">
<field name="session_id"/>
<newline/>
<field name="attendee_ids" colspan="4">
<tree string="Attendees" editable="bottom">
<field name="partner_id"/>
</tree>
</field>
<button type="object" name="action_add_attendee" string="_Add Attendees" icon="gtk-ok"
confirm="Are you sure you want to add those attendees?"/>
<button type="special" special="cancel" string="_Cancel" icon="gtk-cancel"/>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_openacademy_create_attendee_wizard_form">
<field name="name">Add attendee</field>
<field name="res_model">openacademy.create.attendee.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem name="Add attendee" parent="menu_main_openacademy"
id="menu_action_openacademy_create_attendee_wizard_form"
action="action_openacademy_create_attendee_wizard_form"
/>
<act_window id="act_window_action_openacademy_create_attendee_wizard_form"
name="Add Attendees"
src_model="openacademy.session"
res_model="openacademy.create.attendee.wizard"
view_mode="form"
key2="client_action_multi"
target="new"
/>
</data>
</openerp>
167
O por el sidebar:
<act_window id="act_window_action_openacademy_create_attendee_wizard_form"
name="Add Attendees"
src_model="openacademy.session"
res_model="openacademy.create.attendee.wizard"
view_mode="form"
key2="client_action_multi"
target="new"
/>
168
Corregir el wizard llamado por el men principal del modulo que genera error.
Como creamos un acceso del wizard tambin por el menu principal del modulo, tenemos que modificar le mtodo action_add_attendee ya que el mismo esta preparado
solamente para ser llamado cuando pasado en el context los registros a guardar los
participantes, pero como al ser llamado por el men principal estos datos no son pasados.
def action_add_attendee(self, cr, uid, ids, context=None):
print "Este es el contexto", context
if not context:
context = {}
wizard_data = self.browse(cr, uid, ids[0], context=context)
attendee_db = self.pool.get('openacademy.attendee')
if context.get('active_model') == 'openacademy.session':
los_ids = context.get('active_ids')
for data in los_ids:
for attendee in wizard_data.attendee_ids:
attendee_db.create(cr, uid, {
#'name': attendee.name,
'partner_id': attendee.partner_id.id,
'session_id': data,
}, context=context)
else:
for attendee in wizard_data.attendee_ids:
attendee_db.create(cr, uid, {
'partner_id': attendee.partner_id.id,
'session_id': wizard_data.session_id.id,
}, context=context)
return {}
169
En el Wizard creamos que el mismo sea posible ser llamado cuando seleccionamos
varios registro o apenas 1 registro de la clase session cuando seleccionado varios registros en nos trae la sessin de primer registro seleccionado en el tree, haora vamos
implementar que verifique si tenemos varios registros seleccionados y ocultamos
(atributo invisible) el campo session en la vista de este wizard.
Para aplicar este funcionamiento simplesmente cambiamos la vista del wizard con
el codigo abajo en el archivo openacademy/wizard/create_attendee_view.xml:
<record model="ir.ui.view" id="view_openacademy_create_attendee_wizard_form">
<field name="name">view.openacademy.create.attendee.wizard.form</field>
<field name="model">openacademy.create.attendee.wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Add Attendee">
<field name="session_id" invisible="len(context.get('active_ids',[]))>1"
/>
<newline/>
<field name="attendee_ids" colspan="4">
<tree string="Attendees" editable="bottom">
<field name="partner_id"/>
</tree>
</field>
<button type="object" name="action_add_attendee" string="_Add Attendees" icon="gtk-ok"
confirm="Are you sure you want to add those attendees?"/>
<button type="special" special="cancel" string="_Cancel" icon="gtk-cancel"/>
</form>
</field>
</record>
170
Una observacin importante como las vistas son basadas en los action y no en la
vista propriamente dicha, y normalmente en una vista hacemos que tenga 1 actin
para varias tipos de vistas, por exemplo en nuestro modulo para session tenemos 1 action pero es el mismo para todos los tipos de vista, tipo graph, tree, form, calendar etc.
Veamos el action que tenemos en la vista:
<record model="ir.actions.act_window" id="action_openacademy_session_form">
<field name="name">sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,gantt,graph</field>
<field name="search_view_id" ref="view_openacademy_session_search"/>
<!--<field name="limit">100</field>-->
</record>
name="view_mode">tree,form,calendar,gantt,graph
Para
poder utilizar en el dashboard tenemos que crear los action para cada una de las vistas
mostrada en el dashboard.
171
Lo mas importante son las sineas que hacen referencia a las vistas en si. Por ejemplo openacademy.view_openacademy_session_graph que es el nombre de la vista
creada en el archivo openacademy_view.xml. Lo mismo hacemos con las otras 2 vistas que queremos en nuestro dashboard.
Despues creamos el dashboard propriamente dicho:
<record model="ir.ui.view" id="board_session_form">
<field name="name">Session Dashboard Form</field>
<field name="model">board.board</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Session Dashboard">
<board style="2-1">
<column>
<action name="%(act_session_graph)d" string="Attendees by course" />
<action name="%(act_course_list)d" string="Courses" />
</column>
<column>
<action name="%(act_session_calendar)d" string="Sessions" />
</column>
</board>
</form>
</field>
</record>
172
173
Hacer todo lo necesario para que nuestro modulo openacademy este totalmente al
idioma espaol paraguay.
En nuestro modulo tenemos 3 archivos python que podran tener expresiones que
no fueron pasada para el modulo de traducciones de openerp. Serian openacademy.py,
partner.py y wizard/create_attendee.py.
Todo lo que sea string tenemos que utilizar la funcin _(), para que openerp pueda
guardar para que sea traducido los mensajes.
Aqu esta el archivo openacademy.py con las modificaciones necesarias.
# -*- coding: utf-8 -*from osv import osv
from osv import fields
import time
from tools.translate import _
class openacademy_course(osv.osv):
_name = 'openacademy.course'
def _get_attendee_count(self, cr, uid, ids, field, arg, context=None):
res = {}
session_obj = self.pool.get('openacademy.session')
course_obj = self.pool.get('openacademy.course')
courses = course_obj.browse(cr, uid, ids, context=context)
for course in courses:
attendee_cont = 0
for session in course.session_ids:
attendee_cont += len( session.attendee_ids )
res[ course.id ] = attendee_cont
return res
_columns = {
'name' : fields.char('Name', size=128, required=True),
'description' : fields.text('Description'),
'responsible_id' : fields.many2one('res.users', 'Responsible'),
'session_ids': fields.one2many('openacademy.session', 'course_id', 'Sessions'),
'attendee_count': fields.function(_get_attendee_count, type='integer', string=_('Attendee Count'),
method=True),
}
def _check_description(self,cr,uid,ids,context=None):
courses = self.browse(cr,uid,ids,context=context)
check = True
for course in courses:
if course.name==course.description:
return False
return check
_constraints = [(_check_description, _('Please use a different description'), ['name','description'])]
_sql_constraints = [('unique_name', 'unique(name)', _('Course Title must be unique')),]
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
course_obj = self.pool.get('openacademy.course')
course = course_obj.browse(cr, uid, id, context=context)
new_name = course.name + ' (copy)'
list = course_obj.search(cr, uid, [('name','like',new_name)], context=context)
if len(list)>0:
new_name='%s (%s)' % (new_name,len(list)+1)
default.update({'name': new_name})
new_id = super(openacademy_course, self).copy(cr, uid, id, default, context=context)
return new_id
openacademy_course()
class openacademy_session(osv.osv):
_name = 'openacademy.session'
def onchange_remaining_seats(self, cr, uid, ids, seats, attendee_ids):
remaining_seats_percent = self._get_remaining_seats_percent(seats, attendee_ids)
res = {}
res.update({
'value': {
'remaining_seats_percent': remaining_seats_percent,
174
175
176