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')

Relacions avançades

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

Observem una cosa que quasi sempre es fa en Odoo al crear un mètode: el for que recorre self. La variable self en aquestes funcions és un recodset que conté una llista de objectes sobre els que calcular, en aquest cas, la llista d'equips.

Una altra cosa interessant és el sorted(), un mètode de l'ORM de Odoo que ordena un recordset en funció d'una clau, que en aquest cas són els punts.

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.

     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].

Però abans de tot, cal millorar la manera en la que es donen d'alta els equips en una lliga. Com que utilitzem un model enmig anomenat points, cada vegada que es dona d'alta un equip, cal crear el points i relacionar-lo amb la lliga i l'equip. Això pot ser tediós per a 20 equips. El que podem fer és un Wizard que en ajude a omplir aquest One2many en la lliga.

Aquest és el codi python del model transitori:

class wizPoints(models.TransientModel):
    _name='league.wiz_points'
    def _default_league(self):
      return self.env['league.league'].browse(self._context.get('active_id'))
    league = fields.Many2one('league.league',default=_default_league)
    teams = fields.Many2many('league.team')

    @api.multi
    def assign(self):
     for w in self:
      now_teams = w.league.teams.mapped('team')
      new_teams = w.teams - now_teams     # Evitar equips duplicats
      for i in new_teams:
        self.env['league.points'].create({'league':w.league.id,'team':i.id})
      return {
            'type': 'ir.actions.client',
            'tag': 'reload',
             }

Aquest és el codi XML necessari per a la vista Wizard i el action que la mostra:

        <act_window id="league.launch_points_wizard"
                    name="select teams"
                    src_model="league.league"
                    res_model="league.wiz_points"
                    view_mode="form"
                    target="new"
                    key2="client_action_multi"/>

    <record model="ir.ui.view" id="league.wiz_points_view">
            <field name="name">Wiz Points</field>
            <field name="model">league.wiz_points</field>
            <field name="arch" type="xml">
                <form string="Select teams">

                        <field name="league"/>
                        <field name="teams"/>
                    <footer>
                        <button name="assign" type="object"  string="Assign" class="oe_highlight"/>
                        or
                        <button special="cancel" string="Cerrar"/>
                    </footer>

                </form>
            </field>
        </record>

I en el form de la lliga posariem:

  <button name="%(launch_points_wizard)d" type="action" string="Select Teams" class="oe_highlight" />

Una vegada introduits els equips, cal generar la temporada. Aquest és el codi:

     @api.one
     def create_calendar(self):
       self.days.unlink() # Esborra totes les jornades i partits (ondelete="cascade")     

       data = fields.Datetime.from_string(self.start_date)
       teams = self.teams.mapped('team').ids
       print teams
       random.shuffle(teams)
       print teams
       for i in range(1,len(teams)):
        day = self.env['league.day'].create({'sequence':i,'league':self.id})
        day2 = self.env['league.day'].create({'sequence':i+len(teams)-1,'league':self.id})

        for j in range(0,len(teams)/2): # Primera volta
           if i%2 == 0:     # alternar en casa o visitant
            team_a = teams[j]
            team_b = teams[len(teams)-1-j]
           else:
            team_b = teams[j]
            team_a = teams[len(teams)-1-j]
           self.env['league.match'].create({
                                        'day':day.id,
                                        'local':team_a,
                                        'visitor':team_b,
                                        'date':fields.Datetime.to_string(data)
                                        })
           data = data + timedelta(days=7)
        for j in range(0,len(teams)/2): # Segona volta
           if i%2 == 0:     # alternar en casa o visitant
            team_a = teams[j]
            team_b = teams[len(teams)-1-j]
           else:
            team_b = teams[j]
            team_a = teams[len(teams)-1-j]
           self.env['league.match'].create({
                                        'day':day2.id,
                                        'visitor':team_a,
                                        'local':team_b,
                                        'date':fields.Datetime.to_string(data)
                                        })
           data = data + timedelta(days=7)
        aux=[]
        aux.append(teams[0])
        aux.append(teams[len(teams)-1])
        aux.extend(teams[1:len(teams)-1])
        print aux
        teams = aux

