Toggle navigation

Construyendo un Website

Creación de un módulo básico

En Odoo, las tareas se realizan mediante la creación de módulos.

Los módulos personalizan el comportamiento de una instalación de Odoo, añadiendo nuevos comportamientos o modificando los existentes (incluyendo nuevas características o modificando las de otros módulos).

Odoo's scaffolding puede configurar un módulo básico. Para rápidamente comenzar con simplemente invocar:

$ ./odoo-bin scaffold Academy my-modules

Esto creará automáticamente un directorio de módulo ( my-modules) con un módulo de la academy dentro. El directorio puede ser un directorio de módulos existente si quieres, pero el nombre del módulo debe ser único dentro del directorio.

Un módulo para demostración

Contamos con un módulo “completo” listo para su instalación.

Aunque no hace absolutamente nada lo podemos instalar:

  • Inicia el servidor de Odoo

    $ ./odoo-bin --addons-path addons,my-modules
    
  • Abre un navegador con la dirección http://localhost:8069

  • Crear una nueva base de datos incluyendo datos de demostración

  • Luego ir Settings ‣ Modules ‣ Modules

  • En la esquina superior derecha, quite el filtro Installed y busca academy

  • Haga clic en Install para el módulo Academy

En el explorador

Los Controllers interpretan las peticiones del navegador y envía datos de regreso.

Agregar un controlador simple y asegura que es importado en el __init__.py (de tal modo que Odoo pueda encontrarlo):

academy/controllers.py
# -*- coding: utf-8 -*-
from odoo import http

class Academy(http.Controller):
    @http.route('/academy/academy/', auth='public')
    def index(self, **kw):
        return "Hello, world"

#     @http.route('/academy/academy/objects/', auth='public')
#     def list(self, **kw):

Mata el servidor (^C) y luego reinicialo:

$ ./odoo-bin --addons-path addons,my-modules

Abre una página en tu navegador en http://localhost:8069/academy/academy/, debería aparecer tu “página”:

Plantillas

Generar HTML en Python no es muy agradable.

La solución habitual es usar algo llamado “Plantillas” (templates en inglés), son pseudo-documentos con marcadores de posición y con la lógica de la vista. Odoo permite usar cualquier sistema de plantillas de Python, pero ofrece su propio sistema de plantillas llamado QWeb que está integrado con otras funciones.

Create a template and ensure the template file is registered in the __manifest__.py manifest, and alter the controller to use our template:

academy/controllers.py
class Academy(http.Controller):
    @http.route('/academy/academy/', auth='public')
    def index(self, **kw):
        return http.request.render('academy.index', {
            'teachers': ["Diana Padilla", "Jody Caroll", "Lester Vaughn"],
        })

#     @http.route('/academy/academy/objects/', auth='public')
#     def list(self, **kw):
academy/templates.xml
<odoo>

        <template id="index">
            <title>Academy</title>
            <t t-foreach="teachers" t-as="teacher">
              <p><t t-esc="teacher"/></p>
            </t>
        </template>
        <!-- <template id="object"> -->
        <!--   <h1><t t-esc="object.display_name"/></h1> -->
        <!--   <dl> -->

La plantilla itera (t-foreach) en todos los profesores (pasados a través del Contexto del template (template context en inglés)), e imprime cada docente en su propio párrafo.

Para terminar reinicia Odoo y actualiza los datos del módulo (para instalar la plantilla) esto lo haces yendo a Settings ‣ Modules ‣ Modules ‣ Academy y haciendo clic en Upgrade.

Ve a http://localhost:8069/academy/academy/ y ahora debe aparecer:

Almacenamiento de datos en Odoo

Los modelos de Odoo mapean elementos tablas en la base de datos.

En la sección anterior sólo nos muestra una lista de cadenas estáticamente escritas en código Python. Esto no permite modificaciones o almacenamiento persistente así que ahora grabaremos nuestros datos a la base de datos.

Definir el modelo de datos

Definir un modelo que represente a un profesor y asegurate que se importa en el __init__.py así es cargado correctamente por odoo:

academy/models.py

from odoo import models, fields, api

