Diferencia entre revisiones de «Wizards en Odoo»
Sin resumen de edición |
|||
Línea 179: | Línea 179: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Wizard amb dades per context == | |||
== Exemple Complet de Wizards == | == Exemple Complet de Wizards == |
Revisión del 17:31 18 ene 2022
Els wizards d'Odoo permeten fer un asistent interactiu per a que l'usuari complete una tasca. Com que no ha d'agafar les dades dirèctament en un formulari, si no que va ajundant-lo a completar-lo, no pot ser guardat en la base de dades fins al final.
Els wizards en Odoo són models que estenen la classe TransientModel en compte de Model. Aquesta classe és molt pareguda, però:
- Les dades no són persistents, encara que es guarden temporalment en la base de dades.
- A partir de odoo 14 necessiten permisos
- Els records dels wizards poden tindre referències Many2one amb el records dels models normals, però no al contrari.
En realitat, no estem fent res molt diferent al que fem en Odoo a exepció del TransientModel. Es tracta de crear formularis i accions igual que podem crear-los per a altres propòsits. Un Wizard és un conjunt de tècniques que s'utilitzen conjuntament sovint. Així, el cicle de vida d'un wizard serà el següent:
- Un botó o el menú de dalt de la vista crida a un action que mostrarà el wizard. Por ser cridat de 4 maneres:
- Per un action ja preexistent en la base de dades amb %()d i un botó de tipus action.
- Per un action ja preexistent que tinga binding_model i per tant ixca en el menú de dalt d'una vista en eixe model.
- Per un action generat per Python i retornat per una funció. (En Odoo totes les funcions cridades desde la vista poden retornar un action que després el client executa).
- La més exòtica és per un action preexistent però obtingut en una funció Python i retornat per aquesta. No és molt freqüent, però pot ser l'única opció si la funció pot o no retornar un wizard i la cridada al mateix es vol definir una vegada només.
- Eixe action, en els wizards, sol obrir una finestra modal (target="new") on es mostren alguns fields del TransientModel.
- La finestra conté un formulari que sol tindre un botó per a enviar, crear o el que es necessite i un especial Cancel que té la seua sintaxi específica.
- Els wizards solen ser assitents que tenen botos de next, back per exemple. Eixe comportament s'implementa amb:
- Un field de tipus selection anomenat state (és important el nom).
- Un header en el formulari amb un widget statusbar per mostrar el progrés.
- Els botons anterior i següent que criden a funcions del TransientModel.
- Aquestes funcions canvien el field state i retornen un action del mateix wizard per refrescar-lo i que no es tanque.
- El formulari té groups o field que es mostren o s'oculten en funció del field state amb una etiqueta específica del XML anomenada states=.
- En cas de tindre un wizard complex en el que omplir Many2many o One2many, tal vegada es necessiten més transientModels per fer relacions. No es poden fer relacions amb models normals.
- Finalment, el wizard acabarà creant o modificant alguns models permanent de la base de dades. Això es fa en una funció. Eixe funció pot retornar un action per mostrar les instàncies creades o per refrescar la vista que l'ha cridat.
Wizard bàsic
A continuació anem a veure un exemple de wizard que sols mostra un formulari i crea una instºancia d'un model a partir de les dades del formulari:
class wizard(models.TransientModel):
_name = 'mmog.wizard'
def _default_attacker(self):
return self.env['mmog.fortress'].browse(self._context.get('active_id')) # El context conté, entre altre coses,
#el active_id del model que està obert.
fortress_attacker = fields.Many2one('mmog.fortress',default=_default_attacker)
fortress_target = fields.Many2one('mmog.fortress')
soldiers_sent = fields.Integer(default=1)
@api.multi
def launch(self):
if self.fortress_attacker.soldiers >= self.soldiers_sent:
self.env['mmog.attack'].create({'fortress_attacking':self.fortress_attacker.id,
'fortress_defender':self.fortress_target.id,
'data':fields.datetime.now(),'soldiers_sent':self.soldiers_sent})
return {}
En el python cal observar la classe de la que hereta, el default, que extrau el active_id del form que a llançat el wizard i el mètode que és cridat pel botó de la vista.
<record model="ir.ui.view" id="wizard_mmog_fortress_view">
<field name="name">wizard.mmog.fortress</field>
<field name="model">mmog.wizard</field>
<field name="arch" type="xml">
<form string="Select fortress">
<group>
<field name="fortress_attacker"/>
<field name="fortress_target"/>
<field name="soldiers_sent"/>
</group>
<footer>
<button name="launch" type="object"
string="Launch" class="oe_highlight"/>
or
<button special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
<act_window id="launch_mmog_fortress_wizard"
name="Launch attack"
binding_model="mmog.fortress"
res_model="mmog.wizard"
view_mode="form"
target="new"
/>
<!-- Forma alternativa recomanada per el manual d'odoo a partir de la versió 14
<record id="launch_mmog_fortress_wizard" model="ir.actions.act_window">
<field name="name">Launch attack</field>
<field name="res_model">mmog.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="model_mmog_fortress"/>
</record> -->
En la vista, tenim creat un form normal amb dos botons. Un d'ells és especial per a cancel·lar el wizard. L'altre crida al mètode. També s'ha creat un action indicant el src_model sobre el que treballa i el model del wizard que utilitza. Els action que criden a wizard tenen l'atribut target a new per a que llance una finestra emergent.
<button name="%(launch_mmog_fortress_wizard)d" type="action" string="Launch attack" class="oe_highlight" />
Si volem, podem ficar un botó que cride al action del wizard. Observem la sintaxi del name, que és igual sempre que el button siga de tipus action, ja que és l'anomenat XML id.
Wizard amb assistent
En aquest exemple anem a fer un wizard amb assistent. Per començar, cal crear un camp state amb varis valors possibles:
state = fields.Selection([
('pelis', "Movie Selection"),
('dia', "Day Selection"),
], default='pelis')
@api.multi
def action_pelis(self):
self.state = 'pelis'
return {
'type': 'ir.actions.act_window',
'res_model': self._name,
'res_id': self.id,
'view_mode': 'form',
'target': 'new',
}
@api.multi
def action_dia(self):
self.state = 'dia'
return {
'type': 'ir.actions.act_window',
'res_model': self._name,
'res_id': self.id,
'view_mode': 'form',
'target': 'new',
}
I uns botons que van fent que passe d'un estar a un altre:
<header>
<button name="action_pelis" type="object"
string="Reset to movie selection"
states="dia"/>
<button name="action_dia" type="object"
string="Select dia" states="pelis"
class="oe_highlight"/>
<field name="state" widget="statusbar"/>
</header>
<group states="pelis">
<field name="cine"/>
<field name="pelicules"/>
</group>
<group states="dia">
<field name="dia"/>
</group>
Després es pot fer que el formulari tinga un aspecte diferent depèn del valor de state
Els wizards poden tornar a recarregar la vista des de la que són cridats:
return {
'name': 'Reserves',
'view_type': 'form',
'view_mode': 'form', # Pot ser form, tree, kanban...
'res_model': 'wizards.reserves', # El model de destí
'res_id': reserva.id, # El id concret per obrir el form
# 'view_id': self.ref('wizards.reserves_form') # Opcional si hi ha més d'una vista posible.
'context': self._context, # El context es pot ampliar per afegir opcions
'type': 'ir.actions.act_window',
'target': 'current', # Si ho fem en current, canvia la finestra actual.
}
L'exemple anterior és la manera llarga i completa de cridar a una vista en concret, però si sols necessitem refrescar la vista cridada, podem afegir:
return {
'type': 'ir.actions.client',
'tag': 'reload',
}