From b41bda0a868f7edfaff7aebeaa84e70adc07501f Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Tue, 15 Sep 2026 11:09:28 +0200 Subject: [PATCH 01/17] [ADD] : Creation of the new module Module creation as requested in the tutorial --- estate/__init__.py | 0 estate/__manifest__.py | 5 +++++ 2 files changed, 5 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..edd1f00f71a --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,5 @@ +{ + 'name':'Real Estate', + 'version':'1.0', + 'depends':['base'] +} \ No newline at end of file From a6b3d7528842fb97b0ff85e094a9aac9d19473d6 Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Tue, 15 Sep 2026 11:56:50 +0200 Subject: [PATCH 02/17] [IMP] : Creation of new model property Relate to chapter 3 of the tutorial --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+) 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 index e69de29bb2d..9a7e03eded3 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ 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..c50b30d672c --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,24 @@ +from odoo import models, fields + +class EstateProperty(models.Model): + _name = 'estate.property' + _description = 'Real Estate property model' + + name = fields.Char('Name',required=True) + description = fields.Text('Description') + postcode = fields.Char('Postcode',required=True) + date_availability = fields.Date('Availability') + expected_price = fields.Float('Expected Price',required=True) + selling_price = fields.Float('Selling Price') + bedrooms = fields.Integer('Bedrooms') + 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( + string='Garden Orientation', + selection=[('North','North'),('South','South'),('East','East'),('West','West')] + ) + + From feee2dbddb3c97227b2567bc651309cce3fe4902 Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Tue, 15 Sep 2026 13:36:30 +0200 Subject: [PATCH 03/17] [IMP] : Added right for group_user Relate to chapter 4 of the tutorial --- estate/__manifest__.py | 3 ++- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index edd1f00f71a..94a560626b4 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,5 +1,6 @@ { 'name':'Real Estate', 'version':'1.0', - 'depends':['base'] + 'depends':['base'], + 'data':['security/ir.model.access.csv'] } \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..1eb8b98e9d7 --- /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,estate,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From e637945573b66dddb7413551a3513df0b9f3d990 Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Tue, 15 Sep 2026 15:13:35 +0200 Subject: [PATCH 04/17] [IMP] : Added views into the app As mentioned in the chapter 5 of the training --- estate/__manifest__.py | 7 ++++++- estate/models/estate_property.py | 16 +++++++++++++--- estate/views/estate_menu.xml | 8 ++++++++ estate/views/estate_property_views.xml | 10 ++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 estate/views/estate_menu.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 94a560626b4..3c2ac926147 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,5 +2,10 @@ 'name':'Real Estate', 'version':'1.0', 'depends':['base'], - 'data':['security/ir.model.access.csv'] + 'application':True, + 'installable':True, + 'data':['security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menu.xml', + ] } \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index c50b30d672c..9d07e39e4d2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ from odoo import models, fields + class EstateProperty(models.Model): _name = 'estate.property' _description = 'Real Estate property model' @@ -7,10 +8,10 @@ class EstateProperty(models.Model): name = fields.Char('Name',required=True) description = fields.Text('Description') postcode = fields.Char('Postcode',required=True) - date_availability = fields.Date('Availability') + date_availability = fields.Date('Availability',copy=False,default=fields.Date.add(fields.Date.today(),months=3)) expected_price = fields.Float('Expected Price',required=True) - selling_price = fields.Float('Selling Price') - bedrooms = fields.Integer('Bedrooms') + 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') @@ -20,5 +21,14 @@ class EstateProperty(models.Model): string='Garden Orientation', selection=[('North','North'),('South','South'),('East','East'),('West','West')] ) + active = fields.Boolean('Active',default=True) + state = fields.Selection( + string='State', + selection=[('New','New'),('Offer Received','Offer Received'), + ('Offer accepted','Offer accepted'),('Sold','Sold'),('Cancelled','Cancelled')], + required=True, + copy=False, + default='New' + ) diff --git a/estate/views/estate_menu.xml b/estate/views/estate_menu.xml new file mode 100644 index 00000000000..e45f1914265 --- /dev/null +++ b/estate/views/estate_menu.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..1914ee502ba --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,10 @@ + + + + Properties + estate.property + list,form + + + + From c6c24d0f2b31ebcd61bbc9c2609a8e15c7a40a7c Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Tue, 15 Sep 2026 15:21:49 +0200 Subject: [PATCH 05/17] [FIX] estate: feedback implementation for git Corrected the code based on the feedback from the PR (Thank you !) --- estate/__init__.py | 2 +- estate/__manifest__.py | 9 +++++---- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 10 +++++++--- estate/security/ir.model.access.csv | 2 +- estate/views/estate_menu.xml | 2 +- 6 files changed, 16 insertions(+), 11 deletions(-) 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 3c2ac926147..bd77d9cc518 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,8 +4,9 @@ 'depends':['base'], 'application':True, 'installable':True, - 'data':['security/ir.model.access.csv', - 'views/estate_property_views.xml', - 'views/estate_menu.xml', + 'data':[ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menu.xml', ] -} \ 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 9d07e39e4d2..4abc0f4db49 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -19,16 +19,20 @@ class EstateProperty(models.Model): garden_area = fields.Integer('Garden Area') garden_orientation = fields.Selection( string='Garden Orientation', - selection=[('North','North'),('South','South'),('East','East'),('West','West')] + selection=[('north','North'),('south','South'),('east','East'),('west','West')] ) active = fields.Boolean('Active',default=True) state = fields.Selection( string='State', - selection=[('New','New'),('Offer Received','Offer Received'), - ('Offer accepted','Offer accepted'),('Sold','Sold'),('Cancelled','Cancelled')], + selection=[('new','New'), + ('offer_received','Offer Received'), + ('offer_accepted','Offer accepted'), + ('sold','Sold'), + ('cancelled','Cancelled')], required=True, copy=False, default='New' ) + diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 1eb8b98e9d7..682a453386d 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -access_estate,estate,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property_user,access.estate.property,model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menu.xml b/estate/views/estate_menu.xml index e45f1914265..a91019d42fc 100644 --- a/estate/views/estate_menu.xml +++ b/estate/views/estate_menu.xml @@ -5,4 +5,4 @@ - \ No newline at end of file + From 84e33608d81a7e22d8aa3d56e60a976df5ff48f7 Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Tue, 15 Sep 2026 16:44:56 +0200 Subject: [PATCH 06/17] [IMP] estate: add title to list view Feature based on chapter 6 of the training --- estate/__manifest__.py | 12 +++++----- estate/models/estate_property.py | 32 +++++++++++++------------- estate/views/estate_property_views.xml | 17 +++++++++++++- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index bd77d9cc518..34929a705a3 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,10 +1,10 @@ { - 'name':'Real Estate', - 'version':'1.0', - 'depends':['base'], - 'application':True, - 'installable':True, - 'data':[ + 'name': 'Real Estate', + 'version': '1.0', + 'depends': ['base'], + 'application': True, + 'installable': True, + 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_menu.xml', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 4abc0f4db49..16f394cd884 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -5,13 +5,13 @@ class EstateProperty(models.Model): _name = 'estate.property' _description = 'Real Estate property model' - name = fields.Char('Name',required=True) + name = fields.Char('Name', required=True) description = fields.Text('Description') - postcode = fields.Char('Postcode',required=True) - date_availability = fields.Date('Availability',copy=False,default=fields.Date.add(fields.Date.today(),months=3)) - expected_price = fields.Float('Expected Price',required=True) - selling_price = fields.Float('Selling Price',readonly=True,copy=False) - bedrooms = fields.Integer('Bedrooms',default=2) + postcode = fields.Char('Postcode', required=True) + date_availability = fields.Date('Availability', copy=False, default=fields.Date.add(fields.Date.today(), months=3)) + expected_price = fields.Float('Expected Price', 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') @@ -19,20 +19,20 @@ class EstateProperty(models.Model): garden_area = fields.Integer('Garden Area') garden_orientation = fields.Selection( string='Garden Orientation', - selection=[('north','North'),('south','South'),('east','East'),('west','West')] + selection=[('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West')] ) - active = fields.Boolean('Active',default=True) + active = fields.Boolean('Active', default=True) state = fields.Selection( string='State', - selection=[('new','New'), - ('offer_received','Offer Received'), - ('offer_accepted','Offer accepted'), - ('sold','Sold'), - ('cancelled','Cancelled')], + selection=[('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled')], required=True, copy=False, default='New' ) - - - diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 1914ee502ba..85f8987828d 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,10 +1,25 @@ + Properties estate.property list,form - + + estate.properties.list + estate.property + + + + + + + + + + + + From e5f83fc14aa6c53522290f455dd33292a0b1403c Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Wed, 16 Sep 2026 09:31:00 +0200 Subject: [PATCH 07/17] [IMP] estate: add search view Feature based on chapter 6 of the training --- estate/models/estate_property.py | 2 +- estate/views/estate_property_views.xml | 83 +++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 16f394cd884..fa0c537f8cc 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -34,5 +34,5 @@ class EstateProperty(models.Model): ('cancelled', 'Cancelled')], required=True, copy=False, - default='New' + default='new' ) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 85f8987828d..f81d544bda0 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,7 +4,7 @@ Properties estate.property - list,form + list,form,search @@ -22,4 +22,85 @@ + + + estate.properties.form + estate.property + +
+ +

