In some cases, there is a need to override the create​ method in Odoo and add additional logic to it. 

When overriding this method, the custom logic is triggered on each iteration of creating a new object.

 This means whenever a new object is created the extra code will be evaluated and applied.

In case when the additional logic is closely related to the object's data, this approach can be all right, since the codebase looks simpler and more clear.

However, in situations where some of the custom logic is repeating, especially when using database data, the logic will be executed n-times for n-objects, without any need for that.

In such cases should be used the decorator model_create_multi​. Adding this decorator to the create​ method allows the repeating code to be executed only once and applied to each object using a for​ loop.

It is important to mention that when using the model_create_multi​ decorator, the method receives a list of values while returns a list of newly created objects.

A very simple case to understand the need for this decorator is shown below:


@api.model_create_multi
def create(self, vals_list):
    round_precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
    for vals in vals_list:
		    vals["quantity"] = float_round(vals["quantity"], precision_digits=round_precision)
    return super().create(vals_list)
        

Without using the decorator the logic related to getting the value for round_precision​ will be executed every time a new object is created, which means for a batch of n-objects will be executed n-times.

But in our example, it will be executed only once and applied to all object values using the for​ loop.

Technical
Tips

When using many2many_tags​ widget, it is possible to define a background color for each of the tag values. For that to work the following steps need to be done:

  • The related model needs to have an Integer type field where the color index will be stored, usually named color​.
  • The color​ field needs to be added in the view using the widget color_picker
  • In the definition of the relational field in the XML view, when using the widget many2many_tags​, it needs to be set the option color_field​ with value the name of the color field previously defined.​
  • The last step is optional, by default when a user selects a value, can edit the background color of the value's tag, however, it is possible to make the color not editable at this stage, by using the option no_edit_color

Let’s review the steps above using as an example the relational many2many​ field tag_ids in the sale.order​ model related to the relational model crm.tag​​.

The model crm.tag​ there is a field color​ defined in the model definition as the following:

color = fields.Integer("Color")

The definition of the field in the XML view, using the widget color_picker​ looks like the following:

<field name="color" required="True" widget="color_picker"/>

Having this definition of the field makes it possible for every sales tag value defined in the system to be selected a color which later will be used as a background color for the tag.

Once the field is set up in the relational model the next step is when defining the related field tag_ids​ in the XML views to use the widget many2many_tags​ and add as an option the color_field​.

<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>

This will add a background color for the selected tag values in the sales order. Also users will be able to change the color of the tags during the selection process. A possible issue with this change is that the color will be applied to all sales orders where the same value is used. In case the admin wants to keep the colors same as initially set, it is possible to disable the edit of the color at the moment of selection by using the option no_edit_color​ set to True​.

<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_edit_color': true}"/>
Odoo 17 Odoo 18 Technical
Posts


In Odoo it is really convenient to change the price of multiple products using the pricelist option. In this post we will go through the steps that are needed to make that happen.

At first would be need to enable the option “Advanced Price Rules” in the Sales configuration page, which can be found in Sales >> Configuration >> Settings

Once that is done, open the pricelist page from Sales >> Products >> Pricelists​ and select an existing pricelist or create a new one.

The pricelist can be specific per company, country group or website. In our case we will assume that the pricelist is global for all products without any restriction.

In the “Price Rules” tab click on the button “Add a line” which will open a wizard as the one on the image:


Select the Computation​ method “DIscount” and in the Discount​ field set the value by which the price should be changed.

The important thing when setting this field is to take into consideration that if the price need to increase the value should be negative or in case of decrease it should be positive.

For example if the price need to increase for all products by 10% than the value in this field should be -10. In case the price for all products need to decrease by 10% the value should be 10.

For more complex pricing rules need to be used the option “Formula”, which still have the field Discount​ but additionally can be setup few more variables such as:

  • Base On​:  The price field based on which the calculation will be done (Sales Price or Cost Price)
  • Extra Fee​: Additional fixed fee added on the price calculated with the discount
  • Rounding Method​: Decimal number representing the minimum non-zero value at the desired precision.
  • Margins​: Minimum margin over the base price

The wizard with “Formula” options is shown in the image below:

 

The created pricelist can be assigned to a Customer, used in a Sales Order, or applied on the Website.

Functional Odoo 17 Odoo 18 Sales
Posts


When using a date field in the email templates it is important to use it with formatting function for the value to be represented in the format set in the language settings.

The function for formatting a date is format_date​ and by default is using the format specified in the language settings. However, it is possible to specify a custom one (using LDML format), if needed.

An example of adding a date in an email template is shown in the example below:

<p>Date Created: <t t-out="format_date(object.date_order) or ''"></t></p>

