From 369253ecbff90c5a75b8d1af3d26bb113efa3a85 Mon Sep 17 00:00:00 2001 From: eslamicoo912 Date: Wed, 17 Jun 2026 17:15:27 +0300 Subject: [PATCH 1/6] real estate module starter --- estate/__init__.py | 1 + estate/__manifest__.py | 13 +++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 23 +++++++++++ estate/views/estate_property_views.xml | 54 ++++++++++++++++++++++++++ 5 files changed, 92 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 create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..02d91592f1b --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,13 @@ +{ + 'name': 'Estate', + 'version': '1.0', + 'summary': 'Tutorial module for managing real estate properties', + 'description': 'A starter module to learn Odoo development by managing estate properties.', + 'author': 'Your Name', + 'depends': ['base'], + 'data': [ + 'views/estate_property_views.xml', + ], + 'installable': True, + 'application': True, +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..083f173f59c --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,23 @@ +from odoo import models, fields + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property" + + name = fields.Char(string="Title", required=True) + description = fields.Text(string="Description") + postcode = fields.Char(string="Postcode") + date_availability = fields.Date(string="Available From") + expected_price = fields.Float(string="Expected Price", required=True) + selling_price = fields.Float(string="Selling Price", readonly=True) + bedrooms = fields.Integer(string="Bedrooms") + living_area = fields.Integer(string="Living Area (sqm)") + facades = fields.Integer(string="Facades") + garage = fields.Boolean(string="Garage") + garden = fields.Boolean(string="Garden") + garden_area = fields.Integer(string="Garden Area (sqm)") + garden_orientation = fields.Selection( + string="Garden Orientation", + selection=[('north', 'North'), ('south', 'South'), + ('east', 'East'), ('west', 'West')], + ) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..e3b3ff46ffe --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,54 @@ + + + estate.property.form + estate.property + +
+ + + + + + + + + + + + + + + + + +
+
+
+ + + estate.property.tree + estate.property + + + + + + + + + + + + + + + + + Properties + estate.property + tree,form + + + +
From 9c5da5a28e633d3be8c71af0836136f7a774b834 Mon Sep 17 00:00:00 2001 From: eslamicoo912 Date: Sat, 20 Jun 2026 15:01:06 +0300 Subject: [PATCH 2/6] creating views for estate property module --- estate/__manifest__.py | 3 +- estate/models/estate_property.py | 27 +++------- estate/security/ir.model.access.csv | 2 + estate/views/estate_property_views.xml | 54 -------------------- estate/views/views.xml | 70 ++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 75 deletions(-) create mode 100644 estate/security/ir.model.access.csv delete mode 100644 estate/views/estate_property_views.xml create mode 100644 estate/views/views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 02d91592f1b..7b85af04649 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,7 +6,8 @@ 'author': 'Your Name', 'depends': ['base'], 'data': [ - 'views/estate_property_views.xml', + "views/views.xml", + "security/ir.model.access.csv" ], 'installable': True, 'application': True, diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 083f173f59c..75dc8aa5ae8 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,23 +1,10 @@ from odoo import models, fields -class EstateProperty(models.Model): - _name = "estate.property" - _description = "Estate Property" - name = fields.Char(string="Title", required=True) - description = fields.Text(string="Description") - postcode = fields.Char(string="Postcode") - date_availability = fields.Date(string="Available From") - expected_price = fields.Float(string="Expected Price", required=True) - selling_price = fields.Float(string="Selling Price", readonly=True) - bedrooms = fields.Integer(string="Bedrooms") - living_area = fields.Integer(string="Living Area (sqm)") - facades = fields.Integer(string="Facades") - garage = fields.Boolean(string="Garage") - garden = fields.Boolean(string="Garden") - garden_area = fields.Integer(string="Garden Area (sqm)") - garden_orientation = fields.Selection( - string="Garden Orientation", - selection=[('north', 'North'), ('south', 'South'), - ('east', 'East'), ('west', 'West')], - ) +class TestModel(models.Model): # Inheritence -> This class inherits from models.Model + _name = "estate_property_model" # Name of the table in database + _description = "Estate Property Model" # user-friendly name + + name = fields.Char(required=True) # VARCHAR & NOT NULL + expected_price = fields.Float(required=True) # NUMERIC & NOT NULL + description = fields.Char() \ 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..a524ab4409b --- /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_property_model_user,estate.property.user,model_estate_property_model,,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml deleted file mode 100644 index e3b3ff46ffe..00000000000 --- a/estate/views/estate_property_views.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - estate.property.form - estate.property - -
- - - - - - - - - - - - - - - - - -
-
-
- - - estate.property.tree - estate.property - - - - - - - - - - - - - - - - - Properties - estate.property - tree,form - - - -
diff --git a/estate/views/views.xml b/estate/views/views.xml new file mode 100644 index 00000000000..a17a6b2a0c0 --- /dev/null +++ b/estate/views/views.xml @@ -0,0 +1,70 @@ + + + + + + Estate Property action + estate_property_model + list,form + + + + + + + + + estate.property.model.list + estate_property_model + + + + + + + + + + + + estate.property.model.form + estate_property_model + +
+ + + + + + + + + + + + + + + +
+
+
+ + + + estate.property.model.search + estate_property_model + + + + + + + + + + +
+
From 48ab6792e7093f645540aab57c0a8c03b3f02cb9 Mon Sep 17 00:00:00 2001 From: eslamicoo912 Date: Sat, 20 Jun 2026 20:05:54 +0300 Subject: [PATCH 3/6] creating more models and views with relationships --- estate/__manifest__.py | 7 ++- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 21 ++++++- estate/models/estate_property_offer.py | 9 +++ estate/models/estate_property_tag.py | 13 ++++ estate/models/estate_property_type.py | 12 ++++ estate/security/ir.model.access.csv | 5 +- .../views/{views.xml => estate_property.xml} | 34 ++++++++-- estate/views/estate_property_offer.xml | 63 +++++++++++++++++++ estate/views/estate_property_tag.xml | 54 ++++++++++++++++ estate/views/estate_property_type.xml | 61 ++++++++++++++++++ 11 files changed, 271 insertions(+), 10 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 rename estate/views/{views.xml => estate_property.xml} (64%) create mode 100644 estate/views/estate_property_offer.xml create mode 100644 estate/views/estate_property_tag.xml create mode 100644 estate/views/estate_property_type.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 7b85af04649..69814356e7f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -3,10 +3,13 @@ 'version': '1.0', 'summary': 'Tutorial module for managing real estate properties', 'description': 'A starter module to learn Odoo development by managing estate properties.', - 'author': 'Your Name', + 'author': '', 'depends': ['base'], 'data': [ - "views/views.xml", + "views/estate_property_offer.xml", + "views/estate_property_type.xml", + "views/estate_property_tag.xml", + "views/estate_property.xml", "security/ir.model.access.csv" ], 'installable': True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..e5f89221b05 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property +from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 75dc8aa5ae8..66ece616095 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,10 +1,27 @@ from odoo import models, fields -class TestModel(models.Model): # Inheritence -> This class inherits from models.Model +class EstatePropertyModel(models.Model): # Inheritence -> This class inherits from models.Model _name = "estate_property_model" # Name of the table in database _description = "Estate Property Model" # user-friendly name name = fields.Char(required=True) # VARCHAR & NOT NULL expected_price = fields.Float(required=True) # NUMERIC & NOT NULL - description = fields.Char() \ No newline at end of file + description = fields.Char() + property_type_id = fields.Many2one( + comodel_name="estate_property_type_model", + string="Property Type", + ondelete="set null" + ) + property_tag_ids = fields.Many2many( + comodel_name="estate_property_tag_model", + relation="estate_property_tag_rel", + column1="estate_property_id", + column2="estate_property_tag_id", + string="Tag" + ) + property_offer_ids = fields.One2many( + comodel_name="estate_property_offer_model", + inverse_name="property_id", + string="Property Offers" + ) \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..61fecf38c9e --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,9 @@ +from odoo import models, fields + +class EstatePropertyOffer(models.Model): + _name = "estate_property_offer_model" + + price = fields.Float(required=True) + status = fields.Selection([('accepted', 'Accepted'), ('refused', 'Refused')], string="Offer Status") + partner_id = fields.Many2one(comodel_name="res.partner", string="Partner") + property_id = fields.Many2one(comodel_name="estate_property_model", string="Estate Property") \ 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..5a1dd57ad28 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,13 @@ +from odoo import models, fields + +class EstatePropertyTag(models.Model): + _name = "estate_property_tag_model" + + name = fields.Char(required=True) + property_ids = fields.Many2many( + comodel_name="estate_property_model", + relation="estate_property_tag_rel", + column1="estate_property_tag_id", + column2="estate_property_id", + string="Properties" + ) \ No newline at end of file diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..4b15261ccb9 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,12 @@ +from odoo import models, fields + + +class PropertyType(models.Model): + _name = "estate_property_type_model" + + name = fields.Char(required=True) + property_ids = fields.One2many( + comodel_name="estate_property_model", + inverse_name="property_type_id", + string="Properties" + ) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index a524ab4409b..20dc10709b5 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_model_user,estate.property.user,model_estate_property_model,,1,1,1,1 \ No newline at end of file +access_estate_property_model_user,estate.property.user,model_estate_property_model,,1,1,1,1 +access_estate_property_type_user,estate.property.type.user,model_estate_property_type_model,base.group_user,1,1,1,1 +access_estate_property_tag_user,estate.property.tag.user,model_estate_property_tag_model,base.group_user,1,1,1,1 +access_estate_property_offer_user,estate.property.offer.user,model_estate_property_offer_model,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/views.xml b/estate/views/estate_property.xml similarity index 64% rename from estate/views/views.xml rename to estate/views/estate_property.xml index a17a6b2a0c0..51d651764e8 100644 --- a/estate/views/views.xml +++ b/estate/views/estate_property.xml @@ -9,10 +9,23 @@ - - + + action="estate_property_model_action" sequence="1"/> + + + + + + + @@ -23,6 +36,9 @@ + + + @@ -41,12 +57,21 @@ + + + + + + - + + + + @@ -61,6 +86,7 @@ + diff --git a/estate/views/estate_property_offer.xml b/estate/views/estate_property_offer.xml new file mode 100644 index 00000000000..c597203600a --- /dev/null +++ b/estate/views/estate_property_offer.xml @@ -0,0 +1,63 @@ + + + + + + Estate Property Offer + estate_property_offer_model + list,form + + + + + estate.property.offer.model.list + estate_property_offer_model + + + + + + + + + + + + + estate.property.offer.model.form + estate_property_offer_model + +
+ + + + + + + + + + + + + + + + +
+
+
+ + + + estate.property.offer.model.search + estate_property_offer_model + + + + + + + +
+
diff --git a/estate/views/estate_property_tag.xml b/estate/views/estate_property_tag.xml new file mode 100644 index 00000000000..66cbf3e69e8 --- /dev/null +++ b/estate/views/estate_property_tag.xml @@ -0,0 +1,54 @@ + + + + + + Estate Property Tag + estate_property_tag_model + list,form + + + + + estate.property.tag.model.list + estate_property_tag_model + + + + + + + + + + + estate.property.tag.model.form + estate_property_tag_model + +
+ + + + + + + + + + +
+
+
+ + + + estate.property.tag.model.search + estate_property_tag_model + + + + + + +
+
diff --git a/estate/views/estate_property_type.xml b/estate/views/estate_property_type.xml new file mode 100644 index 00000000000..2fad2ee49b8 --- /dev/null +++ b/estate/views/estate_property_type.xml @@ -0,0 +1,61 @@ + + + + + + Estate Property Type action + estate_property_type_model + list,form + + + + + estate.property.type.model.list + estate_property_type_model + + + + + + + + + + + estate.property.type.model.form + estate_property_type_model + +
+ + + + + + + + + + + + + + + + + +
+
+
+ + + + estate.property.type.model.search + estate_property_type_model + + + + + + +
+
From 01bdf273a8240a2342cb08e125d51a6cd0a2ac36 Mon Sep 17 00:00:00 2001 From: eslamicoo912 Date: Sat, 20 Jun 2026 20:32:44 +0300 Subject: [PATCH 4/6] computed fields --- estate/models/estate_property.py | 12 +++++++++++- estate/views/estate_property.xml | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 66ece616095..37b04843d29 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 api, models, fields class EstatePropertyModel(models.Model): # Inheritence -> This class inherits from models.Model @@ -8,6 +8,16 @@ class EstatePropertyModel(models.Model): # Inherite name = fields.Char(required=True) # VARCHAR & NOT NULL expected_price = fields.Float(required=True) # NUMERIC & NOT NULL description = fields.Char() + + living_area = fields.Integer() + garden_area = fields.Integer() + total_area = fields.Integer(compute="_compute_total_area") + + @api.depends("living_area", "garden_area") + def _compute_total_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + property_type_id = fields.Many2one( comodel_name="estate_property_type_model", string="Property Type", diff --git a/estate/views/estate_property.xml b/estate/views/estate_property.xml index 51d651764e8..02541f74080 100644 --- a/estate/views/estate_property.xml +++ b/estate/views/estate_property.xml @@ -36,6 +36,9 @@ + + + @@ -57,6 +60,10 @@ + + + + From 1ab1dcd554047aaf9095fe65a9bb51fdf865923a Mon Sep 17 00:00:00 2001 From: eslamicoo912 Date: Sun, 21 Jun 2026 23:22:17 +0300 Subject: [PATCH 5/6] buttons with business logic and working with constraints --- estate/models/estate_property.py | 50 ++++++++++++++++++++++++-- estate/models/estate_property_offer.py | 10 +++++- estate/views/estate_property.xml | 30 +++++++++++++--- estate/views/estate_property_offer.xml | 8 ++++- 4 files changed, 89 insertions(+), 9 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 37b04843d29..1cc06f451c4 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import api, models, fields +from odoo import api, models, fields, exceptions class EstatePropertyModel(models.Model): # Inheritence -> This class inherits from models.Model @@ -10,9 +10,17 @@ class EstatePropertyModel(models.Model): # Inherite description = fields.Char() living_area = fields.Integer() + garden = fields.Boolean() garden_area = fields.Integer() + garden_orientation = fields.Selection([("north", 'North'), ('south', 'South'), ('east', "East"), ("west", 'West')]) total_area = fields.Integer(compute="_compute_total_area") + best_price = fields.Float(compute="_compute_best_offer_price", string="Best Accepted Offer") + + sold = fields.Boolean() + cancelled = fields.Boolean() + property_status = fields.Char(default="New", string="Property Status") + @api.depends("living_area", "garden_area") def _compute_total_area(self): for record in self: @@ -34,4 +42,42 @@ def _compute_total_area(self): comodel_name="estate_property_offer_model", inverse_name="property_id", string="Property Offers" - ) \ No newline at end of file + ) + + @api.depends("property_offer_ids") + def _compute_best_offer_price(self): + for record in self: + accepted_offers= record.property_offer_ids.filtered(lambda rec: rec.status == "accepted") + prices = accepted_offers.mapped("price") + record.best_price = max(prices) if prices 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 = 0 + self.garden_orientation = "" + + def action_property_sold(self): + if(self.cancelled==False): + self.sold = True + self.property_status = "Sold" + return True + else: + raise exceptions.UserError("Cancelled properties can't be sold") + + def action_property_cancelled(self): + if (self.sold==False): + self.cancelled = True + self.property_status = "Cancelled" + return True + else: + raise exceptions.UserError("Sold properties can't be cancelled") + + @api.constrains("expected_price") + def _check_expected_price_positive(self): + for record in self: + if(record.expected_price<=0): + raise exceptions.ValidationError("The price must be a positive number") \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 61fecf38c9e..fede31772c5 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -6,4 +6,12 @@ class EstatePropertyOffer(models.Model): price = fields.Float(required=True) status = fields.Selection([('accepted', 'Accepted'), ('refused', 'Refused')], string="Offer Status") partner_id = fields.Many2one(comodel_name="res.partner", string="Partner") - property_id = fields.Many2one(comodel_name="estate_property_model", string="Estate Property") \ No newline at end of file + property_id = fields.Many2one(comodel_name="estate_property_model", string="Estate Property") + + def action_accept_offer(self): + self.status = "accepted" + return True + + def action_refuse_offer(self): + self.status = "refused" + return True \ No newline at end of file diff --git a/estate/views/estate_property.xml b/estate/views/estate_property.xml index 02541f74080..7f8c686ab6d 100644 --- a/estate/views/estate_property.xml +++ b/estate/views/estate_property.xml @@ -42,6 +42,7 @@ + @@ -52,6 +53,10 @@ estate_property_model
+
+
@@ -60,23 +65,38 @@ - - - - + + + - + + + + + + + + + + + + + + + + + diff --git a/estate/views/estate_property_offer.xml b/estate/views/estate_property_offer.xml index c597203600a..aad76292cda 100644 --- a/estate/views/estate_property_offer.xml +++ b/estate/views/estate_property_offer.xml @@ -15,9 +15,11 @@ - +