class Teachers(models.Model):
    _name = 'academy.teachers'

    name = fields.Char()

Luego crea los controles de acceso básicos para el modelo y declara los archivos en el manifiesto:

academy/__manifest__.py

    # always loaded
    'data': [
        'security/ir.model.access.csv',
        'templates.xml',
    ],
    # only loaded in demonstration mode
academy/security/ir.model.access.csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0

Simplemente da acceso de lectura (perm_read) a todos los usuarios (eso significa deja group_id:id vacío)

Datos de demostración

El segundo paso es añadir algunos datos de demostración del sistema para que sea posible probar fácilmente. Esto se hace agregando un Archivo de datos demo, que debe estar declarado en el manifiesto:

academy/demo.xml
<odoo>

        <record id="padilla" model="academy.teachers">
            <field name="name">Diana Padilla</field>
        </record>
        <record id="carroll" model="academy.teachers">
            <field name="name">Jody Carroll</field>
        </record>
        <record id="vaughn" model="academy.teachers">
            <field name="name">Lester Vaughn</field>
        </record>

</odoo>

Acceder a los datos

El último paso es modificar el modelo y la plantilla a utilizar nuestros datos de demostración:

  1. Buscar los registros de la base de datos en lugar de tener una lista estática

  2. Because search() returns a set of records matching the filter ("all records" here), alter the template to print each teacher's name
academy/controllers.py
class Academy(http.Controller):
    @http.route('/academy/academy/', auth='public')
    def index(self, **kw):
        Teachers = http.request.env['academy.teachers']
        return http.request.render('academy.index', {
            'teachers': Teachers.search([])
        })

#     @http.route('/academy/academy/objects/', auth='public')
academy/templates.xml
        <template id="index">
            <title>Academy</title>
            <t t-foreach="teachers" t-as="teacher">
                <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
            </t>
        </template>
        <!-- <template id="object"> -->

Reinicia el servidor y actualiza el módulo (con el fin de actualizar el manifiesto así cómo las plantillas y cargar también el archivo de demostración) ve a http://localhost:8069/academy/academy/. La página debe parecer levemente diferente: nombres simplemente deben ser prefijados por un número (el identificador de base de datos para el profesor).

Soporte de sitio web

Odoo tiene un módulo dedicado a la construcción de sitios Web.

Hasta ahora hemos utilizado controladores directamente, pero Odoo 8 agrega una integración más profunda y algunos otros servicios (por ejemplo estilos por defecto, manejo de temas) a través del módulo website.

  1. En primer lugar, añadir website como una dependencia del módulo Academy

  2. luego agregar la bandera website=True al controlador, esta establece nuevas variables en el objeto *request* y le permite utilizar una el esquema de disposición de los elementos del sitio (layout en inglés) en nuestra plantilla

  3. Utilizar el layout en nuestra plantilla.

