@@ -72,15 +72,14 @@ that computed fields remain consistent.
72
72
product.breadcrumb = f " { category.name} / { product.name} "
73
73
74
74
.. note ::
75
-
76
75
- Compute methods are referenced using their names as strings in the `compute ` field argument,
77
76
rather than directly linking the method object. This allows placing them after the field
78
77
declaration.
79
78
- Model methods should be private :dfn: `prefixed with an underscore ` to keep them hidden from
80
79
the :doc: `external API <../../reference/external_api >`.
81
80
- Numeric field values default to `0 ` when not explicitly set.
82
81
- A compute method can depend on another computed field.
83
- - Field values for related models can be accessed via the `Many2one `, `One2many `, or
82
+ - Field values for related models can be accessed via their `Many2one `, `One2many `, or
84
83
`Many2many ` field.
85
84
- Variables used for relational field values are typically not suffixed with `_id ` or `_ids `.
86
85
While the field itself represents the stored ID(s), the variable holds the corresponding
@@ -93,7 +92,7 @@ that computed fields remain consistent.
93
92
- :ref: `Coding guidelines on naming and ordering the members of model classes
94
93
<contributing/coding_guidelines/model_members>`
95
94
96
- Our real estate models can benefit from several computed fields to automate common calculations.
95
+ Our real estate models could benefit from several computed fields to automate common calculations.
97
96
Let's implement them.
98
97
99
98
.. exercise ::
@@ -576,23 +575,130 @@ the :code:`@api.onchange()` decorator. These methods are triggered when the spec
576
575
are altered. They operate on the in-memory representation of a single-record recordset received
577
576
through `self `. If field values are modified, the changes are automatically reflected in the UI.
578
577
579
- .. todo: explain the env (self.env.uid, self.env.user, self.env.ref(xml_id), self.env[model_name])
578
+ .. example ::
579
+ In the following example, onchange methods are implemented to:
580
+
581
+ - unpublish products when all sellers are removed;
582
+ - warn the user if changing the sales price would result in a negative margin;
583
+ - raise a blocking user error if the category is changed after sales have been made.
584
+
585
+ .. code-block :: python
586
+
587
+ from odoo import _, api, fields, models
588
+ from odoo.exceptions import UserError
589
+
590
+
591
+ class Product (models .Model ):
592
+ is_published = fields.Boolean(string = " Published" )
593
+
594
+ @api.onchange (' seller_ids' )
595
+ def _onchange_seller_ids_unpublish_if_no_sellers (self ):
596
+ if not self .seller_ids:
597
+ self .is_published = False
598
+
599
+ @api.onchange (' price' )
600
+ def _onchange_price_warn_if_margin_is_negative (self ):
601
+ if self .margin < 0 :
602
+ return {
603
+ ' warning' : {
604
+ ' title' : _(" Warning" ),
605
+ ' message' : _(
606
+ " The sales price was changed from %(before_price)s to %(new_price)s , which"
607
+ " would result in a negative margin. A sales price of minimum %(min_price)s "
608
+ " is recommended." ,
609
+ before_price = self ._origin.price, new_price = self .price, min_price = self .cost,
610
+ ),
611
+ }
612
+ }
613
+
614
+ @api.onchange (' category_id' )
615
+ def _onchange_category_id_block_if_existing_sales (self ):
616
+ existing_sales = self .env[' sales.order' ].search([(' product_id' , ' =' , self ._origin.id)])
617
+ if existing_sales:
618
+ raise UserError(_(
619
+ " You cannot change the category of a product that has already been sold; unpublish"
620
+ " it instead."
621
+ ))
622
+
623
+ .. note ::
624
+ - It is recommended to give self-explanatory names to onchange methods as multiple onchange
625
+ methods can be defined for a single field.
626
+ - Onchange methods don't need to iterate over the records as `self ` is always a recordset of
627
+ length 1.
628
+ - The :code: `_() ` function from the `odoo ` package marks display strings :dfn: `strings shown
629
+ to the user and denoted with double quotes ` for translation.
630
+ - Regular string interpolation isn't possible withing the translation function. Instead,
631
+ values to interpolate are passed as either positional arguments when using the :code: `%s `
632
+ format, or as keyword arguments when using the :code: `%(name)s ` format.
633
+ - The `_origin ` model attribute refers to the original record before user modifications.
634
+ - The `env ` model attribute is an object that allows access to other models and their classes.
635
+ - The `search ` method can be used to query a model for records matching a given search domain.
636
+ - In onchanges methods, the `
10000
id ` attribute cannot be used to directly access the record's ID.
637
+ - Blocking user errors are raised as exceptions.
580
638
581
639
.. seealso ::
582
640
- Reference documentation for the :meth: `@api.onchange() <odoo.api.onchange> ` decorator
641
+ - :doc: `How-to guide on translations </developer/howtos/translations >`
583
642
- Reference documentation for the :class: `UserError <odoo.exceptions.UserError> ` exception
643
+ - :ref: `Reference documentation for the environment object <reference/orm/environment >`
644
+ - Reference documentation for the :meth: `search <odoo.models.Model.search> ` method
584
645
585
- .. todo: tip ref translation
586
-
587
- .. todo: raise UserError + translation
588
- .. todo: note: mention that the method is public so it can be called directly by the client.
589
- always return something in public methods as they are part of the :ref:external API and can be called through XML-RPC
646
+ In our real estate app, data entry could be more intuitive and efficient. Let's use onchange methods
647
+ to automate updates and guide users as they edit data.
590
648
591
649
.. exercise ::
592
- tmp
650
+ #. Set the garden area to zero if the garden checkbox is unchecked.
651
+ #. Set the garden checkbox to checked if the garden area is set.
652
+ #. Display a non-blocking warning if the garden area is set to zero and the garden checkbox is
653
+ unchecked.
654
+ #. Prevent archiving a property that has **pending ** offers.
655
+
656
+ .. spoiler :: Solution
657
+
658
+ .. code-block :: python
659
+ :caption: `real_estate_property.py`
660
+ :emphasize- lines: 1 - 2 , 9 - 40
661
+
662
+ from odoo import _, api, fields, models
663
+ from odoo.exceptions import UserError
664
+ from odoo.tools import date_utils
593
665
594
- .. todo: if garden unchecked -> set garden area to zero
595
- .. todo: if write in garden area -> set garden checked
666
+
667
+ class RealEstateProperty (models .Model ):
668
+ [... ]
669
+
670
+ @api.onchange (' active' )
671
+ def _onchange_active_block_if_existing_offers (self ):
672
+ if not self .active:
673
+ existing_offers = self .env[' real.estate.offer' ].search(
674
+ [(' property_id' , ' =' , self ._origin.id), (' state' , ' =' , ' waiting' )]
675
+ )
676
+ if existing_offers:
677
+ raise UserError(
678
+ _(" You cannot change the active state of a property that has pending offers." )
679
+ )
680
+
681
+ @api.onchange (' has_garden' )
682
+ def _onchange_has_garden_set_garden_area_to_zero_if_unchecked (self ):
683
+ if not self .has_garden:
684
+ self .garden_area = 0
685
+
686
+ @api.onchange (' garden_area' )
687
+ def _onchange_garden_area_uncheck_garden_if_zero (self ):
688
+ if self .garden_area and not self .has_garden:
689
+ self .has_garden = True
690
+
691
+ @api.onchange (' garden_area' )
692
+ def _onchange_garden_area_display_warning_if_zero_and_unchecked (self ):
693
+ if not self .garden_area and self .has_garden:
694
+ return {
695
+ ' warning' : {
696
+ ' title' : _(" Warning" ),
697
+ ' message' : _(
698
+ " The garden area was set to zero, but the garden checkbox is checked."
699
+ ),
700
+ }
701
+ }
596
702
597
703
.. _tutorials/server_framework_101/constraints :
598
704
@@ -692,6 +798,9 @@ buttons can be of type **action**, defined in XML, or **object**, implemented in
692
798
Together, these types of buttons facilitate the integration of user interactions with business
693
799
logic.
694
800
801
+ .. todo: note: mention that the method is public so it can be called directly by the client.
802
+ always return something in public methods as they are part of the :ref:external API and can be called through XML-RPC
803
+
695
804
.. todo: def create of offer -> write state of the property to offer received
696
805
.. todo: def unlink: _unlink_if_state_is_valid (new or cancelled)
697
806
0 commit comments