chapter 5 - connect the dots
This commit is contained in:
parent
32cba451c1
commit
81929ceb1c
@ -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
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
@ -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
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
@ -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`
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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. Let’s 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 |
@ -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>
|
||||||
|
@ -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
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user