+ +

+
+
+ + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.properties.search + estate.property + + + + + + + + + + + + + + + + + + + From de60fce516a3f97330cb43b43448681ad8aea650 Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Wed, 16 Sep 2026 11:58:37 +0200 Subject: [PATCH 08/17] [IMP] estate: added tag,offer,property type Feature based on chapter 7 of the training --- estate/__manifest__.py | 3 ++ estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 12 ++++++++ estate/models/estate_property_offer.py | 13 +++++++++ estate/models/estate_property_tag.py | 7 +++++ estate/models/estate_property_type.py | 7 +++++ estate/security/ir.model.access.csv | 3 ++ estate/views/estate_menu.xml | 4 +++ estate/views/estate_property_offer_views.xml | 30 ++++++++++++++++++++ estate/views/estate_property_tag_views.xml | 10 +++++++ estate/views/estate_property_type_views.xml | 10 +++++++ estate/views/estate_property_views.xml | 16 +++++++++++ 12 files changed, 118 insertions(+) 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 34929a705a3..d0b1ab9537b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,6 +7,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_menu.xml', ] } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..09b2099fe84 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 \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index fa0c537f8cc..1ce9e597e92 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -5,6 +5,7 @@ class EstateProperty(models.Model): _name = 'estate.property' _description = 'Real Estate property model' + #Property information name = fields.Char('Name', required=True) description = fields.Text('Description') postcode = fields.Char('Postcode', required=True) @@ -36,3 +37,14 @@ class EstateProperty(models.Model): copy=False, default='new' ) + property_type_id = fields.Many2one("estate.property.type", string='Property Type') + + #Other Information + salesman_id = fields.Many2one('res.users', string='Salesman', default=lambda self:self.env.user) + buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False) + + #Tags + tag_ids = fields.Many2many('estate.property.tag', string='Tags') + + #Offers + offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offer') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..c6e3253e720 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,13 @@ +from odoo import models,fields + +class EstatePropertyOffer(models.Model): + _name = 'estate.property.offer' + _description = 'Estate Property Offer' + + price = fields.Float(string='Price') + status = fields.Selection([ + ('accepted', 'Accepted'), + ('refused', 'Refused') + ], copy=False, string='Status') + partner_id = fields.Many2one('res.partner', required=True, string='Partner') + property_id = fields.Many2one('estate.property', required=True) \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..9015528b882 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import models,fields + +class EstatePropertyTag(models.Model): + _name= 'estate.property.tag' + _description = 'Estate Property Tag' + + name = fields.Char(string='Name', required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..6e6b2e5045f --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import models,fields + +class EstatePropertyType(models.Model): + _name = 'estate.property.type' + _description = 'Define the Real Estate Property Type' + + name = fields.Char(string='Name', required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 682a453386d..2d9ca473a31 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_property_user,access.estate.property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type_user,access.estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_user,access.estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_user,access.estate.property.offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menu.xml b/estate/views/estate_menu.xml index a91019d42fc..557e8fce764 100644 --- a/estate/views/estate_menu.xml +++ b/estate/views/estate_menu.xml @@ -4,5 +4,9 @@ + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..345b0e19443 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,30 @@ + + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + estate.property.offer.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 new file mode 100644 index 00000000000..495f28efbf1 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,10 @@ + + + + + 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..6e7e10df82c --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,10 @@ + + + + + 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 f81d544bda0..127a1d6a5a5 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -14,6 +14,7 @@ + @@ -32,9 +33,13 @@

+

+ +

+ @@ -74,6 +79,17 @@ + + + + + + + + + + + From ada43a3d21782ea2720504c82d72aebd083f3994 Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Wed, 16 Sep 2026 13:08:08 +0200 Subject: [PATCH 09/17] [FIX] estate: fix style and remarks Change style Change filter to match convention (estate_property_views.xml) rename boolean variable remove block around each for (estate_property_views.xml) change id for menuitem (estate_menu.xml) added lamba for the default for date_availability added author & license key in manifest --- estate/__manifest__.py | 4 +++- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 22 +++++++++++--------- estate/models/estate_property_offer.py | 5 +++-- estate/models/estate_property_tag.py | 5 +++-- estate/models/estate_property_type.py | 3 ++- estate/views/estate_menu.xml | 6 +++--- estate/views/estate_property_offer_views.xml | 2 +- estate/views/estate_property_tag_views.xml | 2 +- estate/views/estate_property_type_views.xml | 2 +- estate/views/estate_property_views.xml | 21 +++---------------- 11 files changed, 33 insertions(+), 41 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index d0b1ab9537b..4344f144632 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -11,5 +11,7 @@ 'views/estate_property_tag_views.xml', 'views/estate_property_offer_views.xml', 'views/estate_menu.xml', - ] + ], + 'author': 'macai', + 'license': 'LGPL-3', } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 09b2099fe84..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,4 @@ from . import estate_property from . import estate_property_type from . import estate_property_tag -from . import estate_property_offer \ No newline at end of file +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 1ce9e597e92..d277b4effca 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -5,25 +5,26 @@ class EstateProperty(models.Model): _name = 'estate.property' _description = 'Real Estate property model' - #Property information +# Property information name = fields.Char('Name', required=True) description = fields.Text('Description') postcode = fields.Char('Postcode', required=True) - date_availability = fields.Date('Availability', copy=False, default=fields.Date.add(fields.Date.today(), months=3)) + date_availability = fields.Date('Availability', copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3)) expected_price = fields.Float('Expected Price', 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') + has_garage = fields.Boolean('Garage') + has_garden = fields.Boolean('Garden') garden_area = fields.Integer('Garden Area') garden_orientation = fields.Selection( string='Garden Orientation', selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), - ('west', 'West')] + ('west', 'West'), + ] ) active = fields.Boolean('Active', default=True) state = fields.Selection( @@ -32,19 +33,20 @@ class EstateProperty(models.Model): ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer accepted'), ('sold', 'Sold'), - ('cancelled', 'Cancelled')], + ('cancelled', 'Cancelled'), + ], required=True, copy=False, default='new' ) property_type_id = fields.Many2one("estate.property.type", string='Property Type') - #Other Information - salesman_id = fields.Many2one('res.users', string='Salesman', default=lambda self:self.env.user) +# Other Information + salesman_id = fields.Many2one('res.users', string='Salesman', default=lambda self: self.env.user) buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False) - #Tags +# Tags tag_ids = fields.Many2many('estate.property.tag', string='Tags') - #Offers +# Offers offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offer') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index c6e3253e720..b807a908c10 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ -from odoo import models,fields +from odoo import models, fields + class EstatePropertyOffer(models.Model): _name = 'estate.property.offer' @@ -10,4 +11,4 @@ class EstatePropertyOffer(models.Model): ('refused', 'Refused') ], copy=False, string='Status') partner_id = fields.Many2one('res.partner', required=True, string='Partner') - property_id = fields.Many2one('estate.property', required=True) \ No newline at end of file + 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 9015528b882..bc30eeb0ffa 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,7 +1,8 @@ -from odoo import models,fields +from odoo import models, fields + class EstatePropertyTag(models.Model): - _name= 'estate.property.tag' + _name = 'estate.property.tag' _description = 'Estate Property Tag' name = fields.Char(string='Name', required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 6e6b2e5045f..585d919f942 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,4 +1,5 @@ -from odoo import models,fields +from odoo import models, fields + class EstatePropertyType(models.Model): _name = 'estate.property.type' diff --git a/estate/views/estate_menu.xml b/estate/views/estate_menu.xml index 557e8fce764..0ec713c1c9a 100644 --- a/estate/views/estate_menu.xml +++ b/estate/views/estate_menu.xml @@ -1,10 +1,10 @@ - - + + - + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 345b0e19443..1a1eb470103 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -27,4 +27,4 @@ - \ 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 495f28efbf1..87371cfeb30 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -7,4 +7,4 @@ 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 index 6e7e10df82c..57ae856ac57 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -7,4 +7,4 @@ 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 127a1d6a5a5..145b13081c6 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -55,26 +55,12 @@ - - - - - - - - - - - - - - + + - - @@ -108,8 +94,7 @@ - + From d8333040b08d3dcafb88733c0be92f820a2dbf15 Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Thu, 17 Sep 2026 09:15:14 +0200 Subject: [PATCH 10/17] [IMP] estate: Add computation field for offer As refer in the chapter 8 of the training --- estate/models/estate_property.py | 19 ++++++++++++++++++- estate/models/estate_property_offer.py | 18 +++++++++++++++++- estate/views/estate_property_offer_views.xml | 4 ++++ estate/views/estate_property_views.xml | 2 ++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index d277b4effca..f046b108f42 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import models, fields, api class EstateProperty(models.Model): @@ -26,6 +26,7 @@ class EstateProperty(models.Model): ('west', 'West'), ] ) + total_area = fields.Integer(compute='_compute_total_area', string='Total Area') active = fields.Boolean('Active', default=True) state = fields.Selection( string='State', @@ -50,3 +51,19 @@ class EstateProperty(models.Model): # Offers offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offer') + best_price = fields.Float(compute='_compute_best_price', string='Best Offer') + +# Computation Fields: + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + if len(record.offer_ids) > 0: + record.best_price = max(record.offer_ids.mapped('price')) + else: + record.best_price = 0. diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index b807a908c10..f8f5fd9f452 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ -from odoo import models, fields +from odoo import models, fields, api +from datetime import date class EstatePropertyOffer(models.Model): @@ -10,5 +11,20 @@ class EstatePropertyOffer(models.Model): ('accepted', 'Accepted'), ('refused', 'Refused') ], copy=False, string='Status') + validity = fields.Integer(string='Validity (Days)', default=7) + date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_date_deadline', string='Deadline') partner_id = fields.Many2one('res.partner', required=True, string='Partner') property_id = fields.Many2one('estate.property', required=True) + +# computed fields + + @api.depends('validity', 'create_date') + def _compute_date_deadline(self): + for record in self: +# Fallback when create_date is not set yet: + base_date = record.create_date or fields.Date.today() + record.date_deadline = fields.Date.add(base_date, days=record.validity) + + def _inverse_date_deadline(self): + for record in self: + record.validity = (record.date_deadline - record.create_date.date()).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 1a1eb470103..6297e6f88d7 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -8,6 +8,8 @@ + + @@ -21,6 +23,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 145b13081c6..40effefedb9 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -47,6 +47,7 @@
+
@@ -62,6 +63,7 @@ +
From c1ed077b695467051a54fe370aa8820b6c0e61b3 Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Thu, 17 Sep 2026 09:26:27 +0200 Subject: [PATCH 11/17] [IMP] estate: Add changing has_garden computation As refer in the chapter 8 of the training --- estate/models/estate_property.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index f046b108f42..a9bb63d6f41 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -57,6 +57,7 @@ class EstateProperty(models.Model): @api.depends('living_area', 'garden_area') def _compute_total_area(self): +# QUESTION is the for each loop here necessary ? or can I just use self ? for record in self: record.total_area = record.living_area + record.garden_area @@ -67,3 +68,12 @@ def _compute_best_price(self): record.best_price = max(record.offer_ids.mapped('price')) else: record.best_price = 0. + + @api.onchange('has_garden') + def _onchange_has_garden(self): + if self.has_garden: + self.garden_area = 10 + self.garden_orientation = 'north' + else: + self.garden_area = None + self.garden_orientation = None From b2973264c6cd630c3aa79b730a5a25a77e0d5140 Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Thu, 17 Sep 2026 09:32:41 +0200 Subject: [PATCH 12/17] [FIX] estate: fix the styling Remove unused import Move the comment at the end of lines --- estate/models/estate_property.py | 3 +-- estate/models/estate_property_offer.py | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index a9bb63d6f41..35649b705a3 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -57,8 +57,7 @@ class EstateProperty(models.Model): @api.depends('living_area', 'garden_area') def _compute_total_area(self): -# QUESTION is the for each loop here necessary ? or can I just use self ? - for record in self: + for record in self: #QUESTION is the for each loop here necessary ? or can I just use self ? record.total_area = record.living_area + record.garden_area @api.depends('offer_ids.price') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index f8f5fd9f452..6e41c0dd247 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,4 @@ from odoo import models, fields, api -from datetime import date class EstatePropertyOffer(models.Model): @@ -21,8 +20,7 @@ class EstatePropertyOffer(models.Model): @api.depends('validity', 'create_date') def _compute_date_deadline(self): for record in self: -# Fallback when create_date is not set yet: - base_date = record.create_date or fields.Date.today() + base_date = record.create_date or fields.Date.today() #Fallback when create_date is not set yet: record.date_deadline = fields.Date.add(base_date, days=record.validity) def _inverse_date_deadline(self): From 98ae518ccb0aef9fc4198e01572ed589dac67381 Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Thu, 17 Sep 2026 09:39:24 +0200 Subject: [PATCH 13/17] [FIX] estate: fix the comment styling --- estate/models/estate_property.py | 2 +- estate/models/estate_property_offer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 35649b705a3..8398960b239 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -57,7 +57,7 @@ class EstateProperty(models.Model): @api.depends('living_area', 'garden_area') def _compute_total_area(self): - for record in self: #QUESTION is the for each loop here necessary ? or can I just use self ? + for record in self: # QUESTION is the for each loop here necessary ? or can I just use self ? record.total_area = record.living_area + record.garden_area @api.depends('offer_ids.price') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 6e41c0dd247..021d3875d73 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -20,7 +20,7 @@ class EstatePropertyOffer(models.Model): @api.depends('validity', 'create_date') def _compute_date_deadline(self): for record in self: - base_date = record.create_date or fields.Date.today() #Fallback when create_date is not set yet: + base_date = record.create_date or fields.Date.today() # Fallback when create_date is not set yet: record.date_deadline = fields.Date.add(base_date, days=record.validity) def _inverse_date_deadline(self): From 170e2161771acea22f61ac7203f6868abc434e22 Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Thu, 17 Sep 2026 11:19:33 +0200 Subject: [PATCH 14/17] [IMP] estate: Add buttons to update property Added buttons to cancel or 'sell' a property Added buttons to refuse or accept an offer Set the buyer and the price on acceptance Refer to chapter 9 of the training --- estate/models/estate_property.py | 33 ++++++++++++++++++++ estate/models/estate_property_offer.py | 22 +++++++++++++ estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 6 +++- 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8398960b239..05cdf2d846b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import models, fields, api +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -76,3 +77,35 @@ def _onchange_has_garden(self): else: self.garden_area = None self.garden_orientation = None + +# Actions buttons + def action_cancel_property_state(self): + for record in self: + if record.state == 'cancelled': + raise UserError('Cancelled properties cannot be cancelled again !') + elif record.state == 'sold': + raise UserError('Sold Properties cannot be cancelled !') + record.state = 'cancelled' + return True + return True + + + def action_sell_property_state(self): + for record in self: + if record.state == 'sold': + raise UserError('Sold Properties cannot be sold again !') + elif record.state == 'cancelled': + raise UserError('Cancelled properties cannot be sold !') + record.state = 'sold' + return True + return True + +# Actions : + def action_set_selling_offer(self, buyer, price): + for record in self: + if record.buyer_id: + raise UserError('An offer is already accepted for this house') + record.buyer_id = buyer + record.selling_price = price + return True + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 021d3875d73..ed01c40bfc9 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ from odoo import models, fields, api +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): @@ -26,3 +27,24 @@ def _compute_date_deadline(self): def _inverse_date_deadline(self): for record in self: record.validity = (record.date_deadline - record.create_date.date()).days + +# Action buttons + + def action_accept_property_offer(self): + for record in self: + try: + for properties in record.property_id: + properties.action_set_selling_offer(record.partner_id, record.price) + except UserError as ue: + raise ue + record.status = 'accepted' + return True + return True + + def action_refuse_property_offer(self): + for record in self: + if record.status == 'accepted': + raise UserError('Accepted offer cannot be refused !') + record.status = 'refused' + return True + return True diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 6297e6f88d7..3857f687f93 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -10,6 +10,8 @@ + +
+
+

