Projecte Odoo complet

De Jose Castillo Aliaga
Ir a la navegación Ir a la búsqueda

Aquest article tracta de explicar amb tot el detall que es puga el procés de creació d'un mòdul en Odoo. La teoria està en l'article d'Odoo i també part en la part d'Instal·lar Odoo. Anirem explicant qüestions tècniques, però també sobre el procés pas a pas per evitar errades i anar comprovant que tot funciona.

Com que és un article didàctic, algunes coses no es faran a la primera com es farien en un desenvolupament professional. Ens deixarem conscientment coses i algunes les desfarem o seran redundants. Per exemple, començarem sense fer res d'herència per a, després, desfer part del treball i aplicar l'herència quan ja sabem.

Un dels temes favorits dels alumnes i dels que més joc donen a l'hora de fer un mòdul és la gestió de campionats esportius. Per tant, aquest mòdul serà sobre la creació d'un mòdul per a gestionar lligues de qualsevol esport amb fase de tots contra tots i play-offs.

Creació e instal·lació d'un mòdul

El nostre mòdul serà instal·lat en un servidor en producció. Per tant, es copiarà el mòdul al directori oficial dels mòduls o es crearà en la configuració central del servidor un PATH que continga la ruta absoluta del directori o està. El procés per a desenvolupar-lo pot ser diferent. El recomanable seria fer el que diu l'article d'Instal·lació d'Odoo a la seua secció Configuració de la ruta dels mòduls..

Creem un mòdul buit en aquesta ruta amb el comandament:

 $ odoo scaffold league .

Podríem posar-nos directament a programar en els directoris creats, però es recomana primer instal·lar el mòdul buit. Anem a la web > Configuració > Activar mode desenvolupador i després Aplicacions > Actualitzar llista d'aplicacions. Finalment es cerca el mòdul i s'instal·la. En principi no dona problemes perquè no té res.

Creació dels primers models

El nostre mòdul gestiona lligues, per tant ens interessa gestionar les temporades, equips, classificacions, jugadors, jornades, partits, resultats, guanyadors i altres coses que ja anirem pensant.

El més recomanable per a fer un mòdul per primera vegada i aprendre és fer tots els models que pensem que necessitem en el python amb els fields bàsics i fer les seues vistes.

Aquest pot ser el codi inicial per provar que funciona.

En models.py:

# -*- coding: utf-8 -*-

from odoo import models, fields, api

class league(models.Model):
     _name = 'league.league'
     name = fields.Char()
     start_date = fields.Date()
     end_date = fields.Date()
     
class team(models.Model):
	_name = 'league.team'
	name = fields.Char()
	logo = fields.Binary()
	
class player(models.Model):
	_name = 'league.player'
	name = fields.Char()
	photo = fields.Binary()
	
class match(models.Model):
	_name = 'league.match'
	name = fields.Char()
	date = fields.Datetime()


En views.xml:

<odoo>
  <data>
    <record model="ir.actions.act_window" id="league.leagues_action_window">
      <field name="name">league window</field>
      <field name="res_model">league.league</field>
      <field name="view_mode">tree,form</field>
    </record>
    <record model="ir.actions.act_window" id="league.teams_action_window">
      <field name="name">league window</field>
      <field name="res_model">league.team</field>
      <field name="view_mode">tree,form</field>
    </record>
    <record model="ir.actions.act_window" id="league.players_action_window">
      <field name="name">league window</field>
      <field name="res_model">league.player</field>
      <field name="view_mode">tree,form</field>
    </record>
    <record model="ir.actions.act_window" id="league.matches_action_window">
      <field name="name">league window</field>
      <field name="res_model">league.match</field>
      <field name="view_mode">tree,form</field>
    </record>
    
    <menuitem name="League" id="league.menu_root"/>

    <menuitem name="Data" id="league.menu_data" parent="league.menu_root"/>

    <menuitem name="Leagues" id="league.menu_leagues" parent="league.menu_data"
              action="league.leagues_action_window"/>
    <menuitem name="Teams" id="league.menu_teams" parent="league.menu_data"
              action="league.teams_action_window"/>
    <menuitem name="Players" id="league.menu_players" parent="league.menu_data"
              action="league.players_action_window"/>
    <menuitem name="Matchs" id="league.menu_matches" parent="league.menu_data"
              action="league.matches_action_window"/>
    
  </data>
</odoo>


Una vegada fet, deguem para el servici Odoo, i reiniciar-lo especificant la base de dades i el mòdul que volem actualitzar i depurar, com diu la part de depurar Odoo del manual d'Instal·lació.

 $ odoo -d league -u league

Les relacions entre els models

De moment són models amb poques dades (ja afegirem més) independents entre ells. Però els equips participen en lligues, tenen jugadors i els partits tenen equips. Cal fer aquestes relacions bàsiques:

Player (Many2one) <-> (One2many) Team
Match (Many2one visitor) --> Team
Match (Many2one local) --> Team

La relació entre equips i lligues és especial, ja que necessitem també guardar els punts que té en eixa lliga en concret. Per tant, necessitem una taula en mig anomenada Points:

Team (One2many) <-> (Many2one) Points (Many2one) <-> (One2many) League

Els partits són de lligues, però es tenen que organitzar per jornades:

Match (Many2one) <-> (One2many) Day (Many2one) <-> (One2many) League

El codi queda, per tant:

En models.py:

# -*- coding: utf-8 -*-

from odoo import models, fields, api