academy/__manifest__.py
    'version': '0.1',

    # any module necessary for this one to work correctly
    'depends': ['website'],

    # always loaded
    'data': [
academy/controllers.py
from odoo import http

class Academy(http.Controller):
    @http.route('/academy/academy/', auth='public', website=True)
    def index(self, **kw):
        Teachers = http.request.env['academy.teachers']
        return http.request.render('academy.index', {
academy/templates.xml
<odoo>

        <template id="index">
            <t t-call="website.layout">
                <t t-set="title">Academy</t>
                <div class="oe_structure">
                    <div class="container">
                        <t t-foreach="teachers" t-as="teacher">
                            <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
                        </t>
                    </div>
                </div>
            </t>
        </template>
        <!-- <template id="object"> -->

Después de reiniciar el servidor al actualizar el módulo (para actualizar el manifiesto y la plantilla) entramos a http://localhost:8069/academy/academy/ debería mostrar una página más agradable con un esquema de imagen y otros elementos en la página incorporados (menú de primer nivel, pie de página,…)

El layout del módulo website también proporciona soporte para herramientas de edición: haga clic en: Sign In (en la parte superior derecha), coloca las credenciales (admin / admin por defecto) haga clic en: Log In.

Estás ahora en Odoo: la interfaz administrativa. Por ahora haga clic en el botón Website (esquina superior izquierda).

Estamos ahora en el website pero como administrador, con acceso a funciones de edición avanzadas dichas opciones son provistas por el módulo website:

  • Un editor de código para la plantilla (Customize ‣ HTML Editor) donde puedes ver y editar todas las plantillas utilizadas actualmente

  • El botón Edit en la parte superior izquierda cambia al "modo edición" donde hay bloques (fragmentos) y edición de texto

  • Un número de otras características tales como vista previa móvil y SEO

URLs y enrutamiento

Controller methods are associated with routes via the route() decorator which takes a routing string and a number of attributes to customise its behavior or security.

Hemos visto una cadena enrutamiento “literal”, que corresponde exactamente a una sección de URL, pero las cadenas de enrutamiento también pueden usar patrones convertidores que coincide con los bits de direcciones URL y los pone disponibles como variables locales. Por ejemplo podemos crear un nuevo método de control controller que toma una parte del URL y lo imprime:

academy/controllers.py
            'teachers': Teachers.search([])
        })

    @http.route('/academy/<name>/', auth='public', website=True)
    def teacher(self, name):
        return '<h1>{}</h1>'.format(name)


#     @http.route('/academy/academy/objects/', auth='public')
#     def list(self, **kw):
#         return http.request.render('academy.listing', {

reiniciar Odoo, accede a http://localhost:8069/academy/Alice/ y http://localhost:8069/academy/Bob/ y ver la diferencia.

As the name indicates, converter patterns don't just do extraction, they also do validation and conversion, so we can change the new controller to only accept integers:

academy/controllers.py
            'teachers': Teachers.search([])
        })

    @http.route('/academy/<int:id>/', auth='public', website=True)
    def teacher(self, id):
        return '<h1>{} ({})</h1>'.format(id, type(id).__name__)


#     @http.route('/academy/academy/objects/', auth='public')

Reinicia Odoo, entra a http://localhost:8069/academy/2, nota cómo el valor anterior era una cadena, pero el nuevo fue convertido a un entero. Tratar de acceder a http://localhost:8069/academy/Carol/ y tenga en cuenta que la página no se encontró: “Carol” no es un número entero, la ruta fue ignorada y ninguna ruta pudo encontrarse.

Odoo ofrece un convertidor adicional llamado model que provee un registro directamente dado su id. Vamos a usar esto para crear una página genérica para las biografías de los maestros:

academy/controllers.py
            'teachers': Teachers.search([])
        })

    @http.route('/academy/<model("academy.teachers"):teacher>/', auth='public', website=True)
    def teacher(self, teacher):
        return http.request.render('academy.biography', {
            'person': teacher
        })


#     @http.route('/academy/academy/objects/', auth='public')
academy/templates.xml
                </div>
            </t>
        </template>
        <template id="biography">
            <t t-call="website.layout">
                <t t-set="title">Academy</t>
                <div class="oe_structure"/>
                <div class="oe_structure">
                    <div class="container">
                        <p><t t-esc="person.id"/> <t t-esc="person.name"/></p>
                    </div>
                </div>
                <div class="oe_structure"/>
            </t>
        </template>
        <!-- <template id="object"> -->
        <!--   <h1><t t-esc="object.display_name"/></h1> -->
        <!--   <dl> -->

luego cambia la lista de modelo a nuestro nuevo controlador:

academy/templates.xml
                <div class="oe_structure">
                    <div class="container">
                        <t t-foreach="teachers" t-as="teacher">
                            <p><a t-attf-href="/academy/{{ slug(teacher) }}">
                              <t t-esc="teacher.name"/></a>
                            </p>
                        </t>
                    </div>
                </div>
                <div class="oe_structure"/>
                <div class="oe_structure">
                    <div class="container">
                        <h3><t t-esc="person.name"/></h3>
                    </div>
                </div>
                <div class="oe_structure"/>

Reiniciar Odoo y actualizar el módulo, a continuación, puede visitar la página de cada profesor. Como ejercicio, intente agregar bloques a la página de un maestro para escribir una biografía, y luego ir a la página de otro maestro y así sucesivamente. Descubrirá que su modelo de biografía es compartida entre todos los maestros, porque los bloques se agregan a la plantilla y la plantilla de la biografía es compartida entre todos los maestros, cuando se edita una página están todas siendo editadas al mismo tiempo.