Ja que estem, per a fer proves podem fer un botó que genere resultats aleatòriament:

     @api.one
     def random_score(self):
      for match in self.matches:
          match.write({'local_points':random.randint(0,5),'visitor_points':random.randint(0,5),'played':True})

Y en la vista form de la lliga podem posar un botó:

 <button name="random_score" string="Random Scores" type="object" class="oe_highlight" attrs="{'invisible':[('n_teams', '=', 0 )]}"/>

Les Vistes fins al moment

Abans de pasar a altres coses, anem a repasar cóm està la vista fins al moment:

<odoo>
  <data>

  <!-- ################ VISTES DE LA LLIGA ################  -->

        <act_window id="league.launch_points_wizard"
                    name="select teams"
                    src_model="league.league"
                    res_model="league.wiz_points"
                    view_mode="form"
                    target="new"
                    key2="client_action_multi"/>

    <record model="ir.ui.view" id="league.league_tree">
      <field name="name">Leagues</field>
      <field name="model">league.league</field>
      <field name="arch" type="xml">
        <tree>
          <field name="name"/>
          <field name="start_date"/>
          <field name="end_date"/>
        </tree>
      </field>
    </record>
    <record model="ir.ui.view" id="league.league_form">
      <field name="name">Leagues</field>
      <field name="model">league.league</field>
      <field name="arch" type="xml">
        <form><sheet>
          <group>
          <group string="Main Data">
             <field name="name"/>
             <field name="start_date"/>
             <field name="end_date"/>
          </group>
          <group string="Actions">
            <button name="create_calendar" string="Create Calendar" type="object" class="oe_highlight"/>
	     <button name="%(launch_points_wizard)d" type="action" string="Select Teams" class="oe_highlight" />
          </group>
          </group>
          <notebook>
        	  <page string="Data">
			<group>
			<field name="teams" mode="kanban,tree">
			<tree><field name="name"/><field name="points"/></tree>
                <kanban delete="true">
                    <templates>
                    <t t-name="kanban-box">
                            <div class="oe_kanban_global_click">
                                        <a type="delete" class="oe_edit_only"><button class="oe_highlight">X</button></a>
				<div class="o_kanban_image">
                                <a type="open">
                                    <img t-att-src="kanban_image('league.points', 'logo', record.id.value)" style="width:50px;"/>
                                </a>
				</div>
                                <div class="oe_kanban_details">
                                    <strong>
                                        <a type="edit">
                                            <field name="name"></field>
                                        </a>
                                    </strong>
					<ul><li>
                                      Points: <field name="points"></field>
					</li></ul>

                                </div>
                            </div>
                        </t>
                    </templates>
                </kanban>
		<form>
		<group>	
                                            <field name="logo" widget="image" style="width:100px;"></field>
                                            <field name="name"></field>
                                            <field name="team"></field>
                                            <field name="points"></field>
	</group>	</form>
			</field>
			<field name="days">
			<tree><field name="name"/><field name="sequence"/></tree>
			</field>
			</group>
		  </page>
        	  <page string="Information">
			<group>
				<field name="classification">
				<tree><field name="name"/><field name="points"/></tree>
				</field>
				<field name="matches">
				<tree>
				<field name="name"/>
				<field name="day"/>
				<field name="local"/>
				<field name="visitor"/>
				<field name="local_points"/>
				<field name="visitor_points"/>
				<field name="played"/>
				</tree>
				</field>
			</group>
		  </page>
          </notebook>
        </sheet></form>
      </field>
    </record>

    <record model="ir.ui.view" id="league.wiz_points_view">
            <field name="name">Wiz Points</field>
            <field name="model">league.wiz_points</field>
            <field name="arch" type="xml">
                <form string="Select teams">

                        <field name="league"/>
                        <field name="teams"/>
                    <footer>
                        <button name="assign" type="object"  string="Assign" class="oe_highlight"/>
                        or
                        <button special="cancel" string="Cerrar"/>
                    </footer>
 
                </form>
            </field>
        </record>


