@@ -907,7 +907,10 @@ perform custom validation and raise blocking validation errors if the constraint
907
907
@api.constrains (' amount' )
908
908
def _check_amount_higher_than_previous_offers (self ):
909
909
for offer in self :
910
- if offer.amount < max (offer.property_id.offer_ids.mapped(' amount' )):
910
+ same_buyer_offers = offer.property_id.offer_ids.filtered(
911
+ lambda o : o.buyer_id == offer.buyer_id
912
+ )
913
+ if offer.amount < max (same_buyer_offers.mapped(' amount' )):
911
914
raise ValidationError(_(
912
915
" The amount of the new offer must be higher than the amount of the previous "
913
916
" offers."
@@ -1014,14 +1017,6 @@ fields with default values.
1014
1017
Trigger business workflows
1015
1018
==========================
1016
1019
1017
- **Action buttons ** allow users to trigger specific workflows directly from the user interface. These
1018
- buttons can be of type **action **, defined in XML, or **object **, implemented in the model.
1019
- Together, these types of buttons facilitate the integration of user interactions with business
1020
- logic.
1021
-
1022
- .. todo: note: mention that the method is public so it can be called directly by the client.
1023
- always return something in public methods as they are part of the :ref:external API and can be called through XML-RPC
1024
-
1025
1020
.. _tutorials/server_framework_101/crud_methods :
1026
1021
1027
1022
CRUD methods
@@ -1126,9 +1121,10 @@ accessed directly using :code:`record.field`.
1126
1121
1127
1122
def unlink (self ):
1128
1123
for offer in self :
1124
+ property_offers = offer.property_id.offer_ids
1129
1125
if (
1130
1126
offer.property_id.state in (' offer_received' , ' under_option' )
1131
- and len (offer.property_id.offer_ids) == 1 # The current offer is the last one .
1127
+ and not (property_offers - self ) # All the property's offers are being deleted .
1132
1128
):
1133
1129
offer.property_id.state = ' new'
1134
1130
return super ().unlink()
@@ -1165,46 +1161,175 @@ accessed directly using :code:`record.field`.
1165
1161
property .address_id = address.id
1166
1162
return res
1167
1163
1168
- .. _tutorials/server_framework_101/action_type_actions :
1164
+ .. _tutorials/server_framework_101/xml_actions :
1165
+
1166
+ XML actions
1167
+ -----------
1168
+
1169
+ **Action buttons ** allow users to trigger workflows directly from the user interface. The simplest
1170
+ type of action button is **action **. These buttons are linked to actions defined in XML and are
1171
+ typically used to open specific views or trigger server actions. These buttons allow developers to
1172
+ link workflows to the UI without needing to write Python code, making them ideal for simple,
1173
+ preconfigured tasks.
1174
+
1175
+ We have already seen how to :ref: `link menu items to XML-defined window actions
1176
+ <tutorials/server_framework_101/define_window_actions>`. To link a **button ** to an XML-defined
1177
+ action, a `button ` element must be added to the view, with its `type ` attribute set to `action `. The
1178
+ `name ` attribute should reference the XML ID of the action to be executed, following the format
1179
+ `%(XML_ID)d `.
1180
+
1181
+ .. example ::
1182
+ In the following example, a button is added to the product form view to display all products in
1183
+ the same category.
1169
1184
1170
- XML-defined actions
1171
- -------------------
1185
+ .. code-block :: xml
1172
1186
1173
- Action-type buttons link to actions defined in XML and are typically used to display specific views
1174
- or trigger server actions. These buttons allow developers to link workflows to the UI without
1175
- writing Python code, making them ideal for simple, preconfigured tasks.
1187
+ <form >
1188
+ <sheet >
1189
+ <div name =" button_box" >
1190
+ <button
1191
+ string =" Similar Products"
1192
+ type =" action"
1193
+ name =" product.view_products_action"
1194
+ context =" {'search_default_category_id': category_id.id, 'create': False, 'edit': False}"
1195
+ />
1196
+ </div >
1197
+ </sheet >
1198
+ </form >
1176
1199
1177
- We already saw :ref: `how to link XML-defined window actions to menu items
1178
- <tutorials/server_framework_101/define_window_actions>`. To link a button to an XML-defined action,
1179
- a `button ` element must be added to the view, with its `type ` attribute set to `action `. The `name `
1180
- attribute should reference the XML ID of the action to execute.
1200
+ .. note ::
1201
+ - The button is placed at the top of the form view by using a button container (`button_box `).
1202
+ - The `context ` attribute is used to:
1203
+
1204
+ - Filter the products to display only those in the same category as the current product.
1205
+ - Prevent users from creating or editing products when browsing them through the button.
1206
+
1207
+ .. seealso ::
1208
+ Reference documentation for :ref: `button containers
1209
+ <reference/view_architectures/form/button_container>`.
1181
1210
1182
1211
.. exercise ::
1212
+ Replace the property form view's :guilabel: `Offers ` notebook page with a **stat button **. This
1213
+ button should:
1214
+
1215
+ - Be placed at the top of the property form view.
1216
+ - Display the total number of offers for the property.
1217
+ - Use a relevant icon.
1218
+ - Allow users to browse offers in list and form views.
1219
+
1183
1220
.. tip ::
1184
- Rely on the reference documentation for :ref: `action buttons
1185
- <reference/view_architectures/form/button>` and :ref: `headers
1186
- <reference/view_architectures/form/header>` in form views.
1221
+ - Rely on the reference documentation for :ref: `action buttons
1222
+ <reference/view_architectures/form/button>` in form views.
1223
+ - Find icon codes (`fa-<something> `) in the `Font Awesome v4 catalog
1224
+ <https://fontawesome.com/v4/icons/> `_.
1225
+ - Ensure that your count computations :ref: `scale with the number of records to process
1226
+ <performance/good_practices/batch>`.
1227
+ - Assign the `default_<field> ` context key to a button to define default values when creating
1228
+ new records opened through that button.
1187
1229
1188
- .. todo: "view offers" statbutton with count + remove notebook page of offers
1230
+ .. spoiler :: Solution
1189
1231
1190
- .. _tutorials/server_framework_101/object_type_actions :
1232
+ .. code-block :: python
1233
+ :caption: `real_estate_property.py`
1234
+ :emphasize- lines: 4 ,8 - 15
1191
1235
1192
- Model-defined actions
1193
- ---------------------
1236
+ offer_ids = fields.One2many(
1237
+ string = " Offers" , comodel_name = ' real.estate.offer' , inverse_name = ' property_id'
1238
+ )
1239
+ offer_count = fields.Integer(string = " Offer Count" , compute = ' _compute_offer_count' )
1240
+
1241
+ [... ]
1242
+
1243
+ @api.depends (' offer_ids' )
1244
+ def _compute_offer_count (self ):
1245
+ offer_data = self .env[' real.estate.offer' ]._read_group(
1246
+ [(' property_id' , ' in' , self .ids)], groupby = [' property_id' ], aggregates = [' __count' ],
1247
+ )
1248
+ property_data = {property .id: count for property , count in offer_data}
1249
+ for property in self :
1250
+ property .offer_count = property_data.get(property .id, 0 )
1251
+
1252
+ .. code-block :: xml
1253
+ :caption: `real_estate_offer_views.xml`
1254
+ :emphasize-lines: 1-10
1255
+
1256
+ <record id =" real_estate.view_offers_action" model =" ir.actions.act_window" >
1257
+ <field name =" name" >Offers</field >
1258
+ <field name =" res_model" >real.estate.offer</field >
1259
+ <field name =" view_mode" >list,form</field >
1260
+ <field name =" help" type =" html" >
1261
+ <p class =" o_view_nocontent_smiling_face" >
1262
+ Create a new offer.
1263
+ </p >
1264
+ </field >
1265
+ </record >
1266
+
1267
+ .. code-block :: python
1268
+ :caption: `__manifest__.py`
1269
+ :emphasize- lines: 1
1270
+
1271
+ ' views/real_estate_property_views.xml' , # Depends on `real_estate_offer_views.xml`.
1272
+
1273
+ .. code-block :: xml
1274
+ :caption: `real_estate_property_views.xml`
1275
+ :emphasize-lines: 2-12
1276
+
1277
+ <sheet >
1278
+ <div name =" button_box" class =" oe_button_box" >
1279
+ <button
1280
+ string =" Offers"
1281
+ icon =" fa-handshake-o"
1282
+ type =" action"
1283
+ name =" real_estate.view_offers_action"
1284
+ context =" {'default_property_id': id}"
1285
+ >
1286
+ <field string =" Offers" name =" offer_count" widget =" statinfo" />
1287
+ </button >
1288
+ </div >
1289
+ [...]
1290
+
1291
+ .. _tutorials/server_framework_101/model_actions :
1292
+
1293
+ Model actions
1294
+ -------------
1194
1295
1195
- Object-type buttons link to model methods that execute custom business logic. These methods enable
1196
- more complex workflows, such as processing the current records, configuring actions depending on
1197
- these records, or integrating with external systems.
1296
+ Another, more versatile type of action button is **object **. These buttons are linked to model
1297
+ methods that execute custom business logic. These methods enable more complex workflows, such as
1298
+ processing the current records, configuring client actions depending on these records, or
1299
+ integrating with external systems.
1198
1300
1199
1301
To link a button to a model-defined action, its `type ` attribute must be set to `object `, and its
1200
1302
`name ` attribute must be set to the name of the model method to call when the button is clicked. The
1201
1303
method receives the current recordset through `self ` and should return a dictionary acting as an
1202
1304
action descriptor.
1203
1305
1306
+ .. example ::
1307
+ In the following example,
1308
+
1309
+ .. note ::
1310
+ - Action methods should be public :dfn: `not prefixed with an underscore ` to make them callable
1311
+ by the client. Such methods should always return something as they are automatically part of
1312
+ the :doc: `external API <../../reference/external_api >`.
1313
+
1314
+ .. exercise ::
1315
+ #. tmp ... in the header.
1316
+
1317
+ .. tip ::
1318
+ - Rely on the reference documentation for :ref: `headers
1319
+ <reference/view_architectures/form/header>` in form views.
1320
+
1204
1321
.. todo: accept/refuse offer buttons -> auto refuse others when accepting (write)
1205
1322
.. todo: multi-checkbox refuse offers in bulk
1206
1323
.. todo: "assign myself as salesperson" action
1207
1324
1325
+ .. spoiler :: Solution
1326
+
1327
+ .. code-block :: python
1328
+ :caption: `real_estate_property.py`
1329
+ :emphasize- lines: 1
1330
+
1331
+ [... ]
1332
+
1208
1333
----
1209
1334
1210
1335
.. todo: add incentive for chapter 6