chapter 5 - connect the dots

This commit is contained in:
Antoine Vandevenne (anv) 2024-08-19 17:01:46 +02:00
parent 32cba451c1
commit 81929ceb1c
15 changed files with 1560 additions and 209 deletions

View File

@ -813,6 +813,8 @@ In general in Odoo, when manipulating strings, prefer ``%`` over ``.format()``
of position (when multiple variables have to be replaced). This makes the of position (when multiple variables have to be replaced). This makes the
translation easier for the community translators. translation easier for the community translators.
.. _contributing/coding_guidelines/model_members:
Symbols and Conventions Symbols and Conventions
----------------------- -----------------------

View File

@ -518,6 +518,8 @@ behavior is desired:
:class:`~odoo.fields.Many2one` :class:`~odoo.fields.Many2one`
:type: :class:`~odoo.addons.base.models.res_company` :type: :class:`~odoo.addons.base.models.res_company`
.. _reference/orm/recordsets:
Recordsets Recordsets
========== ==========

View File

@ -39,7 +39,7 @@ Ready? Let's get started!
- :doc:`server_framework_101/02_lay_the_foundations` - :doc:`server_framework_101/02_lay_the_foundations`
- :doc:`server_framework_101/03_build_user_interface` - :doc:`server_framework_101/03_build_user_interface`
- :doc:`server_framework_101/04_relational_fields` - :doc:`server_framework_101/04_relational_fields`
- :doc:`server_framework_101/05_business_logic` - :doc:`server_framework_101/05_connect_the_dots`
- :doc:`server_framework_101/06_security` - :doc:`server_framework_101/06_security`
- :doc:`server_framework_101/07_advanced_views` - :doc:`server_framework_101/07_advanced_views`
- :doc:`server_framework_101/08_inheritance` - :doc:`server_framework_101/08_inheritance`

View File