<!-- ######################VISTES DE TEAM #################### -->

    <record model="ir.ui.view" id="league.team_tree">
      <field name="name">Teams</field>
      <field name="model">league.team</field>
      <field name="arch" type="xml">
        <tree>
          <field name="name"/>
          <field name="players"/>
        </tree>
      </field>
    </record>
    <record model="ir.ui.view" id="league.team_form">
      <field name="name">Teams</field>
      <field name="model">league.team</field>
      <field name="arch" type="xml">
        <form><sheet>
          <group>
          <field name="logo" widget="image" style="width:200px;"/>
          <field name="name"/>
          </group>
          <notebook>
        	  <page string="Data">
			<group>
			<field name="players" mode="kanban,tree">
			<tree><field name="name"/></tree>
                <kanban delete="false">
                    <templates>
                    <t t-name="kanban-box">
                            <div class="oe_kanban_global_click">
				<div class="o_kanban_image">
                                <a type="open">
                                    <img t-att-src="kanban_image('league.player', 'photo', record.id.value)" style="width:50px;"/>
                                </a>
				</div>
                                <div class="oe_kanban_details">
                                    <strong>
                                        <a type="edit">
                                            <field name="name"></field>
                                        </a>
                                    </strong>
					<ul><li>
                                        <field name="team"></field>
					</li></ul>

                                </div>
                            </div>
                        </t>
                    </templates>
                </kanban>
			</field>
			</group>
		  </page>
        	  <page string="Information">
			<group>
				<field name="points">
				<tree><field name="league"/><field name="points"/></tree>
				</field>
			</group>
		  </page>
          </notebook>
        </sheet></form>
      </field>
    </record>


    <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">team 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">player 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">match 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>

Com es pot observar tenim, de moment, totes les vistes en el mateix fitxer. Això es pot separar quan vulguem.

Sols he necessitat fer, de moment, el form de la lliga i de l'equip. Més endavant ja farem la resta i millorarem detalls dels forms i trees que tenim. Tot està colocat de forma molt rudimentaria, però ja comença a ser fàcil d'utilitzar. Per exemple, la llista d'equips està en forma de Kanban simple i separem en dues pages la part d'administració i la part d'informació de classificació o llista de partits.

Observem també que ja hem afegit als One2many en la vista el context="{'default_<field>':active_id}" per facilitar la creació d'un equip en la lliga o un jugador en un equip.

Podem aprofitar que hem fet les vistes necessàries per a fer altres que milloraran l'apariencia com són les dels jugadors i dels partits:

<!--  ############################ Vistes PLAYERS ################ -->

    <record model="ir.ui.view" id="league.players_tree">
      <field name="name">Players</field>
      <field name="model">league.player</field>
      <field name="arch" type="xml">
        <tree>
          <field name="name"/>
          <field name="team"/>
        </tree>
      </field>
    </record>
    <record model="ir.ui.view" id="league.player_form">
      <field name="name">Players</field>
      <field name="model">league.player</field>
      <field name="arch" type="xml">
        <form><sheet>
          <group>
          <field name="photo" widget="image" style="width:200px;"/>
          <field name="name"/>
          <field name="team"/>
          </group>
        </sheet></form>
      </field>
    </record>
    <record model="ir.ui.view" id="league.player_kanban">
      <field name="name">Players</field>
      <field name="model">league.player</field>
      <field name="arch" type="xml">
                <kanban>
                    <templates>
                    <t t-name="kanban-box">
                            <div class="oe_kanban_global_click">
                                <div class="o_kanban_image">
                                <a type="open">
                                    <img t-att-src="kanban_image('league.player', 'photo', record.id.value)" style="width:50px;"/>
                                </a>
                                </div>
                                <div class="oe_kanban_details">
                                    <strong>
                                        <a type="edit">
                                            <field name="name"></field>
                                        </a>
                                    </strong>
                                        <ul><li>
                                      Team: <field name="team"></field>
                                        </li></ul>

                                </div>
                            </div>
                        </t>
                    </templates>
                </kanban>
                </field>
        </record>