In case it is needed to display datetime​ or time​ values the functions available for them are: format_datetime​ and format_time​.


Functional Technical
Tips

In everyday business cases, there are a lot of situations where a particular field needs to have different attributes or attribute values based on different access groups.

This type of case is possible to handle in Odoo, however, there are two different solutions based on which version of Odoo is used.

In Odoo 15 and older versions, this can be achieved by adding a more generic definition of the field in the main view, while the one specific for certain groups is added in a new inherited view, which is limited only to the specific access group.

For example, a very simple case can be if the field phone​ in the Leads view by default needs to be readonly​, but only for group Sales Administrator is editable:

<record id="crm_lead_view_form" model="https://ir.ui.view">
<field name="name">https://crm.lead.form</field>
<field name="model">crm.lead</field>
<field name="arch" type="xml">
<form>
​​...
<field name='phone' readonly="True"/>
</form>
</field>
</record>

<record id="crm_lead_view_phone_form" model="https://ir.ui.view">
<field name="model">crm.lead</field>
<field name="inherit_id" ref="crm.crm_lead_view_form"/>
<field name="priority">99</field>
<field name="groups_id" eval="[(6, 0, [ref('sales_team.group_sale_manager')])]"/>
<field name="arch" type="xml">
<form>
<field name='phone' position="attributes">
<attribute name="readonly">False</attribute>
</field>
</form>
</field>
</record>

Starting from Odoo 16 this type of inherited view with an attached group to it is no longer supported, and will show a server error if used. The new approach now is by defining the same field twice in the view with different access group rules.

The same example with the phone​ field in Odoo 16 would look like the following:

<record id="crm_lead_view_form" model="ir.ui.view">
    <field name="name">crm.lead.form</field>
    <field name="model">crm.lead</field>
    <field name="arch" type="xml">
        <form>
            ...
            <field name='phone' groups="!sales_team.group_sale_manager" readonly="True"/>
            <field name='phone' groups="sales_team.group_sale_manager" readonly="False"/>
        </form>
    </field>
</record>

To avoid any confusion, this example does not intend to show the best solution on how to make a field editable only for certain access groups, but it is just used to demonstrate how to define the same field with different attributes for different groups.

Odoo 17 Odoo 18 Technical
Tips

The module Partner Stock Risk extends the Account Financial Risk module functionality by adding an extra financial risk check on validation of inventory transfers with "Customer" as a destination location.

Once the module is installed, the extra check is enabled without any additional settings. In order to disable the check the module needs to be uninstalled.

The stock financial risk validation is based only on the General Limits, which means at least one General Limit financial risk needs to be enabled for the check to work.

Specific Limits are not taken into consideration for this check, which is expected, knowing that there is no specific option for Stock in the financial risk Specific Limits section.

The module is a great addition to business processes where additional financial risk validation is needed before dispatching the products. For example, if the invoice is generated after the delivery this module will help in preventing products from being shipped in the first place for customers with financial risk issues.

The module is maintained by the Odoo Community Association (OCA).

Accounting Functional Inventory
Modules Review


A column in a list view can be hidden by using the attributes column_invisible​ or optional​.

The column_invisible​ attribute accepts a boolean value as True/False​ or 1/0​. Since this attribute is related to a column in a table that contains more than one object, it does not support condition-based value related to the tree view’s model. An example of using the attribute in a list view is shown below:


        <field name="name" column_invisible="True"/>

However, if the view is defined as an inline tree view in a form view, can be defined condition based on the value from the form view related object. 
For example, in the account journal entry view the “Tax Incl.” column is shown in the account move line list view only if the Rounding Method is not “Round Globally”.


    <record id="view_move_form" model="ir.ui.view">
        <field name="name">account.move.form</field>
        <field name="model">account.move</field>
        <field name="arch" type="xml">
            <form string="Account Entry" js_class="account_move_form">
            ...
            <field name="invoice_line_ids">
                <tree>
                    ...
                    <field name="price_total"
                    string="Tax incl."
                    column_invisible="parent.tax_calculation_rounding_method == 'round_globally'"
                    />              
                </tree>
            </field>
            </form>
        </field>
    </record>        

The optionl​ attribute supports two possible values show/hide​. In case of hide​ value the column is hidden from the list view by default, however, the user has the option to make it available by using the “Adjustments” option in the right upper corner of the table.

Additionally, It is possible both attributes to be used together:


    <record id="view_move_form" model="ir.ui.view">
        <field name="name">account.move.form</field>
        <field name="model">account.move</field>
        <field name="arch" type="xml">
            <form string="Account Entry" js_class="account_move_form">
            ...
            <field name="invoice_line_ids">
                <tree>
                    ...
                    <field name="price_total"
                    string="Tax incl."
                    column_invisible="parent.tax_calculation_rounding_method == 'round_globally'"
	                optional="hide"
                    />              
                </tree>
            </field>
            </form>
        </field>
    </record>
        