@ -65,8 +65,8 @@ classes, fields are defined as class attributes. Each field is an instance of a
`odoo.fields` package. For example, `Char`, `Float`, `Boolean`, each designed to handle different `odoo.fields` package. For example, `Char`, `Float`, `Boolean`, each designed to handle different
types of data. When defining a field, developers can pass various arguments to finely control how types of data. When defining a field, developers can pass various arguments to finely control how
data is handled and presented in Odoo. For example, `string` defines the label for the field in the data is handled and presented in Odoo. For example, `string` defines the label for the field in the
user interface, `help` provides a tooltip when hovering the field in the user interface, and user interface, `help` provides a tooltip when hovering the field in the user interface, `required`
`required` makes filling in the field mandatory. makes filling in the field mandatory, and `default` provides a default field value.
Individual data entries are called **records**. They are based on the structure defined by models Individual data entries are called **records**. They are based on the structure defined by models
and contain the actual data for each field specified in the model. In Python, records are and contain the actual data for each field specified in the model. In Python, records are
@ -74,10 +74,6 @@ represented as instances of the model's class, allowing developers to interact w
object-oriented programming techniques. For example, in a real estate application using a tenant object-oriented programming techniques. For example, in a real estate application using a tenant
model, each specific tenant (such as "Bafien Carpink") would be a separate record of that model. model, each specific tenant (such as "Bafien Carpink") would be a separate record of that model.
.. seealso::
For the full list of fields and their attributes, see the :ref:`reference documentation
<reference/orm/fields>`.
.. example:: .. example::
Before we dive into creating our own models, let's take a look at a basic example of a model that Before we dive into creating our own models, let's take a look at a basic example of a model that
represents storable products. It defines a `product` model with the `Product` class inheriting represents storable products. It defines a `product` model with the `Product` class inheriting
@ -94,12 +90,12 @@ model, each specific tenant (such as "Bafien Carpink") would be a separate recor
name = fields.Char(string="Name", required=True) name = fields.Char(string="Name", required=True)
description = fields.Text(string="Description") description = fields.Text(string="Description")
price = fields.Float(string="Sale Price", required=True) price = fields.Float(string="Sales Price", required=True)
category = fields.Selection( category = fields.Selection(
string="Category", string="Category",
help="The category of the product; if none are suitable, select 'Other'.", help="The category of the product; if none are suitable, select 'Other'.",
selection=[ selection=[
('apparel', "Clothing") ('apparel', "Clothing"),
('electronics', "Electronics"), ('electronics', "Electronics"),
('home_decor', "Home Decor"), ('home_decor', "Home Decor"),
('other', "Other"), ('other', "Other"),
@ -116,11 +112,14 @@ model, each specific tenant (such as "Bafien Carpink") would be a separate recor
- `category` is a `Selection` field with predefined options, each defined by a technical code - `category` is a `Selection` field with predefined options, each defined by a technical code
and a corresponding label. Since it is required, a default value is provided. and a corresponding label. Since it is required, a default value is provided.
.. seealso::
For the full list of fields and their attributes, see the :ref:`reference documentation
<reference/orm/fields>`.
Building on these new concepts, let's now create the first model for our real estate app. We'll Building on these new concepts, let's now create the first model for our real estate app. We'll
create a model with some fields to represent real estate properties and their characteristics. create a model with some fields to represent real estate properties and their characteristics.
.. exercise:: .. exercise::
#. Create a new :file:`real_estate_property.py` file at the root of the `real_estate` module. #. Create a new :file:`real_estate_property.py` file at the root of the `real_estate` module.
#. Update the :file:`real_estate/__init__.py` file to relatively import the #. Update the :file:`real_estate/__init__.py` file to relatively import the
:file:`real_estate_property.py` file, like so: :file:`real_estate_property.py` file, like so:
@ -132,19 +131,21 @@ create a model with some fields to represent real estate properties and their ch
#. Define a new model with `real.estate.property` as `_name` and a short `_description`. #. Define a new model with `real.estate.property` as `_name` and a short `_description`.
#. Add fields to represent the following characteristics: #. Add fields to represent the following characteristics:
- Name (required) - :guilabel:`Name` (required)
- Description - :guilabel:`Description`
- Image (max 600x400 pixels) - :guilabel:`Image` (max 600x400 pixels)
- Active (default to true) - :guilabel:`Active` (whether the property listing is active; defaults to true)
- State (new, offer received, under option, or sold; required; default to new) - :guilabel:`State` (:guilabel:`New`, :guilabel:`Offer Received`, :guilabel:`Under Option`, or
- Type (house, apartment, office building, retail space, or warehouse; required; default to :guilabel:`Sold`; required; defaults to :guilabel:`New`)
house) - :guilabel:`Type` (:guilabel:`House`, :guilabel:`Apartment`, :guilabel:`Office Building`,
- Selling Price (without currency; with help text; required) :guilabel:`Retail Space`, or :guilabel:`Warehouse`; required; defaults to :guilabel:`House`)
- Availability Date (default to creation date + two months) - :guilabel:`Selling Price` (without currency; with help text; required)
- Floor Area (in square meters; with help text) - :guilabel:`Availability Date`
- Number of Bedrooms (default to two) - :guilabel:`Floor Area` (in square meters; with help text)
- Whether there is a garden - :guilabel:`Number of Bedrooms` (defaults to two)
- Whether there is a garage - :guilabel:`Garage` (whether there is a garage)
- :guilabel:`Garden` (whether there is a garden)
- :guilabel:`Garden Area` (in square meters; with help text)
.. tip:: .. tip::
- The class name doesn't matter, but the convention is to use the model's upper-cased `_name` - The class name doesn't matter, but the convention is to use the model's upper-cased `_name`
@ -163,7 +164,6 @@ create a model with some fields to represent real estate properties and their ch
:caption: `real_estate_property.py` :caption: `real_estate_property.py`
from odoo import fields, models from odoo import fields, models
from odoo.tools import date_utils
class RealEstateProperty(models.Model): class RealEstateProperty(models.Model):
@ -200,15 +200,16 @@ create a model with some fields to represent real estate properties and their ch
selling_price = fields.Float( selling_price = fields.Float(
string="Selling Price", help="The selling price excluding taxes.", required=True string="Selling Price", help="The selling price excluding taxes.", required=True
) )
availability_date = fields.Date( availability_date = fields.Date(string="Availability Date")
string="Availability Date", default=date_utils.add(fields.Date.today(), months=2)
)
floor_area = fields.Integer( floor_area = fields.Integer(
string="Floor Area", help="The floor area in square meters excluding the garden." string="Floor Area", help="The floor area in square meters excluding the garden."
) )
bedrooms = fields.Integer(string="Number of bedrooms", default=2) bedrooms = fields.Integer(string="Number of bedrooms", default=2)
has_garden = fields.Boolean(string="Garden")
has_garage = fields.Boolean(string="Garage") has_garage = fields.Boolean(string="Garage")
has_garden = fields.Boolean(string="Garden")
garden_area = fields.Integer(
string="Garden Area", help="The garden area excluding the building."
)
Congrats, you have just defined the first model of our real estate app! However, the changes have Congrats, you have just defined the first model of our real estate app! However, the changes have
not yet been applied to the database. To do so, you must add the `-u real_estate` argument to the not yet been applied to the database. To do so, you must add the `-u real_estate` argument to the
@ -235,7 +236,6 @@ you created translate into a new SQL table. We will use `psql`, the CLI
:dfn:`command-line interface` allowing to browse and interact with PostgreSQL databases. :dfn:`command-line interface` allowing to browse and interact with PostgreSQL databases.
.. exercise:: .. exercise::
#. In your terminal, execute the command :command:`psql -d tutorials`. #. In your terminal, execute the command :command:`psql -d tutorials`.
#. Enter the command :command:`\\d real_estate_property` to print the description of the #. Enter the command :command:`\\d real_estate_property` to print the description of the
`real_estate_property` table. `real_estate_property` table.
@ -246,6 +246,7 @@ you created translate into a new SQL table. We will use `psql`, the CLI
.. spoiler:: Solution .. spoiler:: Solution
.. code-block:: text .. code-block:: text
:caption: terminal
$ psql -d tutorials $ psql -d tutorials
@ -256,6 +257,7 @@ you created translate into a new SQL table. We will use `psql`, the CLI
id | integer | | not null | nextval('real_estate_property_id_seq'::regclass) id | integer | | not null | nextval('real_estate_property_id_seq'::regclass)
floor_area | integer | | | floor_area | integer | | |
bedrooms | integer | | | bedrooms | integer | | |
garden_area | integer | | |
create_uid | integer | | | create_uid | integer | | |
write_uid | integer | | | write_uid | integer | | |
name | character varying | | not null | name | character varying | | not null |
@ -264,8 +266,8 @@ you created translate into a new SQL table. We will use `psql`, the CLI
availability_date | date | | | availability_date | date | | |
description | text | | | description | text | | |
active | boolean | | | active | boolean | | |
has_garden | boolean | | |
has_garage | boolean | | | has_garage | boolean | | |
has_garden | boolean | | |
create_date | timestamp without time zone | | | create_date | timestamp without time zone | | |
write_date | timestamp without time zone | | | write_date | timestamp without time zone | | |
selling_price | double precision | | not null | selling_price | double precision | | not null |
@ -343,9 +345,6 @@ The most common data operation is creating new records through the `record` and
elements, but other operations exist, such as `delete`, which deletes previously created records, or elements, but other operations exist, such as `delete`, which deletes previously created records, or
even `function`, which allows executing arbitrary code. even `function`, which allows executing arbitrary code.
.. seealso::
:doc:`Reference documentation for XML data files <../../reference/backend/data>`
Some data operations require their data elements to be uniquely identified by the system. This is Some data operations require their data elements to be uniquely identified by the system. This is
achieved by means of the `id` attribute, also known as the **XML ID** or **external identifier**. It achieved by means of the `id` attribute, also known as the **XML ID** or **external identifier**. It
provides a way for other elements to reference it with the `ref` attribute and links data elements provides a way for other elements to reference it with the `ref` attribute and links data elements
@ -387,10 +386,12 @@ created from a data file so that records can be referenced by their full XML ID
- The `ref` attribute is used to reference other records by their XML ID and use their record - The `ref` attribute is used to reference other records by their XML ID and use their record
ID as value. ID as value.
.. seealso::
:doc:`Reference documentation for XML data files <../../reference/backend/data>`
Let's now load some default real estate properties in our database. Let's now load some default real estate properties in our database.
.. exercise:: .. exercise::
#. Create a new :file:`real_estate_property_data.xml` file at the root of the `real_estate` #. Create a new :file:`real_estate_property_data.xml` file at the root of the `real_estate`
module. module.
#. Update the manifest to let the server know that it should load our data file. To do so, have #. Update the manifest to let the server know that it should load our data file. To do so, have
@ -441,10 +442,12 @@ Let's now load some default real estate properties in our database.
<field name="image" type="base64" file="real_estate/country_house.png"/> <field name="image" type="base64" file="real_estate/country_house.png"/>
<field name="type">house</field> <field name="type">house</field>
<field name="selling_price">745000</field> <field name="selling_price">745000</field>
<field name="availability_date">2024-08-01</field>
<field name="floor_area">416</field> <field name="floor_area">416</field>
<field name="bedrooms">5</field> <field name="bedrooms">5</field>
<field name="has_garden">True</field>
<field name="has_garage">True</field> <field name="has_garage">True</field>
<field name="has_garden">True</field>
<field name="garden_area">2100</field>
</record> </record>
<record id="real_estate.loft" model="real.estate.property"> <record id="real_estate.loft" model="real.estate.property">
@ -456,8 +459,8 @@ Let's now load some default real estate properties in our database.
<field name="availability_date">2025-01-01</field> <field name="availability_date">2025-01-01</field>
<field name="floor_area">195</field> <field name="floor_area">195</field>
<field name="bedrooms">3</field> <field name="bedrooms">3</field>
<field name="has_garden">False</field>
<field name="has_garage">True</field> <field name="has_garage">True</field>
<field name="has_garden">False</field>
</record> </record>
<record id="real_estate.mixed_use_commercial" model="real.estate.property"> <record id="real_estate.mixed_use_commercial" model="real.estate.property">
@ -469,8 +472,8 @@ Let's now load some default real estate properties in our database.
<field name="availability_date">2024-10-02</field> <field name="availability_date">2024-10-02</field>
<field name="floor_area">370</field> <field name="floor_area">370</field>
<field name="bedrooms">0</field> <field name="bedrooms">0</field>
<field name="has_garden">False</field>
<field name="has_garage">False</field> <field name="has_garage">False</field>
<field name="has_garden">False</field>
</record> </record>
</odoo> </odoo>
@ -484,9 +487,6 @@ In addition to XML data files, the server framework allows loading data files in
format is often more convenient for describing records with simple field values belonging to the format is often more convenient for describing records with simple field values belonging to the
same model. It also loads faster, making it the go-to format when performance matters most. same model. It also loads faster, making it the go-to format when performance matters most.
.. seealso::
:ref:`Reference documentation for CSV data files <reference/data/csvdatafiles>`
.. example:: .. example::
See below for an example of how a subset of `country states can be loaded into Odoo See below for an example of how a subset of `country states can be loaded into Odoo
<{GITHUB_PATH}/odoo/addons/base/data/res.country.state.csv>`_. <{GITHUB_PATH}/odoo/addons/base/data/res.country.state.csv>`_.
@ -510,7 +510,6 @@ same model. It also loads faster, making it the go-to format when performance ma
state_ca_yt,ca,"Yukon","YT" state_ca_yt,ca,"Yukon","YT"
.. note:: .. note::
- The file name must match the model name. - The file name must match the model name.
- The first line lists the model fields to populate. - The first line lists the model fields to populate.
- XML IDs are specified via the special `id` field. - XML IDs are specified via the special `id` field.
@ -518,6 +517,9 @@ same model. It also loads faster, making it the go-to format when performance ma
as value. as value.
- Each subsequent line describes one new record. - Each subsequent line describes one new record.
.. seealso::
:ref:`Reference documentation for CSV data files <reference/data/csvdatafiles>`
In business applications like Odoo, one of the first questions to consider is who can access the In business applications like Odoo, one of the first questions to consider is who can access the
data. By default, access to newly created models is restricted until it is explicitly granted. data. By default, access to newly created models is restricted until it is explicitly granted.
Granting access rights is done by creating records of the `ir.model.access` model, which specifies Granting access rights is done by creating records of the `ir.model.access` model, which specifies
@ -532,7 +534,6 @@ began being logged at server start-up after creating the model:
WARNING tutorials odoo.modules.loading: The models ['real.estate.property'] have no access rules [...] WARNING tutorials odoo.modules.loading: The models ['real.estate.property'] have no access rules [...]
.. exercise:: .. exercise::
#. Create a new :file:`ir.model.access.csv` file at the root of the `real_estate` module. #. Create a new :file:`ir.model.access.csv` file at the root of the `real_estate` module.
#. Declare it in the manifest as you did for the :file:`real_estate_property_data.xml` file. #. Declare it in the manifest as you did for the :file:`real_estate_property_data.xml` file.
#. Grant access to the `real.estate.property` model to all administrators of the database by #. Grant access to the `real.estate.property` model to all administrators of the database by
@ -550,7 +551,7 @@ began being logged at server start-up after creating the model:
.. spoiler:: Solution .. spoiler:: Solution
.. code-block:: py .. code-block:: python
:caption: `__manifest__.py` :caption: `__manifest__.py`
:emphasize-lines: 2 :emphasize-lines: 2

View File

@ -16,8 +16,8 @@ Add menus items
different parts of Odoo and can be nested to form a hierarchical structure. This allows the different parts of Odoo and can be nested to form a hierarchical structure. This allows the
functionalities of complex applications to be organized into categories and sub-categories and makes functionalities of complex applications to be organized into categories and sub-categories and makes
them easier to navigate. The top level of the menu structure typically contains the menu items for them easier to navigate. The top level of the menu structure typically contains the menu items for
the main applications (like "Contacts", "Sales", and "Accounting"). These top-level menu items can the main applications (like :guilabel:`Contacts`, :guilabel:`Sales`, and :guilabel:`Accounting`).
also be visually enhanced with custom icons for better recognition. These top-level menu items can also be visually enhanced with custom icons for better recognition.
Menu items can take on two distinct roles: Menu items can take on two distinct roles:
@ -47,15 +47,16 @@ a data file. Lets do just that and add menu items to our real estate app!
.. exercise:: .. exercise::
#. Create and declare a new :file:`menus.xml` file at the root of the `real_estate` module. #. Create and declare a new :file:`menus.xml` file at the root of the `real_estate` module.
#. Describe a new "Real Estate" menu item to serve as root menu for our real estate app. #. Describe a new :guilabel:`Real Estate` menu item to serve as root menu for our real estate
app.
- Leave the `parent_id` field empty to place the menu item in the top-level menu. - Leave the `parent_id` field empty to place the menu item in the top-level menu.
- Use the `static/description/icon.png` file as `web_icon`, in the format - Use the `static/description/icon.png` file as `web_icon`, in the format
`<module>,<icon_file_path>`. `<module>,<icon_file_path>`.
#. Nest new "Properties" and "Settings" menu items under the root menu item. As we have not yet #. Nest new :guilabel:`Properties` and :guilabel:`Settings` menu items under the root menu item.
created an action to browse properties or open settings, reference the following existing As we have not yet created an action to browse properties or open settings, reference the
actions instead: following existing actions instead:
- `base.open_module_tree` that opens the list of modules. - `base.open_module_tree` that opens the list of modules.
- `base.action_client_base_menu` that opens the general settings. - `base.action_client_base_menu` that opens the general settings.
@ -128,7 +129,7 @@ it simplifies the syntax and automatically handles some technical details for yo
</menuitem> </menuitem>
.. note:: .. note::
- The outer `menuitem` data operation creates the top-level "Product" menu item. - The outer `menuitem` data operation creates the top-level :guilabel:`Product` menu item.
- The specifications (`name`, `web_icon`, `sequence`, `action`, ...) of menu items are set - The specifications (`name`, `web_icon`, `sequence`, `action`, ...) of menu items are set
through attributes of the XML element. through attributes of the XML element.
- The menu items hierarchy is defined by nesting their XML elements. - The menu items hierarchy is defined by nesting their XML elements.
@ -197,9 +198,6 @@ the `ir.actions.act_window` model whose key fields include:
`help` `help`
An optional help text for the users when there are no records to display. An optional help text for the users when there are no records to display.
.. seealso::
:doc:`Reference documentation for actions <../../reference/backend/actions>`
.. example:: .. example::
The example below defines an action to open existing products in either list or form view. The example below defines an action to open existing products in either list or form view.
@ -220,16 +218,18 @@ the `ir.actions.act_window` model whose key fields include:
The content of the `help` field can be written in different formats thanks to the `type` The content of the `help` field can be written in different formats thanks to the `type`
attribute of the :ref:`field <reference/data/field>` data operation. attribute of the :ref:`field <reference/data/field>` data operation.
.. seealso::
:ref:`Reference documentation for window actions <reference/actions/window>`
As promised, we'll finally get to interact with our real estate properties in the UI. All we need As promised, we'll finally get to interact with our real estate properties in the UI. All we need
now is an action to assign to the menu item. now is an action to assign to the menu item.
.. exercise:: .. exercise::
#. Create and declare a new :file:`actions.xml` file at the root of the `real_estate` module. #. Create and declare a new :file:`actions.xml` file at the root of the `real_estate` module.
#. Describe a new "Properties" window action that opens `real.estate.property` records in list #. Describe a new :guilabel:`Properties` window action that opens `real.estate.property` records
and form views, and assign it to the "Properties" menu item. Be creative with the help text! in list and form views, and assign it to the :guilabel:`Properties` menu item. Be creative
For reference, the list of supported classes can be found in the `view.scss with the help text! For reference, the list of supported classes can be found in the
<{GITHUB_PATH}/addons/web/static/src/views/view.scss>`_ file. `view.scss <{GITHUB_PATH}/addons/web/static/src/views/view.scss>`_ file.
.. tip:: .. tip::
Pay attention to the declaration order of data files in the manifest; you might introduce a Pay attention to the declaration order of data files in the manifest; you might introduce a
@ -279,9 +279,10 @@ now is an action to assign to the menu item.
action="real_estate.view_properties_action" action="real_estate.view_properties_action"
/> />
Clicking the "Properties" menu item now displays a list view of the default properties we created Clicking the :guilabel:`Properties` menu item now displays a list view of the default properties we
earlier. As we specified in the action that both list and form views were allowed, you can click any created earlier. As we specified in the action that both list and form views were allowed, you can
property record to display its form view. Delete all three records to see the help text you created. click any property record to display its form view. Delete all three records to see the help text
you created.
.. _tutorials/server_framework_101/create_custom_views: .. _tutorials/server_framework_101/create_custom_views:
@ -315,11 +316,6 @@ structure and content of the view. These components can be structural (like `she
layout responsive, or `group` that defines column layouts) or semantic (like `field` that displays layout responsive, or `group` that defines column layouts) or semantic (like `field` that displays
field labels and values). field labels and values).
.. seealso::
- :doc:`Reference documentation for view records <../../reference/user_interface/view_records>`
- :doc:`Reference documentation for view architectures
<../../reference/user_interface/view_architectures>`
.. example:: .. example::
The following examples demonstrate how to define simple list, form and search views for the The following examples demonstrate how to define simple list, form and search views for the
`product` model. `product` model.
@ -374,10 +370,14 @@ field labels and values).
</record> </record>
.. note:: .. note::
- The XML structure differs between view types. - The XML structure differs between view types.
- The `description` field is omitted from the list view because it wouldn't fit visually. - The `description` field is omitted from the list view because it wouldn't fit visually.
.. seealso::
- :doc:`Reference documentation for view records <../../reference/user_interface/view_records>`
- :doc:`Reference documentation for view architectures
<../../reference/user_interface/view_architectures>`
In :ref:`the previous section <tutorials/server_framework_101/define_window_actions>`, we defined In :ref:`the previous section <tutorials/server_framework_101/define_window_actions>`, we defined
the `view_mode` of our action to display `real.estate.property` records in list and form view. the `view_mode` of our action to display `real.estate.property` records in list and form view.
Although we haven't created the corresponding views yet, the server framework had our back and Although we haven't created the corresponding views yet, the server framework had our back and
@ -397,15 +397,17 @@ List view
For a start, the list view could use more fields than just the name. For a start, the list view could use more fields than just the name.
.. exercise:: .. exercise::
#. Create a new :file:`real_estate_property_views.xml` file at the root of the `real_estate` #. Create a new :file:`real_estate_property_views.xml` file at the root of the `real_estate`
module. module.
#. Create a custom list view to display the following fields of the `real.estate.property` model #. Create a custom list view to display the following fields of the `real.estate.property` model
in the given order: name, state, type, selling price, availability date, floor area, number of in the given order: :guilabel:`Name`, :guilabel:`State`, :guilabel:`Type`,
bedrooms, presence of a garden, and presence of a garage. :guilabel:`Selling Price`, :guilabel:`Availability Date`, :guilabel:`Floor Area`,
#. Make the visibility of the floor area and all following fields optional so that only the floor :guilabel:`Number of Bedrooms`, :guilabel:`Garage`, :guilabel:`Garden`, and
area is visible by default, while the remaining fields are hidden by default and must be :guilabel:`Garden Area`.
displayed by accessing the view's column selector (:icon:`oi-settings-adjust` button). #. Make the visibility of :guilabel:`Floor Area` and all following fields optional so that only
the floor area is visible by default, while the remaining fields are hidden by default and
must be manually displayed by accessing the view's column selector
(:icon:`oi-settings-adjust` button).
#. After restarting the server to load the new data, refresh the browser to see the result. #. After restarting the server to load the new data, refresh the browser to see the result.
.. tip:: .. tip::
@ -449,8 +451,9 @@ For a start, the list view could use more fields than just the name.
<field name="availability_date"/> <field name="availability_date"/>
<field name="floor_area" optional="show"/> <field name="floor_area" optional="show"/>
<field name="bedrooms" optional="hide"/> <field name="bedrooms" optional="hide"/>
<field name="has_garden" optional="hide"/>
<field name="has_garage" optional="hide"/> <field name="has_garage" optional="hide"/>
<field name="has_garden" optional="hide"/>
<field name="garden_area" optional="hide"/>
</list> </list>
</field> </field>
</record> </record>
@ -463,7 +466,6 @@ Form view
--------- ---------
.. exercise:: .. exercise::
In the :file:`real_estate_property_views.xml` file, create a custom form view to display all In the :file:`real_estate_property_views.xml` file, create a custom form view to display all
fields of the `real.estate.property` model in a well-structured manner: fields of the `real.estate.property` model in a well-structured manner:
@ -475,8 +477,10 @@ Form view
- The image should be displayed as a thumbnail on the right side of the form. - The image should be displayed as a thumbnail on the right side of the form.
- The fields should be grouped in two sections displayed next to each other: - The fields should be grouped in two sections displayed next to each other:
- Listing Information: Type, Selling Price, Availability Date, Active - Listing Information: :guilabel:`Type`, :guilabel:`Selling Price`,
- Building Specifications: Floor Area, Number of Bedrooms, Garden, Garage :guilabel:`Availability Date`, :guilabel:`Active`
- Building Specifications: :guilabel:`Floor Area`, :guilabel:`Number of Bedrooms`,
:guilabel:`Garage`, :guilabel:`Garden`, :guilabel:`Garden Area`
- The description should be displayed at the bottom of the form in its own section, should have - The description should be displayed at the bottom of the form in its own section, should have
no label, should have a placeholder, and should take the full width. no label, should have a placeholder, and should take the full width.
@ -525,8 +529,9 @@ Form view
<group string="Building Specifications"> <group string="Building Specifications">
<field name="floor_area"/> <field name="floor_area"/>
<field name="bedrooms"/> <field name="bedrooms"/>
<field name="has_garden"/>
<field name="has_garage"/> <field name="has_garage"/>
<field name="has_garden"/>
<field name="garden_area"/>
</group> </group>
</group> </group>
<separator string="Description"/> <separator string="Description"/>
@ -555,19 +560,12 @@ automatically excluded from searches. You can observe this behavior by deselecti
:guilabel:`Active` checkbox for one of your property records: you'll notice the record no longer :guilabel:`Active` checkbox for one of your property records: you'll notice the record no longer
appears upon returning to the list view. appears upon returning to the list view.
.. seealso::
:ref:`Reference documentation for the list of reserved field names
<reference/orm/fields/reserved>`
To facilitate the browsing of archived properties, we need to create a search view. Unlike list and To facilitate the browsing of archived properties, we need to create a search view. Unlike list and
form views, search views are not used to display record data on screen. Instead, they define the form views, search views are not used to display record data on screen. Instead, they define the
search behavior and enable users to search on specific fields. They also provide pre-defined search behavior and enable users to search on specific fields. They also provide pre-defined
**filters** that allow for quickly searching with complex queries and grouping records by particular **filters** that allow for quickly searching with complex queries and grouping records by particular
fields. fields.
.. seealso::
:ref:`Reference documentation for search views <reference/view_architectures/search>`
The most common way to set up filters is through **search domains**. Domains are used to select The most common way to set up filters is through **search domains**. Domains are used to select
specific records of a model by defining a list of criteria. Each criterion is a triplet in the specific records of a model by defining a list of criteria. Each criterion is a triplet in the
format :code:`(<field_name>, <operator>, <value>)`. format :code:`(<field_name>, <operator>, <value>)`.
@ -594,32 +592,37 @@ before its operands`.
['|', ('category', '=', 'electronics'), '!', '&', ('price', '>=', 1000), ('price', '<', 2000)] ['|', ('category', '=', 'electronics'), '!', '&', ('price', '>=', 1000), ('price', '<', 2000)]
.. seealso:: .. seealso::
:ref:`Reference documentation for search domains <reference/orm/domains>` - :ref:`Reference documentation for search views <reference/view_architectures/search>`
- :ref:`Reference documentation for search domains <reference/orm/domains>`
- :ref:`Reference documentation for the list of reserved field names
<reference/orm/fields/reserved>`
All the generic search view only allows for is searching on property names; that's the bare minimum. All the generic search view only allows for is searching on property names; that's the bare minimum.
Let's enhance the search capabilities. Let's enhance the search capabilities.
.. exercise:: .. exercise::
#. Create a custom search view with the following features: #. Create a custom search view with the following features:
- Enable searching on the these fields: - Enable searching on the these fields:
- Name: Match records whose name contain the search value. - :guilabel:`Name`: Match records whose name contain the search value.
- Description: Match records whose description *or* name contains the search value. - :guilabel:`Description`: Match records whose description *or* name contains the search
- Selling price: Match records with a price *less than or equal to* the search value. value.
- Floor area: Match records with a floor area *at least* the search value. - :guilabel:`Selling Price`: Match records with a price *less than or equal to* the search
- Number of bedrooms: Match records with *at least* the given number of bedrooms. value.
- :guilabel:`Floor Area`: Match records with a floor area *at least* the search value.
- :guilabel:`Number of Bedrooms`: Match records with *at least* the given number of
bedrooms.
- Implement these filters: - Implement these filters:
- For Sale: The state is "New" or "Offer Received". - :guilabel:`For Sale`: The state is :guilabel:`New` or :guilabel:`Offer Received`.
- Availability Date: Display a list of pre-defined availability date values. - :guilabel:`Availability Date`: Display a list of pre-defined availability date values.
- Garden: The property has a garden. - :guilabel:`Garage`: The property has a garage.
- Garage: The property has a garage. - :guilabel:`Garden`: The property has a garden.
- Archived: The property is archived. - :guilabel:`Archived`: The property is archived.
- Combine selected filters with a logical AND, except for Garden and Garage, which should use - Combine selected filters with a logical AND, except for Garage and Garden, which should use
OR when both are selected. OR when both are selected.
- Enable grouping properties by state and type. - Enable grouping properties by state and type.
@ -672,8 +675,8 @@ Let's enhance the search capabilities.
<separator/> <separator/>
<filter name="filter_availability" date="availability_date"/> <filter name="filter_availability" date="availability_date"/>
<separator/> <separator/>
<filter name="filter_garden" string="Garden" domain="[('has_garden', '=', True)]"/>
<filter name="filter_garage" string="Garage" domain="[('has_garage', '=', True)]"/> <filter name="filter_garage" string="Garage" domain="[('has_garage', '=', True)]"/>
<filter name="filter_garden" string="Garden" domain="[('has_garden', '=', True)]"/>
<separator/> <separator/>
<filter name="filter_inactive" string="Archived" domain="[('active', '=', False)]"/> <filter name="filter_inactive" string="Archived" domain="[('active', '=', False)]"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -10,8 +10,8 @@ of our real estate application.
.. _tutorials/server_framework_101/module_structure: .. _tutorials/server_framework_101/module_structure:
Module structure Organize your module structure
================ ==============================
As our `real_estate` module grows, you may notice that we've already created a dozen files for just As our `real_estate` module grows, you may notice that we've already created a dozen files for just
one model, along with its menu items, actions and views. With more models on the horizon, our module one model, along with its menu items, actions and views. With more models on the horizon, our module
@ -25,10 +25,6 @@ structure guidelines** that offer several benefits:
- **Collaboration**: A standardized structure facilitates understanding among contributors and - **Collaboration**: A standardized structure facilitates understanding among contributors and
ensures easier integration with the Odoo ecosystem. ensures easier integration with the Odoo ecosystem.
.. seealso::
:ref:`Coding guidelines on module directories
<contributing/coding_guidelines/module_structure/directories>`
.. example:: .. example::
Let's consider a possible structure for our example `product` module: Let's consider a possible structure for our example `product` module:
@ -62,7 +58,6 @@ structure guidelines** that offer several benefits:
└── __manifest__.py └── __manifest__.py
.. note:: .. note::
- The :file:`models` directory contains its own :file:`__init__.py` file, simplifying Python - The :file:`models` directory contains its own :file:`__init__.py` file, simplifying Python
imports. The root :file:`__init__.py` file imports the :file:`models` Python package, which imports. The root :file:`__init__.py` file imports the :file:`models` Python package, which
in turns imports individual model files. in turns imports individual model files.
@ -77,8 +72,11 @@ structure guidelines** that offer several benefits:
- The :file:`__init__.py` and :file:`__manifest__.py` files remain in the module's root - The :file:`__init__.py` and :file:`__manifest__.py` files remain in the module's root
directory. directory.
.. exercise:: .. seealso::
:ref:`Coding guidelines on module directories
<contributing/coding_guidelines/module_structure/directories>`
.. exercise::
Restructure the `real_estate` module according to the guidelines. Restructure the `real_estate` module according to the guidelines.
.. tip:: .. tip::
@ -193,8 +191,8 @@ structure guidelines** that offer several benefits:
.. _tutorials/server_framework_101/many2one: .. _tutorials/server_framework_101/many2one:
Many-to-one Many-to-one relationships
=========== =========================
As promised at the end of :doc:`the previous chapter <03_build_user_interface>`, we'll now expand As promised at the end of :doc:`the previous chapter <03_build_user_interface>`, we'll now expand
our app's capabilities by adding new models to manage additional information. This expansion our app's capabilities by adding new models to manage additional information. This expansion
@ -211,14 +209,11 @@ representing the *many* side of the relationship. The field is represented in th
record. By convention, `Many2one` field names end with the `_id` suffix, indicating that they store record. By convention, `Many2one` field names end with the `_id` suffix, indicating that they store
the referenced record's ID. the referenced record's ID.
.. seealso::
:ref:`Reference documentation for Many2one fields <reference/fields/many2one>`
.. example:: .. example::
In the example below, the `Selection` field of the `product` model is replaced by a `Many2one` In the example below, the `Selection` field of the `product` model is replaced by a `Many2one`
field to create a more flexible and scalable model structure. field to create a more flexible and scalable model structure.
.. code-block:: py .. code-block:: python
from odoo import fields, models from odoo import fields, models
@ -239,17 +234,18 @@ the referenced record's ID.
name = fields.Char(string="Name") name = fields.Char(string="Name")
.. note:: .. note::
- The relationship only needs to be declared on the *many* side to be established. - The relationship only needs to be declared on the *many* side to be established.
- The `ondelete` argument on the `Many2one` field defines what happens when the referenced - The `ondelete` argument on the `Many2one` field defines what happens when the referenced
record is deleted. record is deleted.
.. seealso::
:ref:`Reference documentation for Many2one fields <reference/fields/many2one>`
In our real estate app, we currently have a fixed set of property types. To increase flexibility, In our real estate app, we currently have a fixed set of property types. To increase flexibility,
let's replace the current `type` field with a many-to-one relationship to a separate model for let's replace the current `type` field with a many-to-one relationship to a separate model for
managing property types. managing property types.
.. exercise:: .. exercise::
#. Create a new `real.estate.property.type` model. #. Create a new `real.estate.property.type` model.
- Update the :file:`ir.model.access.csv` file to grant all database administrators access to - Update the :file:`ir.model.access.csv` file to grant all database administrators access to
@ -258,12 +254,12 @@ managing property types.
--> Property Types` menu item. --> Property Types` menu item.
- Create a window action to browse property types only in list view. - Create a window action to browse property types only in list view.
- Create the list view for property types. - Create the list view for property types.
- In a data file, describe at least as many default property types as the `type` field of the - In a data file, describe at least as many default property types as the :guilabel:`Type`
`real.estate.property` model supports. field of the `real.estate.property` model supports.
#. Replace the `type` field on the `real.estate.property` model by a many-to-one relationship to #. Replace the :guilabel:`Type` field on the `real.estate.property` model by a many-to-one
the `real.estate.property.type` model. Prevent deleting property types if a property relationship to the `real.estate.property.type` model. Prevent deleting property types if a
references them. property references them.
.. tip:: .. tip::
@ -281,7 +277,6 @@ managing property types.
:caption: `real_estate_property_type.py` :caption: `real_estate_property_type.py`
from odoo import fields, models from odoo import fields, models
from odoo.tools import date_utils
class RealEstatePropertyType(models.Model): class RealEstatePropertyType(models.Model):
@ -290,7 +285,7 @@ managing property types.
name = fields.Char(string="Name", required=True) name = fields.Char(string="Name", required=True)
.. code-block:: py .. code-block:: python
:caption: `__init__.py` :caption: `__init__.py`
:emphasize-lines: 2 :emphasize-lines: 2
@ -372,7 +367,7 @@ managing property types.
</odoo> </odoo>
.. code-block:: py .. code-block:: python
:caption: `__manifest__.py` :caption: `__manifest__.py`
:emphasize-lines: 3,4,7,9 :emphasize-lines: 3,4,7,9
@ -387,7 +382,7 @@ managing property types.
'views/menus.xml', # Depends on actions in views. 'views/menus.xml', # Depends on actions in views.
], ],
.. code-block:: py .. code-block:: python
:caption: `real_estate_property.py` :caption: `real_estate_property.py`
:emphasize-lines: 1-3 :emphasize-lines: 1-3
@ -473,35 +468,40 @@ Two frequently used models in Odoo are:
.. seealso:: .. seealso::
`The list of generic models in the base module <{GITHUB_PATH}/odoo/addons/base/models>`_ `The list of generic models in the base module <{GITHUB_PATH}/odoo/addons/base/models>`_
To make our real estate properties more informative, let's add two pieces of information: the seller To make our real estate properties more informative, let's add three pieces of information: the
of the property and the salesperson managing the property. seller of the property, the salesperson managing the property, and the address of the property.
.. exercise:: .. exercise::
#. Add the following fields to the `real.estate.property` model: #. Add the following fields to the `real.estate.property` model:
- Seller (required): The person putting their property on sale; it can be any individual. - :guilabel:`Seller` (required): The person putting their property on sale; it can be any
- Salesperson: The employee of the real estate agency overseeing the sale of the property. individual.
- :guilabel:`Salesperson`: The employee of the real estate agency overseeing the sale of the
property.
- :guilabel:`Address` (required): The address of the property.
#. Modify the form view of properties to include a notebook component. The property description #. Modify the form view of properties to include a notebook component. The property description
should be in the first page, and the two new fields should be in the second page. should be in the first page, and the three new fields should be in the second page.
.. tip:: .. tip::
You don't need to define any new UI component to browse the seller you assigned to your - You don't need to define any new UI component to browse the seller you assigned to your
default properties! Just go to :menuselection:`Apps` and install the :guilabel:`Contacts` app. default properties! Just go to :menuselection:`Apps` and install the :guilabel:`Contacts`
app.
- In Odoo, addresses are usually represented by a partner.
.. spoiler:: Solution .. spoiler:: Solution
.. code-block:: python .. code-block:: python
:caption: `real_estate_property.py` :caption: `real_estate_property.py`
:emphasize-lines: 1-2 :emphasize-lines: 1-3
address_id = fields.Many2one(string="Address", comodel_name='res.partner', required=True)
seller_id = fields.Many2one(string="Seller", comodel_name='res.partner', required=True) seller_id = fields.Many2one(string="Seller", comodel_name='res.partner', required=True)
salesperson_id = fields.Many2one(string="Salesperson", comodel_name='res.users') salesperson_id = fields.Many2one(string="Salesperson", comodel_name='res.users')
.. code-block:: xml .. code-block:: xml
:caption: `real_estate_property_views.xml` :caption: `real_estate_property_views.xml`
:emphasize-lines: 3-18 :emphasize-lines: 3-19
<record id="real_estate.property_form" model="ir.ui.view"> <record id="real_estate.property_form" model="ir.ui.view">
[...] [...]
@ -515,6 +515,7 @@ of the property and the salesperson managing the property.
<page string="Other Info"> <page string="Other Info">
<group> <group>
<group> <group>
<field name="address_id"/>
<field name="seller_id"/> <field name="seller_id"/>
<field name="salesperson_id"/> <field name="salesperson_id"/>
</group> </group>
@ -530,6 +531,30 @@ of the property and the salesperson managing the property.
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<record id="real_estate.country_house_address" model="res.partner">
<field name="name">Country House</field>
<field name="street">Chaussée de Namur 40</field>
<field name="city">Grand-Rosière-Hottomont</field>
<field name="zip">1367</field>
<field name="country_id" ref="base.be"/>
</record>
<record id="real_estate.loft_address" model="res.partner">
<field name="name">Loft</field>
<field name="street">Rue des Bourlottes 9</field>
<field name="city">Grand-Rosière-Hottomont</field>
<field name="zip">1367</field>
<field name="country_id" ref="base.be"/>
</record>
<record id="real_estate.mixed_use_commercial_address" model="res.partner">
<field name="name">Mixed use commercial building</field>
<field name="street">Rue de Ramillies 1</field>
<field name="city">Grand-Rosière-Hottomont</field>
<field name="zip">1367</field>
<field name="country_id" ref="base.be"/>
</record>
<record id="real_estate.bafien_carpink" model="res.partner"> <record id="real_estate.bafien_carpink" model="res.partner">
<field name="name">Bafien Carpink</field> <field name="name">Bafien Carpink</field>
</record> </record>
@ -546,24 +571,27 @@ of the property and the salesperson managing the property.
.. code-block:: xml .. code-block:: xml
:caption: `real_estate_property_data.xml` :caption: `real_estate_property_data.xml`
:emphasize-lines: 3,8,13 :emphasize-lines: 3-4,9-10,15-16
<record id="real_estate.country_house" model="real.estate.property"> <record id="real_estate.country_house" model="real.estate.property">
[...] [...]
<field name="address_id" ref="real_estate.country_house_address"/>
<field name="seller_id" ref="real_estate.amyfromthevideos"/> <field name="seller_id" ref="real_estate.amyfromthevideos"/>
</record> </record>
<record id="real_estate.loft" model="real.estate.property"> <record id="real_estate.loft" model="real.estate.property">
[...] [...]
<field name="address_id" ref="real_estate.loft_address"/>
<field name="seller_id" ref="real_estate.antony_petisuix"/> <field name="seller_id" ref="real_estate.antony_petisuix"/>
</record> </record>
<record id="real_estate.mixed_use_commercial" model="real.estate.property"> <record id="real_estate.mixed_use_commercial" model="real.estate.property">
[...] [...]
<field name="address_id" ref="real_estate.mixed_use_commercial_address"/>
<field name="seller_id" ref="real_estate.bafien_carpink"/> <field name="seller_id" ref="real_estate.bafien_carpink"/>
</record> </record>
.. code-block:: py .. code-block:: python
:caption: `__manifest__.py` :caption: `__manifest__.py`
:emphasize-lines: 3,5,6 :emphasize-lines: 3,5,6
@ -578,8 +606,8 @@ of the property and the salesperson managing the property.
.. _tutorials/server_framework_101/one2many: .. _tutorials/server_framework_101/one2many:
One-to-many One-to-many relationships
=========== =========================
After exploring how to connect multiple records to a single one with many-to-one relationships, After exploring how to connect multiple records to a single one with many-to-one relationships,
let's consider their counterparts: **one-to-many relationships**. These relationships represent the let's consider their counterparts: **one-to-many relationships**. These relationships represent the
@ -592,14 +620,11 @@ note that `One2many` fields don't store data in the database; instead, they prov
that Odoo computes based on the referenced `Many2one` field. By convention, `One2many` field names that Odoo computes based on the referenced `Many2one` field. By convention, `One2many` field names
end with the `_ids` suffix, indicating that they allow accessing the IDs of the connected records. end with the `_ids` suffix, indicating that they allow accessing the IDs of the connected records.
.. seealso::
:ref:`Reference documentation for One2many fields <reference/fields/one2many>`
.. example:: .. example::
In the example below, a `One2many` field is added to the `product.category` model to allow quick In the example below, a `One2many` field is added to the `product.category` model to allow quick
access to the connected products from the product category. access to the connected products from the product category.
.. code-block:: py .. code-block:: python
from odoo import fields, models from odoo import fields, models
@ -623,28 +648,33 @@ end with the `_ids` suffix, indicating that they allow accessing the IDs of the
) )
.. note:: .. note::
The `One2many` field must reference its `Many2one` counterpart through the `inverse_name` The `One2many` field must reference its `Many2one` counterpart through the `inverse_name`
argument. argument.
.. seealso::
:ref:`Reference documentation for One2many fields <reference/fields/one2many>`
A good use case for a one-to-many relationship in our real estate app would be to connect properties A good use case for a one-to-many relationship in our real estate app would be to connect properties
to a list of offers received from potential buyers. to a list of offers received from potential buyers.
.. exercise:: .. exercise::
#. Create a new `real.estate.offer` model. It should have the following fields: #. Create a new `real.estate.offer` model. It should have the following fields:
- Amount (required): The amount offered to buy the property. - :guilabel:`Amount` (required): The amount offered to buy the property.
- Buyer (required): The person making the offer. - :guilabel:`Buyer` (required): The person making the offer.
- Date (required; default to creation date): When the offer was made. - :guilabel:`Date` (required): When the offer was made.
- Validity (default to 7): The number of days before the offer expires. - :guilabel:`Validity` (defaults to 7): The number of days before the offer expires.
- State (required): Either "Waiting", "Accepted", or "Refused". - :guilabel:`State` (required): Either :guilabel:`Waiting`, :guilabel:`Accepted`, or
:guilabel:`Refused`.
#. Create a list and form views for the `real.estate.offer` model. It's not necessary to create #. Create a list and form views for the `real.estate.offer` model. It's not necessary to create
menu items or actions, as offers will be accessible from properties, but feel free to do it menu items or actions, as offers will be accessible from properties, but feel free to do it
anyway! anyway!
#. Allow connecting properties to multiple offers. #. Allow connecting properties to multiple offers.
#. Modify the form view of properties to display offers in a new notebook page titled "Offers". #. Modify the form view of properties to display offers in a new notebook page titled
:guilabel:`Offers`.
.. spoiler:: Solution .. spoiler:: Solution
@ -660,7 +690,7 @@ to a list of offers received from potential buyers.
amount = fields.Float(string="Amount", required=True) amount = fields.Float(string="Amount", required=True)
buyer_id = fields.Many2one(string="Buyer", comodel_name='res.partner', required=True) buyer_id = fields.Many2one(string="Buyer", comodel_name='res.partner', required=True)
date = fields.Date(string="Date", required=True, default=fields.Date.today()) date = fields.Date(string="Date", required=True)
validity = fields.Integer( validity = fields.Integer(
string="Validity", help="The number of days before the offer expires.", default=7 string="Validity", help="The number of days before the offer expires.", default=7
) )
@ -752,7 +782,7 @@ to a list of offers received from potential buyers.
'views/menus.xml', # Depends on actions in views. 'views/menus.xml', # Depends on actions in views.
], ],
.. code-block:: py .. code-block:: python
:caption: `real_estate_property.py` :caption: `real_estate_property.py`
:emphasize-lines: 1-3 :emphasize-lines: 1-3
@ -774,8 +804,8 @@ to a list of offers received from potential buyers.
.. _tutorials/server_framework_101/many2many: .. _tutorials/server_framework_101/many2many:
Many-to-many Many-to-many relationships
============ ==========================
After the many-to-one and one-to-many relationships, let's consider a more complex use case: After the many-to-one and one-to-many relationships, let's consider a more complex use case:
**many-to-many relationships**. These relationships enable *multiple* records in one model to be **many-to-many relationships**. These relationships enable *multiple* records in one model to be
@ -788,14 +818,11 @@ intermediate (junction) table in the database. This table stores pairs of IDs, e
representing a connection between a record of the first model and a record of the second model. By representing a connection between a record of the first model and a record of the second model. By
convention, `Many2many` field names end with the `_ids` suffix, like for `One2many` fields. convention, `Many2many` field names end with the `_ids` suffix, like for `One2many` fields.
.. seealso::
:ref:`Reference documentation for Many2many fields <reference/fields/many2many>`
.. example:: .. example::
In the example below, a many-to-many relationship is established between the `product` model and In the example below, a many-to-many relationship is established between the `product` model and
the `res.partner` model, which is used to represent sellers offering products for sale. the `res.partner` model, which is used to represent sellers offering products for sale.
.. code-block:: py .. code-block:: python
from odoo import fields, models from odoo import fields, models
@ -815,22 +842,23 @@ convention, `Many2many` field names end with the `_ids` suffix, like for `One2ma
) )
.. note:: .. note::
- It is not necessary to add a `Many2many` field to both models of the relationship. - It is not necessary to add a `Many2many` field to both models of the relationship.
- The optional `relation`, `column1`, and `column2` field arguments allow specifying the name - The optional `relation`, `column1`, and `column2` field arguments allow specifying the name
of the junction table and of its columns. of the junction table and of its columns.
.. seealso::
:ref:`Reference documentation for Many2many fields <reference/fields/many2many>`
Let's conclude this extension of the model family by allowing to associate multiple description tags Let's conclude this extension of the model family by allowing to associate multiple description tags
with each property. with each property.
.. exercise:: .. exercise::
#. Create a new `real.estate.tag` model. It should have the following fields: #. Create a new `real.estate.tag` model. It should have the following fields:
- Name (required): The label of the tag. - :guilabel:`Name` (required): The label of the tag.
- Color: The color code to use for the tag, as an integer. - :guilabel:`Color`: The color code to use for the tag, as an integer.
#. In a data file, describe various default property tags. For example, "Renovated". #. In a data file, describe various default property tags. For example, :guilabel:`Renovated`.
#. Create all necessary UI components to manage tags from the :guilabel:`Configuration` category #. Create all necessary UI components to manage tags from the :guilabel:`Configuration` category
menu item. menu item.
#. Allow connecting properties to multiple tags, and tags to multiple properties. #. Allow connecting properties to multiple tags, and tags to multiple properties.
@ -982,7 +1010,7 @@ with each property.
<field <field
name="tag_ids" name="tag_ids"
widget="many2many_tags" widget="many2many_tags"
options="{'color_field': 'color', 'no_quick_create': True, 'no_create_edit': True}" options="{'color_field': 'color', 'no_quick_create': True, 'no_create': True}"
/> />
[...] [...]
</record> </record>

View File

@ -1,35 +0,0 @@
=========================
Chapter 5: Business logic
=========================
tmp
.. todo: constraints, defaults, onchanges, computes
.. todo: model actions ("assign myself as salesperson" action, "view offers" statbutton)
.. todo: explain the env (self.env.cr, self.env.uid, self.env.user, self.env.context, self.env.ref(xml_id), self.env[model_name])
.. todo: explain the thing about `self`
.. todo: explain magic commands
.. todo: copy=False on some fields
.. todo: introduce lambda functions for defaults :point_down:
There is a problem with the way we defined our Date fields: their default value relies on
:code:`fields.Date.today()` or some other static method. When the code is loaded into memory, the
date is computed once and reused for all newly created records until the server is shut down. You
probably didn't notice it, unless you kept your server running for several days, but it would be
much more visible with Datetime fields, as all newly created records would share the same timestamp.
That's where lambda functions come in handy. As they generate an anonymous function each time
they're evaluated at runtime, they can be used in the computation of default field values to return
an updated value for each new record.
.. todo: salesperson_id = fields.Many2one(default=lambda self: self.env.user)
.. todo: real.estate.offer.amount::default -> property.selling_price
.. todo: real.estate.tag.color -> default=_default_color ; def _default_color(self): return random.randint(1, 11)
.. todo: 6,0,0 to associate tags to properties in data
.. todo: unique tag
.. todo: odoo-bin shell section
----
.. todo: add incentive for chapter 6

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,12 @@
=================== ===========================
Chapter 6: Security Chapter 6: Tighten security
=================== ===========================
tmp tmp
.. todo: restrict access through acl
.. todo: rule to only see your properties or unassigned ones
---- ----
.. todo: add incentive for next chapter .. todo: add incentive for next chapter

View File

@ -4,11 +4,17 @@ Chapter 7: Advanced views
tmp tmp
.. todo:: invisible, required, readonly modifiers .. todo: invisible, required, readonly modifiers
.. todo:: introduce bootstrap .. todo: introduce bootstrap
.. todo:: widgets; eg, <widget name="web_ribbon" title="Archived" bg_color="text-bg-danger" invisible="active"/> .. todo: widgets; eg, <widget name="web_ribbon" title="Archived" bg_color="text-bg-danger" invisible="active"/>
.. todo:: add Gantt view of properties availability <field name="is_priority" widget="boolean_favorite" nolabel="1" readonly="False"/> (/!\ requires to have product installed to have the correct font-size in form view)
.. todo:: add Kanban view of properties .. todo: add Gantt view of properties availability
.. todo: add Kanban view of properties
.. todo: wizards -> create a "receive offer wizard" to default the amount to the property's selling price
.. todo: context active_test False on the category_id field of products to see archived categories
.. todo: sequence widget on tags
.. todo: compute display_name for offers in form view
---- ----

View File

@ -4,7 +4,13 @@ Chapter 8: Inheritance
tmp tmp
.. todo:: inherit from mail.tread mixin and add a chatter .. todo: inherit from mail.tread mixin and add a chatter
.. todo: self.env._context
.. todo: inherit from account.move
.. todo: explain magic commands
.. todo: ex: override of real.estate.property::create to set vals['address_id'] = (0, 0, {})
.. todo: ex: create invoice with lines (6, 0, 0)
.. todo: ex: 6,0,0 to associate tags to properties in data
---- ----

View File

@ -78,7 +78,7 @@ GitHub.
#. Once you have pushed your first change to the shared fork on **odoo-dev**, create a **draft** #. Once you have pushed your first change to the shared fork on **odoo-dev**, create a **draft**
:abbr:`PR (Pull Request)` with your quadrigram in the title. This will enable you to share :abbr:`PR (Pull Request)` with your quadrigram in the title. This will enable you to share
your upcoming work and receive feedback from your coaches. To ensure a continuous feedback your upcoming work and receive feedback from your coaches. To ensure a continuous feedback
loop, push a new commit as soon as you complete a chapter of a tutorial. loop, push a new commit as soon as you complete an exercise of a tutorial.
#. At Odoo we use `Runbot <https://runbot.odoo.com>`_ extensively for our :abbr:`CI (Continuous #. At Odoo we use `Runbot <https://runbot.odoo.com>`_ extensively for our :abbr:`CI (Continuous
Integration)` tests. When you push your changes to **odoo-dev**, Runbot creates a new build Integration)` tests. When you push your changes to **odoo-dev**, Runbot creates a new build
and tests your code. Once logged in, you will be able to see your branch on the `Tutorials and tests your code. Once logged in, you will be able to see your branch on the `Tutorials