+ +

+
+ + + + + + + + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 4413e0560ad..812208f5910 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,13 +5,18 @@ Properties estate.property list,form,search + {'search_default_active': True, 'search_default_current': True} estate.properties.list estate.property - + @@ -19,7 +24,7 @@ - + @@ -30,21 +35,21 @@
-

- +

- - + @@ -66,14 +71,14 @@ - - + + - + @@ -97,7 +102,7 @@ - + From b8fe20e7dd19f010f74246f7d457df9a9356ea1a Mon Sep 17 00:00:00 2001 From: cailleaux-maxime Date: Fri, 18 Sep 2026 10:08:18 +0200 Subject: [PATCH 17/17] [FIX] estate: Fix styling of the code --- estate/models/estate_property_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index bc8499ad247..2b380172a79 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -10,7 +10,7 @@ class EstatePropertyType(models.Model): property_ids = fields.One2many('estate.property', 'property_type_id', string='Properties') sequence = fields.Integer(string='Sequence', default=1) # Offers handling - offer_ids = fields.One2many('estate.property.offer','property_type_id','Property Type') + offer_ids = fields.One2many('estate.property.offer', 'property_type_id', 'Property Type') offer_count = fields.Integer(compute='_compute_offer_count', string='Offer Count') # Compute fields: