From 32a250b7812d66a54706b083e5feed5d82059bbd Mon Sep 17 00:00:00 2001 From: dhvag-odoo Date: Wed, 31 Dec 2025 23:26:14 +0530 Subject: [PATCH 1/9] =?UTF-8?q?[ADD]=20estate=20module:=20initial=20model?= =?UTF-8?q?=20structure=20(Chapters=201=E2=80=933)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Set up the estate module based on the Odoo 19 Server Framework 101 tutorial - Added estate.property model with proper naming conventions - Implemented basic fields as introduced in chapters 1–3 - Kept the code aligned with Odoo ORM standards --- estate/__init__.py | 1 + estate/__manifest__.py | 10 ++++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 20 ++++++++++++++++++++ 4 files changed, 32 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..81e7dd93962 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,10 @@ +{ + "name": "Estate", + "version": "1.0", + "category": "Tutorial", + "author": "Dhrudeep", + "description": "Real estate property management", + "depends": ["base"], + "data": [], + "application": True, +} \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..f4c8fd6db6d --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..00cd7ecc059 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,20 @@ +from odoo import fields, models + +class EstateProperty(models.Model): + _name = "estate.property" + _description = 'Estate Property Table Fields' + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float(required=True) + selling_price = fields.Float() + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + selection=[('n', 'North'), ('w', 'West'), ('e', 'East'), ('s ', 'South')]) From ca94372a611141328bd1a37c6be06be7fb14d250 Mon Sep 17 00:00:00 2001 From: dhvag-odoo Date: Thu, 1 Jan 2026 18:29:08 +0530 Subject: [PATCH 2/9] [ADD] estate module: Setup and Checkpoint Validation (Chapters 4-6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Continued the estate module implementation by completing Chapters 4–6 - Added and configured views, actions, and menus to make the estate.property model fully accessible from the UI - Implemented list and form, views, filter, group-by - Added required defaults and the active field - Verified that all earlier checkpoints (Chapters 1–3) are still satisfied and working as expected - Overall implementation remains consistent with Odoo ORM guidelines and framework best practices --- estate/__init__.py | 2 +- estate/__manifest__.py | 17 ++++-- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 44 ++++++++----- estate/security/ir.model.access.csv | 2 + estate/views/estate_menus.xml | 8 +++ estate/views/estate_property_views.xml | 85 ++++++++++++++++++++++++++ 7 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 estate/security/ir.model.access.csv create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__init__.py b/estate/__init__.py index 9a7e03eded3..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1 @@ -from . import models \ No newline at end of file +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 81e7dd93962..7f72c6b712b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,10 +1,17 @@ { "name": "Estate", "version": "1.0", - "category": "Tutorial", - "author": "Dhrudeep", - "description": "Real estate property management", "depends": ["base"], - "data": [], + "author": "Dhrudeep", + "description": """ + This module provides functionality to manage real estate properties. + """, + "data": [ + "security/ir.model.access.csv", + "views/estate_property_views.xml", + "views/estate_menus.xml", + ], + "category": "Tutorial", + "license": "LGPL-3", "application": True, -} \ No newline at end of file +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f4c8fd6db6d..5e1963c9d2f 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property \ No newline at end of file +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 00cd7ecc059..da32d9aba13 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,20 +1,36 @@ from odoo import fields, models + class EstateProperty(models.Model): _name = "estate.property" - _description = 'Estate Property Table Fields' + _description = "Estate Property Planning" - name = fields.Char(required=True) - description = fields.Text() - postcode = fields.Char() - date_availability = fields.Date() - expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() - living_area = fields.Integer() - facades = fields.Integer() - garage = fields.Boolean() - garden = fields.Boolean() - garden_area = fields.Integer() + name = fields.Char("Name", required=True, default="Unknown") + description = fields.Text("Description") + postcode = fields.Char("Postcode") + date_availability = fields.Date( + "Available From", copy=False, default=fields.Datetime.now + ) + expected_price = fields.Float("Expected Pice", required=True) + selling_price = fields.Float("Selling Price", readonly=True, copy=False) + bedrooms = fields.Integer("Bedrooms", default=2) + living_area = fields.Integer("Living Area") + facades = fields.Integer("Facades") + garage = fields.Boolean("Garage") + garden = fields.Boolean("Garden") + garden_area = fields.Integer("Garden Area") garden_orientation = fields.Selection( - selection=[('n', 'North'), ('w', 'West'), ('e', 'East'), ('s ', 'South')]) + string="Garden Orientation", + selection=[("n", "North"), ("w", "West"), ("e", "East"), ("s", "South")], + ) + active = fields.Boolean("Active", default=True) + state = fields.Selection( + selection=[ + ("new", "New"), + ("offer received", "Offer Received"), + ("offer Accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), + ], + default="new", + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..cb6f39fe7b6 --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_estate_model,access_estate_model,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..92d138e5ff1 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..5fd8e83fcc2 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,85 @@ + + + + + estate_list + estate.property + + + + + + + + + + + + + + + estate_form + estate.property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + its default value set to ‘New’. +
+
+
+
+ + + estate_search_view + estate.property + + + + + + + + + + + + + + + + + + + + Properties + estate.property + list,form + +
+
\ No newline at end of file From f8ae7c65af2b064f2f0698a00beff4f9d7bc6e01 Mon Sep 17 00:00:00 2001 From: dhvag-odoo Date: Fri, 2 Jan 2026 16:30:32 +0530 Subject: [PATCH 3/9] [ADD] estate module: Implement relations between estate models (Chapter 7) - Added relational fields (Many2one, One2many, Many2many) to the estate module - Linked properties with types, offers, tags, buyer, and salesperson - Created required views for managing offers within the property form - Updated access rights for new models --- estate/__manifest__.py | 3 + estate/models/__init__.py | 3 + estate/models/estate_property.py | 7 ++ estate/models/estate_property_offer.py | 16 ++++ estate/models/estate_property_tag.py | 9 +++ estate/models/estate_property_type.py | 8 ++ estate/security/ir.model.access.csv | 5 +- estate/views/estate_menus.xml | 13 +++- estate/views/estate_property_offer_views.xml | 22 ++++++ estate/views/estate_property_tag_views.xml | 24 ++++++ estate/views/estate_property_type_views.xml | 39 ++++++++++ estate/views/estate_property_views.xml | 81 ++++++++++++-------- 12 files changed, 193 insertions(+), 37 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_offer_views.xml create mode 100644 estate/views/estate_property_tag_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 7f72c6b712b..11d80e1e86f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,6 +9,9 @@ "data": [ "security/ir.model.access.csv", "views/estate_property_views.xml", + "views/estate_property_type_views.xml", + "views/estate_property_tag_views.xml", + "views/estate_property_offer_views.xml", "views/estate_menus.xml", ], "category": "Tutorial", diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index da32d9aba13..35ccaf89805 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -34,3 +34,10 @@ class EstateProperty(models.Model): ], default="new", ) + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + seller = fields.Many2one( + "res.users", string="Salesman", default=lambda self: self.env.user + ) + buyer = fields.Many2one("res.partner", string="Buyer", copy=False) + tags = fields.Many2many("estate.property.tag") + offer = fields.One2many("estate.property.offer", "property_id") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..4e635ff1a2d --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,16 @@ +from odoo import fields, models +from odoo.orm.fields_selection import Selection + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Estate Property offer" + + price = fields.Float("Price") + status = fields.Selection( + string="Status", + copy=False, + selection=[("accepted", "Accepted"), ("refused", "Refused")], + ) + partner_id = fields.Many2one("res.partner", string="Partner", required=True) + property_id = fields.Many2one("estate.property", string="Property", required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..4a70e5e6972 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Estate Property Tag" + + name = fields.Char("Name", required=True) + color = fields.Integer("Color Index") diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..54459e3c267 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Estate Property Type" + + name = fields.Char("Name", required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index cb6f39fe7b6..8ece7a37e1c 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -access_estate_model,access_estate_model,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_model,access_estate_model,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 92d138e5ff1..70f6f32136b 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,8 +1,13 @@ - + - - - + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..f7255e87627 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,22 @@ + + + + + estate_property_offer + estate.property.offer + + + + + + + + + + + Property Tags + estate.property.offer + list,form + + + \ No newline at end of file diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..0fbec65193e --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,24 @@ + + + + + estate_property_tag_form + estate.property.tag + +
+ + + + + +
+
+
+ + + Property Tags + estate.property.tag + list,form + +
+
\ No newline at end of file diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..eca2bba1a3b --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,39 @@ + + + + + estate_property_type_views_form + estate.property.type + +
+ +

+ +

+
+
+
+
+ + + estate_type_search_view + estate.property.type + + + + + + + + + + + + + + Property Types + estate.property.type + list,form + +
+
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5fd8e83fcc2..c968b7a1679 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,22 +1,24 @@ - + + estate_list estate.property - - - - - - - + + + + + + + + estate_form estate.property @@ -24,32 +26,46 @@

- +

- - + + + + - - + + - - - - - - - - + + + + + + + + - its default value set to ‘New’. + + + + + + + + + + + +
@@ -60,16 +76,17 @@ estate.property - - - - - - - - + + + + + + + + - + @@ -82,4 +99,4 @@ list,form
-
\ No newline at end of file + \ No newline at end of file From 41b6a55ee89d437b9cac9eb5962eb57ee6f70b02 Mon Sep 17 00:00:00 2001 From: dhvag-odoo Date: Fri, 2 Jan 2026 17:01:40 +0530 Subject: [PATCH 4/9] [FIX] estate module: fixed the import issue - Fixed the import issue in estate_property_offer file. --- estate/models/estate_property_offer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 4e635ff1a2d..ab61ab6aa65 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,4 @@ from odoo import fields, models -from odoo.orm.fields_selection import Selection class EstatePropertyOffer(models.Model): From 48db2c6373b9fb56a0d93fdde88052ff79896a21 Mon Sep 17 00:00:00 2001 From: dhvag-odoo Date: Sat, 3 Jan 2026 23:47:36 +0530 Subject: [PATCH 5/9] [FIX] estate module: Fixed review comments - Applied the changes suggested during code review - Updated XML view structure and corrected attribute order - Cleaned up naming issues in views and filters - Adjusted default values and selection field keys - Removed unnecessary field strings in the model - Added new line in the file ir.model.access.csv --- estate/models/estate_property.py | 68 ++++--- estate/models/estate_property_offer.py | 10 +- estate/models/estate_property_tag.py | 4 +- estate/models/estate_property_type.py | 4 +- estate/security/ir.model.access.csv | 2 +- estate/views/estate_menus.xml | 40 +++- estate/views/estate_property_offer_views.xml | 25 ++- estate/views/estate_property_tag_views.xml | 8 +- estate/views/estate_property_type_views.xml | 13 +- estate/views/estate_property_views.xml | 181 ++++++++++--------- 10 files changed, 206 insertions(+), 149 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 35ccaf89805..021b9972c46 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,43 +1,61 @@ -from odoo import fields, models +from odoo import api, fields, models class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property Planning" - name = fields.Char("Name", required=True, default="Unknown") - description = fields.Text("Description") - postcode = fields.Char("Postcode") + name = fields.Char(required=True, default="Unknown") + description = fields.Text() + postcode = fields.Char() date_availability = fields.Date( "Available From", copy=False, default=fields.Datetime.now ) - expected_price = fields.Float("Expected Pice", required=True) - selling_price = fields.Float("Selling Price", readonly=True, copy=False) - bedrooms = fields.Integer("Bedrooms", default=2) - living_area = fields.Integer("Living Area") - facades = fields.Integer("Facades") - garage = fields.Boolean("Garage") - garden = fields.Boolean("Garden") - garden_area = fields.Integer("Garden Area") + expected_price = fields.Float(required=True) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() garden_orientation = fields.Selection( - string="Garden Orientation", - selection=[("n", "North"), ("w", "West"), ("e", "East"), ("s", "South")], + selection=[ + ('north', "North"), + ('west', "West"), + ('east', "East"), + ('south', "South"), + ], ) - active = fields.Boolean("Active", default=True) + active = fields.Boolean(default=True) state = fields.Selection( selection=[ - ("new", "New"), - ("offer received", "Offer Received"), - ("offer Accepted", "Offer Accepted"), - ("sold", "Sold"), - ("cancelled", "Cancelled"), + ('new', "New"), + ('offer_received', "Offer Received"), + ('offer_Accepted', "Offer Accepted"), + ('sold', "Sold"), + ('cancelled', "Cancelled"), ], default="new", ) - property_type_id = fields.Many2one("estate.property.type", string="Property Type") + property_type_id = fields.Many2one('estate.property.type', string="Property Type") seller = fields.Many2one( - "res.users", string="Salesman", default=lambda self: self.env.user + 'res.users', string="Salesman", default=lambda self: self.env.user ) - buyer = fields.Many2one("res.partner", string="Buyer", copy=False) - tags = fields.Many2many("estate.property.tag") - offer = fields.One2many("estate.property.offer", "property_id") + buyer = fields.Many2one('res.partner', string="Buyer", copy=False) + tags = fields.Many2many('estate.property.tag') + offer = fields.One2many('estate.property.offer', 'property_id') + total_area = fields.Float(compute='_compute_total') + best_price = fields.Float("Best offer", compute='_compute_best_price') + + @api.depends('living_area', 'garden_area') + def _compute_total(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + @api.depends('offer') + def _compute_best_price(self): + for record in self: + record.best_price = ( + max(record.offer.mapped('price')) if record.offer else 0.0 + ) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index ab61ab6aa65..a0c9b610e4c 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -2,14 +2,14 @@ class EstatePropertyOffer(models.Model): - _name = "estate.property.offer" - _description = "Estate Property offer" + _name = 'estate.property.offer' + _description = 'Estate Property offer' price = fields.Float("Price") status = fields.Selection( string="Status", copy=False, - selection=[("accepted", "Accepted"), ("refused", "Refused")], + selection=[('accepted', "Accepted"), ('refused', "Refused")], ) - partner_id = fields.Many2one("res.partner", string="Partner", required=True) - property_id = fields.Many2one("estate.property", string="Property", required=True) + partner_id = fields.Many2one('res.partner', string="Partner", required=True) + property_id = fields.Many2one('estate.property', required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 4a70e5e6972..e1a112dc5b3 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -2,8 +2,8 @@ class EstatePropertyTag(models.Model): - _name = "estate.property.tag" - _description = "Estate Property Tag" + _name = 'estate.property.tag' + _description = 'Estate Property Tag' name = fields.Char("Name", required=True) color = fields.Integer("Color Index") diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 54459e3c267..9408784e953 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -2,7 +2,7 @@ class EstatePropertyType(models.Model): - _name = "estate.property.type" - _description = "Estate Property Type" + _name = 'estate.property.type' + _description = 'Estate Property Type' name = fields.Char("Name", required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 8ece7a37e1c..c7f5957e52e 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -2,4 +2,4 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink access_estate_model,access_estate_model,model_estate_property,base.group_user,1,1,1,1 access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 -access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 70f6f32136b..55e26fdcdab 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,13 +1,33 @@ - - - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index f7255e87627..0f169cad5d0 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,8 +1,10 @@ - - estate_property_offer + + + + estate_property_offer_view_list estate.property.offer @@ -13,10 +15,21 @@ - - Property Tags - estate.property.offer - list,form + + + estate_property_offer_view_form + estate.property.offer + +
+ + + + + + + +
+
\ No newline at end of file diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml index 0fbec65193e..2229ac29a96 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -1,8 +1,10 @@ - - estate_property_tag_form + + + + estate_property_tag_view_form estate.property.tag
@@ -15,7 +17,7 @@ - + Property Tags estate.property.tag list,form diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index eca2bba1a3b..6e4e8a230e3 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -1,8 +1,10 @@ - - estate_property_type_views_form + + + + estate_property_type_view_form estate.property.type @@ -15,8 +17,9 @@ - - estate_type_search_view + + + estate_property_type_view_search estate.property.type @@ -30,7 +33,7 @@ - + Property Types estate.property.type list,form diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index c968b7a1679..dbff8bb45b8 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,102 +1,103 @@ - - - - estate_list - estate.property - - - - - - - - - - - - + + + estate_view_list + estate.property + + + + + + + + + + + + - - - estate_form - estate.property - - - -

- -

+ + + estate_view_form + estate.property + + + +

+ +

+ + + + + + + + + + + + + + - - - - + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
+ + + + + + + + +
+ +
+
- - estate_search_view - estate.property - - - - - - - - - - - - - - - - + + + estate_view_search + estate.property + + + + + + + + + + + + + + + + - - Properties - estate.property - list,form - -
+ + Properties + estate.property + list,form +
\ No newline at end of file From def4d3d55f934415765b1cbde9e6f12a1647c8ee Mon Sep 17 00:00:00 2001 From: dhvag-odoo Date: Mon, 5 Jan 2026 22:59:45 +0530 Subject: [PATCH 6/9] [ADD] estate module: Computed Fields and Onchange (Chapter 8) - This chapter introduces computed fields to derive values automatically. - Field dependencies are managed using the @api.depends decorator. - Onchange methods react to user input in real time on forms. --- estate/__manifest__.py | 32 ++++----- estate/models/estate_property.py | 19 ++++-- estate/models/estate_property_offer.py | 20 +++++- estate/models/estate_property_tag.py | 2 +- estate/views/estate_menus.xml | 12 ++-- estate/views/estate_property_offer_views.xml | 19 +++--- estate/views/estate_property_tag_views.xml | 14 +++- estate/views/estate_property_type_views.xml | 10 +-- estate/views/estate_property_views.xml | 68 ++++++++++---------- 9 files changed, 120 insertions(+), 76 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 11d80e1e86f..f10b600fb02 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,20 +1,20 @@ { - "name": "Estate", - "version": "1.0", - "depends": ["base"], - "author": "Dhrudeep", - "description": """ + 'name': "Estate", + 'version': '1.0', + 'depends': ['base'], + 'author': 'Dhrudeep', + 'description': ''' This module provides functionality to manage real estate properties. - """, - "data": [ - "security/ir.model.access.csv", - "views/estate_property_views.xml", - "views/estate_property_type_views.xml", - "views/estate_property_tag_views.xml", - "views/estate_property_offer_views.xml", - "views/estate_menus.xml", + ''', + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_offer_views.xml', + 'views/estate_menus.xml', ], - "category": "Tutorial", - "license": "LGPL-3", - "application": True, + 'category': 'Tutorial', + 'license': 'LGPL-3', + 'application': True, } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 021b9972c46..8215cbf09a2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -2,8 +2,8 @@ class EstateProperty(models.Model): - _name = "estate.property" - _description = "Estate Property Planning" + _name = 'estate.property' + _description = 'Estate Property Planning' name = fields.Char(required=True, default="Unknown") description = fields.Text() @@ -26,19 +26,21 @@ class EstateProperty(models.Model): ('east', "East"), ('south', "South"), ], + help="Direction for the garden" ) active = fields.Boolean(default=True) state = fields.Selection( selection=[ ('new', "New"), ('offer_received', "Offer Received"), - ('offer_Accepted', "Offer Accepted"), + ('offer_accepted', "Offer Accepted"), ('sold', "Sold"), ('cancelled', "Cancelled"), ], default="new", ) - property_type_id = fields.Many2one('estate.property.type', string="Property Type") + property_type_id = fields.Many2one( + 'estate.property.type', string="Property Type") seller = fields.Many2one( 'res.users', string="Salesman", default=lambda self: self.env.user ) @@ -59,3 +61,12 @@ def _compute_best_price(self): record.best_price = ( max(record.offer.mapped('price')) if record.offer else 0.0 ) + + @api.onchange('garden') + def _onchange_garden(self): + if self.garden: + self.garden_area = "10" + self.garden_orientation = "north" + else: + self.garden_area = None + self.garden_orientation = None diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index a0c9b610e4c..ee7772eb599 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ -from odoo import fields, models +from odoo import api, fields, models +from dateutil.relativedelta import relativedelta class EstatePropertyOffer(models.Model): @@ -11,5 +12,20 @@ class EstatePropertyOffer(models.Model): copy=False, selection=[('accepted', "Accepted"), ('refused', "Refused")], ) - partner_id = fields.Many2one('res.partner', string="Partner", required=True) + partner_id = fields.Many2one( + 'res.partner', string="Partner", required=True) property_id = fields.Many2one('estate.property', required=True) + validity = fields.Integer(default=7) + date_deadline = fields.Date( + compute='_compute_date_deadline', inverse='_inverse_deadline') + + @api.depends('create_date', 'validity') + def _compute_date_deadline(self): + for offer in self: + date = offer.create_date.date() if offer.create_date else fields.Date.today() + offer.date_deadline = date + relativedelta(days=offer.validity) + + def _inverse_deadline(self): + for offer in self: + date = offer.create_date.date() if offer.create_date else fields.Date.today() + offer.validity = (offer.date_deadline - date).days diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index e1a112dc5b3..eb46edfe44c 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -5,5 +5,5 @@ class EstatePropertyTag(models.Model): _name = 'estate.property.tag' _description = 'Estate Property Tag' - name = fields.Char("Name", required=True) + name = fields.Char(required=True) color = fields.Integer("Color Index") diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 55e26fdcdab..a25516c89cd 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -2,32 +2,32 @@ + name="estate"/> + name="Advertisements"/> + action="estate_model_action"/> + name="Settings"/> + action="estate_property_type_action"/> + action="estate_property_tag_action"/> \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 0f169cad5d0..10d4ea88da1 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,6 +1,6 @@ - + @@ -8,13 +8,15 @@ estate.property.offer - - - + + + + - + + estate_property_offer_view_form @@ -23,9 +25,10 @@
- - - + + + +
diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml index 2229ac29a96..2c29583a299 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -10,13 +10,25 @@
- +
+ + estate_property_tag_view_list + estate.property.tag + + + + + + + + + Property Tags estate.property.tag diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index 6e4e8a230e3..6bbec2fa112 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -10,7 +10,7 @@

- +

@@ -23,11 +23,11 @@ estate.property.type - - - + + + - + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index dbff8bb45b8..07d8565e143 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -6,13 +6,15 @@ estate.property - - - - - - - + + + + + + + + +
@@ -32,38 +34,38 @@ - - + + - - - + + + - - - - - - - - - + + + + + + + + + - + - - + + @@ -78,17 +80,17 @@ estate.property - - - - - - - + + + + + + + + domain="['|',('state', '=', 'new'), ('state', '=', 'offer received')]"/> - + From fa2ac6b6442484cbafc3dd882d4e80143c4a7d39 Mon Sep 17 00:00:00 2001 From: dhvag-odoo Date: Wed, 7 Jan 2026 18:49:19 +0530 Subject: [PATCH 7/9] [ADD] estate module: Completed (Chapter 9 and 10) - Added accept and refuse actions for property offers - Handled the case where only one offer should be accepted - Updated property details like selling price and buyer when an offer is accepted - Learn to add python and SQL constraint --- estate/__manifest__.py | 2 +- estate/models/estate_property.py | 29 +++++++++++++ estate/models/estate_property_offer.py | 27 +++++++++++- estate/models/estate_property_tag.py | 21 +++++++++- estate/models/estate_property_type.py | 3 ++ estate/views/estate_menus.xml | 2 +- estate/views/estate_property_offer_views.xml | 44 ++++++++++---------- estate/views/estate_property_tag_views.xml | 17 ++++---- estate/views/estate_property_type_views.xml | 8 ++-- estate/views/estate_property_views.xml | 23 ++++++---- 10 files changed, 130 insertions(+), 46 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index f10b600fb02..17231e37e73 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,8 +8,8 @@ ''', 'data': [ 'security/ir.model.access.csv', - 'views/estate_property_views.xml', 'views/estate_property_type_views.xml', + 'views/estate_property_views.xml', 'views/estate_property_tag_views.xml', 'views/estate_property_offer_views.xml', 'views/estate_menus.xml', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8215cbf09a2..73768a7c04b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import api, fields, models +from odoo.exceptions import UserError, ValidationError class EstateProperty(models.Model): @@ -37,6 +38,7 @@ class EstateProperty(models.Model): ('sold', "Sold"), ('cancelled', "Cancelled"), ], + string="Status", default="new", ) property_type_id = fields.Many2one( @@ -50,6 +52,21 @@ class EstateProperty(models.Model): total_area = fields.Float(compute='_compute_total') best_price = fields.Float("Best offer", compute='_compute_best_price') + # SQL Constraint + _check_expected_price = models.Constraint( + 'CHECK(expected_price > 0)', "The expected price must be strictly positive") + _check_selling_price = models.Constraint( + 'CHECK(selling_price >= 0)', "The selling price must be positive") + + # Python Constriant + @api.constrains('selling_price', 'expected_price') + def _check_price(self): + if self.buyer and self.selling_price < (self.expected_price * 0.9): + raise ValidationError( + "The selling price must be at least 90% of the expected price! You must reduce the expected price if you want to accept this offer.") + + # Compute Methods + # Depends Decorator @api.depends('living_area', 'garden_area') def _compute_total(self): for record in self: @@ -62,6 +79,7 @@ def _compute_best_price(self): max(record.offer.mapped('price')) if record.offer else 0.0 ) + # Onchange Decorator @api.onchange('garden') def _onchange_garden(self): if self.garden: @@ -70,3 +88,14 @@ def _onchange_garden(self): else: self.garden_area = None self.garden_orientation = None + + # Action Methods + def action_sold(self): + if 'cancelled' in self.mapped('state'): + raise UserError("Cancelled properties cannot be sold.") + return self.write({'state': 'sold'}) + + def action_cancel(self): + if 'sold' in self.mapped('state'): + raise UserError("Sold properties cannot be cancelled.") + return self.write({'state': 'cancelled'}) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index ee7772eb599..c4711c495ba 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,8 @@ from odoo import api, fields, models from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError + class EstatePropertyOffer(models.Model): _name = 'estate.property.offer' @@ -8,7 +10,6 @@ class EstatePropertyOffer(models.Model): price = fields.Float("Price") status = fields.Selection( - string="Status", copy=False, selection=[('accepted', "Accepted"), ('refused', "Refused")], ) @@ -19,6 +20,12 @@ class EstatePropertyOffer(models.Model): date_deadline = fields.Date( compute='_compute_date_deadline', inverse='_inverse_deadline') + # SQL Constraint + _check_offer_price = models.Constraint( + 'CHECK(price > 0)', "The selling price must be positive") + + # Compute Methods + # Depends Decorator @api.depends('create_date', 'validity') def _compute_date_deadline(self): for offer in self: @@ -29,3 +36,21 @@ def _inverse_deadline(self): for offer in self: date = offer.create_date.date() if offer.create_date else fields.Date.today() offer.validity = (offer.date_deadline - date).days + + # Action Funcitons + def action_accept(self): + for record in self: + if record.property_id.state != 'offer_accepted': + record.status = 'accepted' + record.property_id.selling_price = record.price + record.property_id.buyer = self.partner_id + record.property_id.state = 'offer_accepted' + else: + raise UserError('One offer has already been accepted') + + def action_refuse(self): + for record in self: + record.status = 'refused' + record.property_id.state = 'new' + record.property_id.selling_price = '0' + record.property_id.buyer = None diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index eb46edfe44c..6eea50f49f0 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class EstatePropertyTag(models.Model): @@ -7,3 +7,22 @@ class EstatePropertyTag(models.Model): name = fields.Char(required=True) color = fields.Integer("Color Index") + + # SQL Constraint + _check_name = models.Constraint('unique(name)', "The name must be unique") + + local_datetime = fields.Char( + compute='_compute_local_datetime', store=True) + + # Copmute Method - Depends Decorator + @api.depends('create_date') + def _compute_local_datetime(self): + for record in self: + if record.create_date: + # user_tz = self.env.user.tz or 'Asia/Kolkata' + local_dt = fields.Datetime.context_timestamp( + record, record.create_date + ) + record.local_datetime = local_dt.strftime("%Y-%m-%d %H:%M:%S") + else: + record.local_datetime = False diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 9408784e953..4435a362014 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -6,3 +6,6 @@ class EstatePropertyType(models.Model): _description = 'Estate Property Type' name = fields.Char("Name", required=True) + + # SQL Constarint + _check_name = models.Constraint('unique(name)', "The name must be unique") diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index a25516c89cd..0897e0ed74c 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -30,4 +30,4 @@ id="estate_view_property_tag" parent="estate_menu_settings" action="estate_property_tag_action"/> -
\ No newline at end of file + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 10d4ea88da1..5a70d52ffaa 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -4,35 +4,37 @@ - estate_property_offer_view_list + estate.property.offer.view.list estate.property.offer +