class league(models.Model):
     _name = 'league.league'
     name = fields.Char()
     start_date = fields.Date()
     end_date = fields.Date()
     teams = fields.One2many('league.points','league')
     days = fields.One2many('league.day','league')
     
class points(models.Model):
	_name = 'league.points'
	name = fields.Char()
	league = fields.Many2one('league.league')
	team = fields.Many2one('league.team')
	points = fields.Integer()
    
class team(models.Model):
	_name = 'league.team'
	name = fields.Char()
	logo = fields.Binary()
	points = fields.One2many('league.points','team') 
	players = fields.One2many('league.player','team')
	
class player(models.Model):
	_name = 'league.player'
	name = fields.Char()
	photo = fields.Binary()
	team = fields.Many2one('league.team')
	
class day(models.Model):
	_name = 'league.day'
	name = fields.Char()
	league = fields.Many2one('league.league')
	matches = fields.One2many('league.match','day')
	
class match(models.Model):
	_name = 'league.match'
	name = fields.Char()
	date = fields.Datetime()
	day = fields.Many2one('league.day')

Aquestes són les relacions bàsiques, però tal vegada necessitem alguna relació més complexa, com:

En el partit és interessant veure la lliga de la que és. Per a fer això es necessita fer un related:

    league = fields.Many2one(related='day.league', readonly=True)

En la lliga és interessant tindre una llista d'equips ordenats per punts. Aquest serà el primer camp computed que anem a fer:

     classification = fields.Many2many('league.points', compute='_get_classification')
        
     @api.depends('teams','days')
     def _get_classification(self):
       for league in self:
         teams = league.teams.sorted(key=lambda r: r.points, reverse=True)
         league.classification = teams.ids
Al mateix temps deuriem d'estar fent unes vistes tree i form bàsiques per comprovar que tot funciona. Més endavant les explicarem

En el partit necessitem saber quin és l'equip visitant, el local i el guanyador. Necessitem, per tant, eixos fields i altres per al punts.

        local = fields.Many2one('league.team')
        visitor = fields.Many2one('league.team')
        winner = fields.Many2one('league.team', compute='_get_winner')
        local_points = fields.Integer()
        visitor_points = fields.Integer()

        @api.depends('local','visitor','local_points','visitor_points')
        def _get_winner(self):
          for match in self:
           if match.local_points > match.visitor_points:
              match.winner = match.local.id
           elif match.local_points < match.visitor_points:
              match.winner = match.visitor.id
           else:
              match.winner = False

En la lliga pot ser interessant tindre una llista de partits en forma de arbre.

     matches = fields.Many2many('league.match', compute='_get_matches')
        
     @api.depends('days')
     def _get_matches(self):
       for league in self:
         league.matches = league.days.mapped('matches').ids

El name del model point no té massa sentit que el tinga que fer l'usuari, així que deuria ser, per exemple, el nom de l'equip:

     name = fields.Char(related='team.name', readonly=True)

El sistema de punts

Aquesta part tracta sobre la part del controlador que s'encarrega de calcular i gestionar els punts dels equips en les diferents jornades de cada lliga.

Recordem que ja tenim un model anomenat points que relaciona un equip en una lliga i diu els punts que té. El primer que cal fer és calcular els punts en funció dels partits guanyats en la lliga.

    points = fields.Integer(compute='_get_points')

    @api.depends('team','league')
    def _get_points(self):
      for points in self:
        league = points.league
        team = points.team
        matches = league.matches.filtered(lambda r: r.local.id == team.id or r.visitor.id == team.id)
        #print matches.mapped('name')
        p = 0
        for m in matches:
         if m.winner.id == False:
            p = p + 1
         elif m.winner.id == team.id:       
            p = p + 3
        points.points = p

Les dades de demo

En principi sols necessitem dades de demo dels equips i jugadors per poder generar lligues més fàcilment.

No cal especificar els XML de demo, ja que es poden consultar al repositori, però cal dir que han sigut obtinguts amb wget, Bash a partir d'un diari esportiu. Després d'uns scripts i algunes modificacions a mà al fitxers, el resultat són els equips de primera divisió de la lliga de futbol i tots els seus jugadors amb fotos i logos.

El més interessant és la relació entre equips i jugadors. Observeu l'id de l'equip i cóm és referenciat en el ref del jugador:

<record id="league.team_1" model="league.team">
<field name="name">Alavés</field>
<field name="logo">iVBORw0KGgoAAAANSUhEUgAAAWIAAAFiCAMAAAD7giJIAAADAFBMVEX///8JVaViueiPstZfkcU4
drb6/P3K2uxPhr9vnMqvyOIPWacDUaM/erm/0+gqbLEZYKv+/v8WXqqkwN4kZ68vb7O90eeqxOBr
mclBfLpLg73X4/DP3u1mlccHVKVFf7t5o85ol8jM3OwKVqa3zeWFq9MOWafc5/Knwt+sxuFik8YT

[...]

<record id="league.player_1" model="league.player">
<field name="name">Adrián Diéguez</field>
<field name="team" ref="league.team_1"></field>
<field name="photo">iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ
bWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp
bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6

El generador de temporades

De moment, el mòdul permet gestionar una lliga, els seus equips, partits, resultats, punts i classificacions. Però no hi ha moltes coses automatitzades. La primera automatització que anem a fer és generar un calendari de jornades i de partits. Aquest generador crea un calendari aleatori, però amb tots els creuaments i l'anada i la tornada. Per a fer-ho, utilitza un algoritme de tots contra tots [1].