Edición de campos

Los datos que son específicos de un registro se guardará en ese registro, así que nosotros agregar un nuevo campo biography a nuestros maestros:

academy/models.py
    _name = 'academy.teachers'

    name = fields.Char()
    biography = fields.Html()
academy/templates.xml
                <div class="oe_structure">
                    <div class="container">
                        <h3><t t-esc="person.name"/></h3>
                        <div><t t-esc="person.biography"/></div>
                    </div>
                </div>
                <div class="oe_structure"/>

Reinicia Odoo y actualiza las vistas, re-carga la página del profesor el campo es invisible porque no contiene nada.

Para los campos de registro, las plantillas pueden utilizar una directiva del t-field especial que permite editar el contenido del campo desde el website mediante interfaces específicamente diseñadas para campos. Cambio la plantilla de la person para usar t-field:

academy/templates.xml
                <div class="oe_structure"/>
                <div class="oe_structure">
                    <div class="container">
                        <h3 t-field="person.name"/>
                        <div t-field="person.biography"/>
                    </div>
                </div>
                <div class="oe_structure"/>

Reinicia Odoo y actualiza el módulo, ahora hay un marcador de posición con el nombre del profesor y una nueva zona de bloques en modo Edit.El contenido que se suelte allí es almacenado en el campo de biografía del profesor correspondiente y por lo tanto es específica a ese maestro.

El nombre del profesor también es editable y cuando se modifica el cambio es visible en el índice.

t-field también puede tomar opciones de formato que dependen del formato del campo. Por ejemplo si desplegamos la fecha de modificación de registro de un maestro:

academy/templates.xml
                <div class="oe_structure">
                    <div class="container">
                        <h3 t-field="person.name"/>
                        <p>Last modified: <i t-field="person.write_date"/></p>
                        <div t-field="person.biography"/>
                    </div>
                </div>

es mostrado de una manera muy “computista” y difícil de leer, pero podríamos hacer una versión legible:

academy/templates.xml
                <div class="oe_structure">
                    <div class="container">
                        <h3 t-field="person.name"/>
                        <p>Last modified: <i t-field="person.write_date" t-options='{"format": "long"}'/></p>
                        <div t-field="person.biography"/>
                    </div>
                </div>

o un despliegue relativo:

academy/templates.xml
                <div class="oe_structure">
                    <div class="container">
                        <h3 t-field="person.name"/>
                        <p>Last modified: <i t-field="person.write_date" t-options='{"widget": "relative"}'/></p>
                        <div t-field="person.biography"/>
                    </div>
                </div>

administración e Integración con el ERP

Una introducción breve e incompleta a la administración de Odoo

La administración de Odoo fue vista brevemente durante la sección website support. Podemos volver a hacerlo usando Administrator ‣ Administrator en el menú (o Sign In si no estás autenticado).

La estructura conceptual del backend Odoo es simple:

  1. en primer lugar son menús, un árbol (menús pueden tener submenús) de registros. Los menús sin hijos apuntan a…

  2. acciones. Las acciones tienen varios tipos: enlaces, informes, datos y código que debe ejecutar Odoo o Mostrar. Las acciones de visualización de datos son llamadas Acciones de ventana, y le dicen a Odoo como mostrar un determinado modelo según un conjunto de vistas…

  3. una vista tiene un tipo, una categoría a la que ésta corresponde (una lista, un gráfico, un calendario) y una arquitectura la cual personaliza la manera en que el modelo se muestra en la vista.

Editar en la interfaz de administración

De forma predeterminada, un modelo de Odoo es esencialmente invisible a un usuario. Para que sea visible debe estar disponible a través de una acción, la cuál en sí mismo debe ser accesible, generalmente esto se hace a través de un menú.

Vamos a crear un menú para nuestro modelo:

academy/__manifest__.py
    'data': [
        'security/ir.model.access.csv',
        'templates.xml',
        'views.xml',
    ],
    # only loaded in demonstration mode
    'demo': [
academy/views.xml
<odoo>

  <record id="action_academy_teachers" model="ir.actions.act_window">
    <field name="name">Academy teachers</field>
    <field name="res_model">academy.teachers</field>
  </record>

  <menuitem sequence="0" id="menu_academy" name="Academy"/>
  <menuitem id="menu_academy_content" parent="menu_academy"
            name="Academy Content"/>
  <menuitem id="menu_academy_content_teachers"
            parent="menu_academy_content"
            action="action_academy_teachers"/>

entonces acceder a http://localhost:8069/web/ en la parte superior izquierda debería haber un menú Academy, que está seleccionada por defecto, ya que es el primer menú y abre un listado de los docentes. Desde el listado es posible Create (crear) nuevos registros de maestros y para cambiar a la vista formulario.

Si no hay ninguna definición de cómo presentar los registros (un view) Odoo creará automáticamente uno básico on-the-fly. En nuestro caso funciona para la vista “list” por ahora (sólo muestra el nombre del profesor), pero en la vista “formulario” HTML el campo biography es al lado del campo name y no hay suficiente espacio. Vamos a definir una vista de formulario personalizado para hacer ver y editar registros maestros con una mejor experiencia:

academy/views.xml
    <field name="name">Academy teachers</field>
    <field name="res_model">academy.teachers</field>
  </record>

  <record id="academy_teacher_form" model="ir.ui.view">
    <field name="name">Academy teachers: form</field>
    <field name="model">academy.teachers</field>
    <field name="arch" type="xml">
      <form>
        <sheet>
          <label for="name"/> <field name="name"/>
          <label for="biography"/>
          <field name="biography"/>
        </sheet>
      </form>
    </field>
  </record>

  <menuitem sequence="0" id="menu_academy" name="Academy"/>
  <menuitem id="menu_academy_content" parent="menu_academy"

Relaciones entre modelos

Hemos visto un par de campos «básicos» almacenados directamente en el registro. Hay un número de campos básicos. Las segunda categorías de campos son relacionase y suelen vincular registros entre ellos (dentro de un modelo o de diferentes modelos).

Para la demostración, vamos a crear un courses modelo. Cada curso debe tener un campo teacher, con un registro de solo maestro, pero cada profesor puede impartir varios cursos:

academy/models.py

    name = fields.Char()
    biography = fields.Html()

class Courses(models.Model):
    _name = 'academy.courses'

    name = fields.Char()
    teacher_id = fields.Many2one('academy.teachers', string="Teacher")
academy/security/ir.model.access.csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0

Agreguemos también vistas para que podamos ver y editar profesor de un curso:

academy/views.xml
      </form>
    </field>
  </record>

  <record id="action_academy_courses" model="ir.actions.act_window">
    <field name="name">Academy courses</field>
    <field name="res_model">academy.courses</field>
  </record>
  <record id="academy_course_search" model="ir.ui.view">
    <field name="name">Academy courses: search</field>
    <field name="model">academy.courses</field>
    <field name="arch" type="xml">
      <search>
        <field name="name"/>
        <field name="teacher_id"/>
      </search>
    </field>
  </record>
  <record id="academy_course_list" model="ir.ui.view">
    <field name="name">Academy courses: list</field>
    <field name="model">academy.courses</field>
    <field name="arch" type="xml">
      <tree string="Courses">
        <field name="name"/>
        <field name="teacher_id"/>
      </tree>
    </field>
  </record>
  <record id="academy_course_form" model="ir.ui.view">
    <field name="name">Academy courses: form</field>
    <field name="model">academy.courses</field>
    <field name="arch" type="xml">
      <form>
        <sheet>
          <label for="name"/>
          <field name="name"/>
          <label for="teacher_id"/>
          <field name="teacher_id"/>
        </sheet>
      </form>
    </field>
  </record>

  <menuitem sequence="0" id="menu_academy" name="Academy"/>
  <menuitem id="menu_academy_content" parent="menu_academy"
            name="Academy Content"/>
  <menuitem id="menu_academy_content_courses"
            parent="menu_academy_content"
            action="action_academy_courses"/>
  <menuitem id="menu_academy_content_teachers"
            parent="menu_academy_content"

It should also be possible to create new courses directly from a teacher's page, or to see all the courses they teach, so add the inverse relationship to the teachers model:

academy/models.py

    name = fields.Char()
    biography = fields.Html()

    course_ids = fields.One2many('academy.courses', 'teacher_id', string="Courses")

class Courses(models.Model):
    _name = 'academy.courses'
academy/views.xml
    <field name="arch" type="xml">
      <form>
        <sheet>
          <label for="name"/> <field name="name"/>

          <label for="biography"/>
          <field name="biography"/>

          <field name="course_ids">
            <tree string="Courses" editable="bottom">
              <field name="name"/>
            </tree>
          </field>
        </sheet>
      </form>

Discusiones y notificaciones

Odoo ofrece modelos técnicos, que no satisfacen directamente las necesidades del negocio pero que agrega capacidades a objetos de negocio sin tener que construirlas a mano.

One of these is the Chatter system, part of Odoo's email and messaging system, which can add notifications and discussion threads to any model. The model simply has to _inherit mail.thread, and add the message_ids field to its form view to display the discussion thread. Discussion threads are per-record.

Para nuestra Academia, tiene sentido permitir intercambio de mensajes en los cursos para manejar, por ejemplo, la programación cambios o discusiones entre profesores y asistentes:

academy/models.py

class Courses(models.Model):
    _name = 'academy.courses'
    _inherit = 'mail.thread'

    name = fields.Char()
    teacher_id = fields.Many2one('academy.teachers', string="Teacher")
academy/views.xml
          <field name="name"/>
          <label for="teacher_id"/>
          <field name="teacher_id"/>
        </sheet>
        <div class="oe_chatter">
          <field name="message_follower_ids" widget="mail_followers"/>
          <field name="message_ids" widget="mail_thread"/>
        </div>
      </form>
    </field>

En la parte inferior de cada formulario de curso, ahora hay un hilo de discusión y la posibilidad para los usuarios del sistema dejar mensajes y seguir o dejar de seguir cursos vinculados a ciertos debates.

Venta de cursos

Odoo ofrece también modelos de negocio que permiten utilizar más directamente en las necesidades del negocio. Por ejemplo con el módulo website_sale es posible configurar un sitio de comercio electrónico basado en los productos en Odoo. Con éste podemos hacer la subscripción a los cursos vendibles usando un tipo de producto específico

En lugar de usar una herencia clásica anterior, esto significa cambiar nuestro modelo course por el modelo product y extender los productos en-sitio (para agregar cualquier cosa que necesitemos de él).

En primer lugar tenemos que agregar una dependencia en website_sale, con lo que obtenemos productos (a través de sale) y la interfaz de comercio electrónico:

academy/__manifest__.py
    'version': '0.1',

    # any module necessary for this one to work correctly
    'depends': ['website_sale'],

    # always loaded
    'data': [

reinicia Odoo, actualice su módulo, ahora hay un menú Shop, listando un número de productos existentes (se crearon como datos de demostración) de la tienda.

El segundo paso es sustituir el modelo courses por product.template y añadir una nueva categoría de producto para ser usada como cursos:

academy/__manifest__.py
        'security/ir.model.access.csv',
        'templates.xml',
        'views.xml',
        'data.xml',    
    ],
    # only loaded in demonstration mode
    'demo': [
academy/data.xml
<odoo>
  <record model="product.public.category" id="category_courses">
    <field name="name">Courses</field>
    <field name="parent_id" ref="website_sale.categ_others"/>
  </record>
</odoo>
academy/demo.xml
        <record id="vaughn" model="academy.teachers">
            <field name="name">Lester Vaughn</field>
        </record>

        <record id="course0" model="product.template">
            <field name="name">Course 0</field>
            <field name="teacher_id" ref="padilla"/>
            <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
            <field name="website_published">True</field>
            <field name="list_price" type="float">0</field>
            <field name="type">service</field>
        </record>
        <record id="course1" model="product.template">
            <field name="name">Course 1</field>
            <field name="teacher_id" ref="padilla"/>
            <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
            <field name="website_published">True</field>
            <field name="list_price" type="float">0</field>
            <field name="type">service</field>
        </record>
        <record id="course2" model="product.template">
            <field name="name">Course 2</field>
            <field name="teacher_id" ref="vaughn"/>
            <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
            <field name="website_published">True</field>
            <field name="list_price" type="float">0</field>
            <field name="type">service</field>
        </record>

</odoo>
academy/models.py
    name = fields.Char()
    biography = fields.Html()

    course_ids = fields.One2many('product.template', 'teacher_id', string="Courses")

class Courses(models.Model):
    _inherit = 'product.template'

    teacher_id = fields.Many2one('academy.teachers', string="Teacher")
academy/security/ir.model.access.csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
academy/views.xml
      </form>
    </field>
  </record>

  <menuitem sequence="0" id="menu_academy" name="Academy"/>
  <menuitem id="menu_academy_content" parent="menu_academy"
            name="Academy Content"/>
  <menuitem id="menu_academy_content_teachers"
            parent="menu_academy_content"

Con esto instalado, algunos cursos ya están disponibles en el Shop, puede que tengas que buscarlo.

Modificar vistas existentes

Hasta ahora, hemos visto brevemente:

  • la creación de nuevos modelos

  • la creación de nuevos puntos de vista

  • la creación de nuevos registros

  • la alteración de los modelos existentes

Estamos a trabajando con la alteración de los registros existentes y la alteraciónvistas existente. Lo vamos a hacer tanto en la Shop como en las vistas regulares.

Alterar las vista se realiza mediante la creación de vistas extension, que se aplican por encima la vista original y ls modifican. Estas modificaciones de la vista se pueden añadir o quitar sin modificar el original, haciéndolo más sencillo probar cosas y deshacer cambios.

Ya que nuestros cursos son gratis, no hay ninguna razón para mostrar su precio en la página de la tienda, así que vamos a modificar la vista y ocultar el precio si es 0. La primera tarea es averiguar que vista muestra el precio, esto puede hacerse a través de Customize ‣ HTML Editor el cual nos permite leer las plantillas involucradas en el despliegue de la página. Pasando por algunos de ellos, la vista llamada “Product Item” pareciera ser la probable culpable.

La modificación de la arquitectura de una vista se realiza en 3 pasos:

  1. Crear una nueva vista

  2. Ampliar la vista para modificar estableciendo la de nueva vista colocando en el campo inherit_id la identificación externa (xml_id) de la vista modificada

  3. En la arquitectura (campo arch), utilice la etiqueta de xpath para seleccionar y modificar elementos de la vista modificada

academy/templates.xml
                <div class="oe_structure"/>
            </t>
        </template>

        <template id="product_item_hide_no_price" inherit_id="website_sale.products_item">
            <xpath expr="//div[hasclass('product_price')]/b" position="attributes">
                <attribute name="t-if">product.price &gt; 0</attribute>
            </xpath>
        </template>

        <!-- <template id="object"> -->
        <!--   <h1><t t-esc="object.display_name"/></h1> -->
        <!--   <dl> -->

Lo segundo que vamos a cambiar es hacer la barra lateral de categorías de producto visible por defecto: Customize ‣ Product Categories le permite alternar entre un árbol de categorías (usado para filtrar la visualización principal) con solo alternarlo entre activado y desactivado.

Esto se hace vía los campos customize_show y active de plantillas usada para extender: una plantilla de extensión (como la que acabamos de crear) puede ser customize_show = True. Esta opción mostrará la vista en menú Customize con una casilla de verificación, permitiendo a los administradores activar o desactivar (y personalizar fácilmente sus páginas web).

Simplemente debemos modificar el registro Product Categories y establecer su valor predeterminado a activo = “True”:

academy/templates.xml
            </xpath>
        </template>

        <record id="website_sale.products_categories" model="ir.ui.view">
            <field name="active" eval="True"/>
        </record>

        <!-- <template id="object"> -->
        <!--   <h1><t t-esc="object.display_name"/></h1> -->
        <!--   <dl> -->

Con esto, la cara lateral sidebar Product Categories automáticamente será activado cuando el módulo Academia sea instalado.