cd specific-parts
mkdir open_academy
cd open_academy
touch __init__.py
nano __openerp__.py
{'name': 'Open Academy'}
# -*- coding: utf-8 -*-
{
'name': "Title",
'summary': "Short subtitle phrase",
'description': """Long description""",
'author': "Your name",
'license': "AGPL-3",
'website': "http://www.example.com",
'category': 'Uncategorized',
'version': '8.0.1.0.0',
'depends': ['base'],
}
Importante: Se não depender de nenhum modulo, ao menos deve depender do modulo base
Qualquer referencia que seu modulo realize com xmls ids, visões ou modelos refenciados por este modulo.
Esta lista garante que tudo será carregado na ordem correta.
.
├── __init__.py
├── __openerp__.py
│
├── controllers
│
└── __init__.py
├── data
├── i18n
├── models
│
└── __init__.py
├── security
├── static
│
└── description
└── views
Um modudo Odoo pode conter três tipos de aquivos:
class A(models.Model):
...
class B(models.AbstractMethod):
...
class C(models.TransientMethod):
...
Os campos de um modelo definiem o que pode ser armazenado e onde. Fields são definidos como atributos da classe.
from openerp import models, fields
class AModel(models.Model):
_name = 'a_name'
name = fields.Char(
string="Name", # Optional label of the field
compute="_compute_name_custom", # Transform the fields in computed fields
store=True, # If computed it will store the result
select=True, # Force index on field
readonly=True, # Field will be readonly in views
inverse="_write_name" # On update trigger
required=True, # Mandatory field
translate=True, # Translation enable
help='blabla', # Help tooltip text
company_dependent=True, # Transform columns to ir.property
search='_search_function' # Custom search function mainly used with compute
)
# The string key is not mandatory by default it wil use the property name Capitalized
name = fields.Char() # Valid definition
Boolean
Boolean type field:
abool = fields.Boolean()
Char
Store string with variable len.:
achar = fields.Char()
Specific options:
Text
Used to store long text.:
atext = fields.Text()
Specific options:
HTML
Used to store HTML, provides an HTML widget.:
anhtml = fields.Html()
Integer
Store integer value. No NULL value support. If value is not set it returns 0:
anint = fields.Integer()
Float
Store float value. No NULL value support. If value is not set it returns 0.0 If digits option is set it will use numeric type:
afloat = fields.Float()
afloat = fields.Float(digits=(32, 32))
afloat = fields.Float(digits=lambda cr: (32, 32))
Date
>>> from openerp import fields
>>> adate = fields.Date()
>>> fields.Date.today()
'2014-06-15'
>>> fields.Date.context_today(self)
'2014-06-15'
>>> fields.Date.context_today(self, timestamp=datetime.datetime.now())
'2014-06-15'
>>> fields.Date.from_string(fields.Date.today())
datetime.datetime(2014, 6, 15, 19, 32, 17)
>>> fields.Date.to_string(datetime.datetime.today())
'2014-06-15'
DateTime
>>> fields.Datetime.context_timestamp(self, timestamp=datetime.datetime.now())
datetime.datetime(2014, 6, 15, 21, 26, 1, 248354, tzinfo=<DstTzInfo 'Europe/Brussels' CEST+2:00:00 DST>)
>>> fields.Datetime.now()
'2014-06-15 19:26:13'
>>> fields.Datetime.from_string(fields.Datetime.now())
datetime.datetime(2014, 6, 15, 19, 32, 17)
>>> fields.Datetime.to_string(datetime.datetime.now())
'2014-06-15 19:26:13'
Binary
Store file encoded in base64 in bytea column:
abin = fields.Binary()
Selection
Store text in database but propose a selection widget. It induces no selection constraint in database. Selection must be set as a list of tuples or a callable that returns a list of tuples:
aselection = fields.Selection([('a', 'A')])
aselection = fields.Selection(selection=[('a', 'A')])
aselection = fields.Selection(selection='a_function_name')
When extending a model, if you want to add possible values to a selection field, you may use the selection_add keyword argument:
class SomeModel(models.Model):
_inherits = 'some.model'
type = fields.Selection(selection_add=[('10', '10ª opção'), ('11', '11ª opção')])
Reference
Store an arbitrary reference to a model and a row:
aref = fields.Reference([('model_name', 'String')])
aref = fields.Reference(selection=[('model_name', 'String')])
aref = fields.Reference(selection='a_function_name')
Specific options:
Many2one
Store a relation against a co-model:
arel_id = fields.Many2one('res.users')
arel_id = fields.Many2one(comodel_name='res.users')
an_other_rel_id = fields.Many2one(comodel_name='res.partner', delegate=True)
One2many
Store a relation against many rows of co-model:
arel_ids = fields.One2many('res.users', 'rel_id')
arel_ids = fields.One2many(comodel_name='res.users', inverse_name='rel_id')
Many2many
Store a relation against many2many rows of co-model:
arel_ids = fields.Many2many('res.users')
arel_ids = fields.Many2many(comodel_name='res.users',
relation='table_name',
column1='col_name',
column2='other_col_name')
Crie um arquivo na pasta models, chamado de course.py com o conteudo:
# -*- coding: utf-8 -*-
from openerp import models, fields, api
class Course(models.Model):
_name = 'openacademy.course'
name = fields.Char(string="Title", required=True)
description = fields.Text()
Crie um arquivo __init__.py na pasta models importando o seu modulo:
from . import course
Edite o arquivo __init__.py da raiz para importar a pasta models:
from . import models
Atualize seu modulo e verifique o banco de dados foi alterado e as tabelas de dados.
Depois que um módulo Odoo é carregado a maioria dos arquivos é convertida em dados e salva no banco de dados da instância. Representando:
Crie um arquivo openacademy/demo/demo.xml para incluir alguns dados:
<openerp>
<data>
<record model="openacademy.course" id="course0">
<field name="name">Course 0</field>
<field name="description">Course 0's description
Can have multiple lines
</field>
</record>
<record model="openacademy.course" id="course1">
<field name="name">Course 1</field>
<!-- no description for this one -->
</record>
<record model="openacademy.course" id="course2">
<field name="name">Course 2</field>
<field name="description">Course 2's description</field>
</record>
</data>
</openerp>
Visões personalizadas definiem a forma como os dados serão exibidos e organizados nos diversões tipos de visões:
Exemplo:
<record id="meu_modulo_view_type" model="ir.ui.view">
<field name="name">Meu modulo Type</field>
<field name="model">meu.modulo</field>
<field name="arch" type="xml">
<!-- view content: <form>, <tree>, <graph>, ... -->
</field>
</record>
<record id="meu_modulo_view_form" model="ir.ui.view">
<field name="name">Meu modulo Form</field>
<field name="model">meu.modulo</field>
<field name="arch" type="xml">
<form>
<group>
<field name="name"/>
<field name="partner_ids" widget="many2many_tags"/>
</group>
<group>
<field name="date"/>
</group>
</form>
</field>
</record>
<record id="meu_modulo_view_tree" model="ir.ui.view">
<field name="name">Meu Modulo List</field>
<field name="model">meu.modulo</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="date"/>
</tree>
</field>
</record>
<record id="meu_modulo_view_search" model="ir.ui.view">
<field name="name">Meu modulo Search</field>
<field name="model">meu.modulo</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="partner_ids"/>
<filter string="S/ Parceiros"
domain="[('partner_ids','=',False)]"/>
</search>
</field>
</record>
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="course_form_view">
<field name="name">course.form</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<form string="Course Form">
<sheet>
<group>
<field name="name"/>
<field name="description"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- window action -->
<!--
The following tag is an action definition for a "window action",
<record model="ir.ui.view" id="course_search_view">
<field name="name">course.search</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="description"/>
</search>
</field>
</record>
def do_operation(self):
print self # => a.model(1, 2, 3, 4, 5)
for record in self:
print record # => a.model(1), then a.model(2), then a.model(3), ...
Recordsets proveem um padrão denominado “Active-Record”:
Em Engenharia de software, active record é um padrão de projeto encontrado em softwares que armazenam seus dados em Banco de dados relacionais. Assim foi nomeado por Martin Fowler em seu livro Patterns of Enterprise Application Architecture[1].
A interface de um certo objeto deve incluir funções como por exemplo:
Portanto modelos podem ser escritos e lidos de forma direta através de um record.
Mas somente nos singletons(apenas uma instancia de model). Setar um field dispara um update no banco de dados. Exemplo
>>> record.name
Example Name
>>> record.company_id.name
Company Name
>>> record.name = "Bob"
>>> record.do_operation()
bin/ipython_openerp #execute na raiz do odoo
>>> group = self.env['res.groups'].search([])
>>> r = group.sorted(key=lambda r: r.name)
>>> for b in r:
... b.name
...
u'Access Rights'
u'Contact Creation'
u'Employee'
u'Manager'
u'Multi Companies'
u'Multi Currencies'
u'Notes / Fancy mode'
u'Portal'
u'Public'
u'Settings'
u'Shared Group'
u'Technical Features'
u'User'
u'User
Filtrando recordsets:
recset.filtered(lambda record: record.company_id == user.company_id)
# or using string
recset.filtered("product_id.can_be_sold")
Ordenando:
# sort records by name
recset.sorted(key=lambda r: r.name)
Map:
recset.mapped(lambda record: record.price_unit - record.cost_price)
# returns a list of name
recset.mapped('name')
# returns a recordset of partners
recset.mapped('invoice_id.partner_id')
Example:
>>> g.mapped('users')
res.users(1, 4)
>>> g.mapped('name')
[u'Access Rights', u'Contact Creation', u'Employee', u'Manager',
u'Multi Companies', u'Multi Currencies', u'Notes / Fancy mode', u'Portal',
u'Public', u'Settings', u'Shared Group', u'Technical Features', u'User',
u'User']
Ids é um atributo especial do recorset.
>>> groups.ids
[3, 11, 5, 10, 6, 7, 12, 1, 2, 4, 14, 8, 9, 13]
>>> groups
res.groups(3, 11, 5, 10, 6, 7, 12, 1, 2, 4, 14, 8, 9, 13)
>>> self.search([('is_company', '=', True)])
res.partner(7, 6, 18, 12, 14, 17, 19, 8,...)
>>> self.search([('is_company', '=', True)])[0].name
'Camptocamp'
>>> self.env['res.users'].search([('login', '=', 'admin')])
res.users(1,)
Exemplo
Out[59]: res.partner(31, 30, 29, 28, 27, 26, 24, 23, 22, 21)
In [63]: partner.search([], limit=10, offset=20) Out[63]: res.partner(12, 13, 14, 15, 18, 16, 38, 53, 52, 39)
In [64]: partner.search([], limit=10, offset=10) Out[64]: res.partner(20, 19, 18, 17, 16, 15, 14, 13, 12, 11)
In [68]: obj = partner.search([], limit=1)
In [69]: obj.name Out[69]: u’EMPRESA’
# Todos os registros utilizamos o dominio vazio.
In [72]: partner.search([])
Out[72]: res.partner(9, 6, 1, 8, 7, 10, 11, 12, 13, 14, 15, 18, 16, 19, 17, 20, 23, 24, 21, 22, 26, 27, 28, 29, 30, 31, 35, 43, 32, 33, 48, 49, 51, 38, 40, 41, 45, 42, 59, 57, 58, 63, 64, 65, 47, 50, 60, 54, 55, 56, 62, 44, 34, 66, 68, 69, 39, 52, 53, 67, 46, 37, 36, 61, 3, 5, 70, 72, 71)
Tipos de operadores
'=', '!=', '<=', '<', '>', '>=', '=?', '=like', '=ilike', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of'
Browsing é o forma padrão de se retornar dados do banco de dados, ele retorna um recordset:
>>> self.browse([1, 2, 3])
res.partner(1, 2, 3)
@api.one
...
self.write({'key': value })
# or
record.write({'key': value})
@api.multi
...
self.write({'key': value })
# It will write on all record.
self.line_ids.write({'key': value })
It will write on all Records of the relation line_ids
Crie um modelo para sessao do curso: - Um curso pode ser ministrado em determinada data; - Um curso deve ter um alunos inscritos alvo;
Cada sessão deve ter: - Um nome; - Um número de lugares; - Uma data de início; - Duração em dias;
class Session(models.Model):
_name = 'openacademy.session'
name = fields.Char(required=True)
start_date = fields.Date()
duration = fields.Float(digits=(6, 2), help="Duration in days")
seats = fields.Integer(string="Number of seats")
<record model="ir.ui.view" id="session_form_view">
<field name="name">session.form</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<form string="Session Form">
<sheet>
<group>
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="session_list_action">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="session_menu" name="Sessions"
parent="openacademy_menu"
action="session_list_action"/>
Adapte os modelos e as visões.
class Course(models.Model):
[...]
responsible_id = fields.Many2one('res.users',
ondelete='set null', string="Responsible", index=True)
[...]
class Session(models.Model):
[...]
instructor_id = fields.Many2one('res.partner', string="Instructor")
course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
Relacionamento inverso entre curso e sessões:
class Course(models.Model):
[...]
responsible_id = fields.Many2one('res.users',
ondelete='set null', string="Responsible", index=True)
session_ids = fields.One2many(
'openacademy.session', 'course_id', string="Sessions")
Relacionamento entre Parceiros e Sessões:
attendee_ids = fields.Many2many('res.partner', string="Attendees")