<!--  ############################ Vistes matches ################ -->

    <record model="ir.ui.view" id="league.match_tree">
      <field name="name">Matches</field>
      <field name="model">league.match</field>
      <field name="arch" type="xml">
        <tree>
                                <field name="name"/>
                                <field name="day"/>
                                <field name="local"/>
                                <field name="visitor"/>
                                <field name="local_points"/>
                                <field name="visitor_points"/>
                                <field name="played"/>
        </tree>
      </field>
    </record>
    <record model="ir.ui.view" id="league.match_form">
      <field name="name">Matches</field>
      <field name="model">league.match</field>
      <field name="arch" type="xml">
        <form><sheet>
          <group>
                                <field name="name"/>
                                <field name="day"/>
                                <field name="local"/>
                                <field name="visitor"/>
                                <field name="local_points"/>
                                <field name="visitor_points"/>
                                <field name="played"/>
          </group>
        </sheet></form>
      </field>
    </record>

Les vistes search

Per a fer més fàcil utilitzar aquest mòdul, anem a millorar la manera en la que els usuaris poden buscar o organitzar les vistes. Pre fer això, deguem afegir a cada model una vista search:

Leagues: Ens interessa poder buscar en els ligues per nom i per data. Aquesta vista search serà molt simple.

Teams: En els equips, podem buscar per nom.

Players: Dels jugadors ens interessa poder buscar per nom, per equip i agrupar per equip:

          <field name="name"/>
          <field name="team"/>
          <filter name="group_by_team" string="Team" context="{'group_by': 'team'}"/>

Matches: En els partits ens interessa agrupar per lliga, per equips i per jornada. també buscar per Lliga, equips i jornades. També podem fer filtres per buscar els finalitzats i els que no:

          <field name="name"/>
          <field name="day"/>
          <field name="local"/>
          <field name="visitor"/>
          <filter name="played" string="Played" domain="[('played', '=', True)]"/>
          <filter name="group_by_league" string="League" context="{'group_by': 'league'}"/>
          <filter name="group_by_day" string="Day" context="{'group_by': 'day'}"/>
          <filter name="group_by_local" string="Local" context="{'group_by': 'local'}"/>
          <filter name="group_by_visitor" string="Visitor" context="{'group_by': 'visitor'}"/>

Les entrades

Anem a afegir una altra funcionalitat i anem a aprofitar per fer alguna cosa en herència. La gestió de lligues, equips, jugadors, jornades i partits no implica un procés del negoci. En aquest moment, anem a fer que es puguen fer vendes d'entrades. En la realitat, les entrades són gestionades per cada equip, però anem a suposar que tot està centralitat en el nostre sistema i sols la nostra empresa és la que ven les entrades.

Una entrada és per a un partit, té una numeració i és per a l'estadi de l'equip local. Els usuaris d'Odoo necessiten poder vendre les entrades d'un partit determinat, buscar les entrades venudes d'un partit, d'un estadi o de una lliga, per exemple. Una altra cosa que necessiten és imprimir les entrades. Tot això és el que anem a fer.

El model:

La gestió de les entrades pot ser molt diferent segons com plantegem la base de dades. En el nostre cas anem a generar les entrades al partit en el moment de vendre. Aquestes entrades sempre tenen un comprador. Si un comprador compra varies entrades per a un partit, es fa una sola venda amb varies línies de factura. També es pot vendre entrades a la gent en taquilla. Aquestes no tenen un comprador donat d'alta, però s'ha d'enregistrar.

Odoo té un mòdul per a esdeveniments que funciona en la web. Si l'instal·lem, veurem que crea un model anomenat event.event per als esdeveniments. Aquest pot ser el partit. També crea un anomenat event.registration per a les entrades. El model de les entrades es pot associar a una línia d'una comanda de venda. Aquests models no són suficients per al que nosaltres volem, ja que no tenen manera de guardar la posició de la butaca. Per tant, cal modificar amb herència els models per afegir informació del partit, estadi i reserves amb número de butaca.

Imprimir:

Crear automàticament:

Les alineacions

Els gràfics

La pàgina web

Venda d'entrades per la web