In such cases if the optional attribute is with value hide​ by default the column is not shown in the view. However, depending on the value that column_invisible​ attribute gets it can be available or not in the optional columns shown on click of “Adjustments” icon at the right upper corner of the table.

Odoo 17 Odoo 18 Technical
Tips

In the older versions of Odoo, before 16, there is “Edit” button, on click of which the view is opened in edit mode. Once the user is finished with the changes on click of “Save” the changes are stored in the database. That makes clear when the user is in Edit mode and when the changes are saved.

In newer versions of Odoo the “Edit” button does not exist anymore. The edit can be done by simply clicking on the field’s input area. This approach has positive sides, such as eliminating a few clicks for the user and doing the edits on the fly. However, it is a bit more challenging for the users to understand how it is working, especially for new users.

Here are a few general rules that can help in understanding how the Edit works in the newer versions of Odoo:

  • Any editable field on hover has the bottom border line of the input area highlighted
  • The field is getting in edit mode by clicking on it’s input area
  • When the field is edited it is not automatically saved.
  • There are three ways how the updated values are saved:
    • The user can manually save them by clicking on the button “Save”. The button is represented with an icon and text shown on hover
    • The user clicks on any workflow button in the view, for example “Confirm” button in the sales order
    • The user exits the view. Even when the user closes the window or the browser if there is content that is not saved will be stored in the database. One exception from this rule is when the changes are done in a private window which is closed without saving the changes with any of the previous two steps.

Removing the edit button makes it easier to update changes on the fly, yet it can be confusing sometimes if the changes are saved or not because the user does not have any indication for that, except for very small icons, Save / Discard, at the top of the screen. So in case if a user works in a multi-window setup, they may be surprised why they don’t see the changes in all windows.
However, even though this way of edit may increase the learning curve for the new users, in the long run makes the interaction with the system more convenient.

Functional Odoo 17 Odoo 18
Posts

In Odoo can be found multiple different methods for filtering and searching records. Each of them is designed with their own uniqueness which makes it more useful in certain cases than others, even though sometimes it may be confusing which one is the right to use. In this post we will go through some of the most used lookup methods and focus on when each of them should be used.

We will cover the following methods:

  • browse
  • search
  • filtered
  • filtered_domain

Browse

The method browse​ returns a recordset for the provided IDs​ in the current environment. In case the method is called without any ID​ it will return an empty recordset. This method creates recordset for the IDs without fetching the actual data immediately from the database. It is useful to use when list of IDs need to be converted into recordset.

Search

The search​ method can accept multiple arguments:

  • domain​: A list of domains to be searched by. An empty list will return all records
  • offset​: Number of results to ignore. Usually used during page pagination
  • limit​: Maximum number of results to return
  • order​: Specified order of records

This method based on the provided arguments creates a search query that is executed in the database and the result from it are stored in memory.

The return from this method is recordset as well. The difference between this recordset and the one from the browse​ method is that this method is doing immediate query to the database and retrieves the data for the selected records, which means when a field is accessed, the value is taken from the memory instead of doing read in the database. The search method has a few additional optimization methods such as search_count​, search_read​ and search_fetch​.

Filtered

The filtered​ ​method as an argument accepts a function or a dot-separated sequence of field names. This method filters records on an already existing recordset, which is one of the differences compared to the previous two methods. The filtering in this method is done on python level and does not use database queries. 

Filtered Domain

Similarly to the filtered​ method, the filtering is done on the existing recordset. The difference is that in this case the argument is a domain, the same format as the one for search​, instead of a function. Depending on the complexity of the domain, the method may execute an actual search to do the proper filtering. However in most of the cases it filters the records in python by evaluating the domain. This is good in case the recordset is already fetched from the database and it is needed some additional filtering which is easier to express in a domain format.

Conclusion

To sum up, the browse​ method is used when there is list of IDs​ but we need a recordset for more convenient handling in the rest of the codebase. 
In case we have some parameters on base of which we need to query the database and get recordset then search​ method is the one that should be used. 
However, if we already have the recordset and need to do some additional filtering on it the filtered​ and filtered_domain​ methods are the right choice. Which one of these methods needs to be used mostly depends on the conditions that need to apply. In case of a simpler condition filtered would be more convenient to use but in case the conditions are more complex can be easier to translate them in a domain format and use filtered_domain​.

These suggestions are based on the assumption that the amount of data handling is moderate. In case of complex domains and bigger amounts of data may need to be done additional evaluation which method would perform the best for such case.


Odoo 17 Odoo 18 Technical
Posts