@@ -31,6 +35,7 @@
+
@@ -64,6 +69,8 @@
+
+
From 0fec7bb165c6b0e013921cc0395886974b3f2151 Mon Sep 17 00:00:00 2001
From: Arthur De Clerck <47596905+Rutradc@users.noreply.github.com>
Date: Mon, 21 Sep 2026 09:12:10 +0200
Subject: [PATCH 11/14] [ADD] chapter-10: add constraints to model
---
estate/models/estate_property.py | 22 ++++++++++++++++++++--
estate/models/estate_property_offer.py | 8 +++++++-
estate/models/estate_property_tag.py | 5 +++++
estate/models/estate_property_type.py | 5 +++++
estate/views/estate_property_views.xml | 4 ++--
5 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
index 05793e210f8..7082f06bad9 100644
--- a/estate/models/estate_property.py
+++ b/estate/models/estate_property.py
@@ -1,5 +1,7 @@
-from odoo import fields, models, api
-from odoo.exceptions import UserError
+from odoo.exceptions import UserError, ValidationError
+
+from odoo import api, fields, models
+from odoo.tools import float_compare, float_is_zero
class EstateProperty(models.Model):
@@ -81,3 +83,19 @@ def cancel_property(self):
raise UserError("Sold properties cannot be cancelled.")
record.state = "cancelled"
return True
+
+ _check_positive_expected_price = models.Constraint(
+ 'CHECK(expected_price >= 0)',
+ 'The expected price should be positive.'
+ )
+
+ _check_positive_selling_price = models.Constraint(
+ 'CHECK(selling_price >= 0)',
+ 'The selling price should be positive.'
+ )
+
+ @api.constrains('selling_price', 'expected_price')
+ def _check_selling_price(self):
+ for record in self:
+ if float_compare(record.selling_price, record.expected_price * 0.9, 2) <= 0 and not float_is_zero(record.selling_price, 2):
+ raise ValidationError(r'The selling price must be at least 90 % of the expected price.')
diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py
index 5faff2eed41..b1a6d8268d7 100644
--- a/estate/models/estate_property_offer.py
+++ b/estate/models/estate_property_offer.py
@@ -1,6 +1,7 @@
-from odoo import api, fields, models
from odoo.exceptions import UserError
+from odoo import api, fields, models
+
class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
@@ -47,3 +48,8 @@ def refuse_offer(self):
for record in self:
record.status = "refused"
return True
+
+ _check_price = models.Constraint(
+ 'CHECK(price >= 0)',
+ 'The offer price should be positive.'
+ )
diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py
index 90b5f2592c2..63d013a7fb4 100644
--- a/estate/models/estate_property_tag.py
+++ b/estate/models/estate_property_tag.py
@@ -6,3 +6,8 @@ class EstatePropertyTag(models.Model):
_description = "Tags for estate properties"
name = fields.Char(required=True)
+
+ _unique_name = models.Constraint(
+ 'UNIQUE(name)',
+ 'Each property tag should have a unique name.'
+ )
diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py
index 144c3be47de..c3a90c25db1 100644
--- a/estate/models/estate_property_type.py
+++ b/estate/models/estate_property_type.py
@@ -6,3 +6,8 @@ class EstatePropertyType(models.Model):
_description = "Estate property types"
name = fields.Char(required=True)
+
+ _unique_name = models.Constraint(
+ 'UNIQUE(name)',
+ 'Each property type should have a unique name.'
+ )
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml
index 711eeb31448..7d696f66f8a 100644
--- a/estate/views/estate_property_views.xml
+++ b/estate/views/estate_property_views.xml
@@ -69,8 +69,8 @@
-
-
+
+
From ba9a63bd9c0ebf8e1432bd25fe9377b9c72943c7 Mon Sep 17 00:00:00 2001
From: Arthur De Clerck <47596905+Rutradc@users.noreply.github.com>
Date: Mon, 21 Sep 2026 09:18:30 +0200
Subject: [PATCH 12/14] [FIX] removed trailing whitespaces for runbot
---
estate/models/estate_property.py | 2 +-
estate/models/estate_property_offer.py | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
index 7082f06bad9..f1ca4f37bf2 100644
--- a/estate/models/estate_property.py
+++ b/estate/models/estate_property.py
@@ -1,7 +1,7 @@
from odoo.exceptions import UserError, ValidationError
+from odoo.tools import float_compare, float_is_zero
from odoo import api, fields, models
-from odoo.tools import float_compare, float_is_zero
class EstateProperty(models.Model):
diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py
index b1a6d8268d7..1c66695e225 100644
--- a/estate/models/estate_property_offer.py
+++ b/estate/models/estate_property_offer.py
@@ -24,9 +24,9 @@ class EstatePropertyOffer(models.Model):
def _compute_date_deadline(self):
for record in self:
if record.create_date:
- record.date_deadline = fields.Date.add(record.create_date, days=record.validity)
+ record.date_deadline = fields.Date.add(record.create_date, days=record.validity)
else:
- record.date_deadline = fields.Date.add(fields.Date.today(), days=record.validity)
+ record.date_deadline = fields.Date.add(fields.Date.today(), days=record.validity)
def _inverse_date_deadline(self):
for record in self:
From 824761ab885352c47ab889aaaf6c669dd0f8ee84 Mon Sep 17 00:00:00 2001
From: Logan Staelens
Date: Mon, 21 Sep 2026 11:25:59 +0200
Subject: [PATCH 13/14] [IMP] estate: adding important file
This file should not be removed.
---
estate/DO_NOT_REMOVE.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 estate/DO_NOT_REMOVE.txt
diff --git a/estate/DO_NOT_REMOVE.txt b/estate/DO_NOT_REMOVE.txt
new file mode 100644
index 00000000000..2f0698240ca
--- /dev/null
+++ b/estate/DO_NOT_REMOVE.txt
@@ -0,0 +1 @@
+this file should not be removed.
From 7f83bd8460c0427d7ff75d16965d05c5e8285377 Mon Sep 17 00:00:00 2001
From: Arthur De Clerck <47596905+Rutradc@users.noreply.github.com>
Date: Mon, 21 Sep 2026 16:29:53 +0200
Subject: [PATCH 14/14] [ADD] chapter-11: add the sprinkles
---
estate/__manifest__.py | 2 +-
estate/models/estate_property.py | 1 +
estate/models/estate_property_offer.py | 3 ++
estate/models/estate_property_tag.py | 2 +
estate/models/estate_property_type.py | 9 ++++
estate/views/estate_property_offer_views.xml | 24 ++++++++++
estate/views/estate_property_tag_views.xml | 12 ++++-
estate/views/estate_property_type_views.xml | 46 ++++++++++++++++++++
estate/views/estate_property_views.xml | 31 ++++++-------
9 files changed, 113 insertions(+), 17 deletions(-)
diff --git a/estate/__manifest__.py b/estate/__manifest__.py
index 99f899646ae..fc109be364a 100644
--- a/estate/__manifest__.py
+++ b/estate/__manifest__.py
@@ -9,10 +9,10 @@
'security/ir.model.access.csv',
# 'security/security.xml',
+ 'views/estate_property_offer_views.xml',
'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',
]
}
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
index f1ca4f37bf2..bb7ca1f20cc 100644
--- a/estate/models/estate_property.py
+++ b/estate/models/estate_property.py
@@ -7,6 +7,7 @@
class EstateProperty(models.Model):
_name = "estate.property"
_description = "Estate property informations"
+ _order = "id desc"
name = fields.Char(required=True, string="Title")
description = fields.Text()
diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py
index 1c66695e225..dc82c444296 100644
--- a/estate/models/estate_property_offer.py
+++ b/estate/models/estate_property_offer.py
@@ -6,6 +6,7 @@
class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Offers for real estate properties"
+ _order = "price desc"
price = fields.Float()
status = fields.Selection(
@@ -19,6 +20,7 @@ class EstatePropertyOffer(models.Model):
property_id = fields.Many2one("estate.property", string="Property", required=True)
validity = fields.Integer(default=7, string="Validity (days)")
date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline", string="Deadline", readonly=False)
+ property_type_id = fields.Many2one(related="property_id.property_type_id")
@api.depends("create_date", "validity")
def _compute_date_deadline(self):
@@ -42,6 +44,7 @@ def accept_offer(self):
record.status = "accepted"
record.property_id.selling_price = record.price
record.property_id.buyer_id = record.partner_id
+ record.property_id.state = "offer_accepted"
return True
def refuse_offer(self):
diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py
index 63d013a7fb4..88346e4466f 100644
--- a/estate/models/estate_property_tag.py
+++ b/estate/models/estate_property_tag.py
@@ -4,8 +4,10 @@
class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Tags for estate properties"
+ _order = "name"
name = fields.Char(required=True)
+ color = fields.Integer()
_unique_name = models.Constraint(
'UNIQUE(name)',
diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py
index c3a90c25db1..dc9c3f7f9de 100644
--- a/estate/models/estate_property_type.py
+++ b/estate/models/estate_property_type.py
@@ -4,10 +4,19 @@
class EstatePropertyType(models.Model):
_name = "estate.property.type"
_description = "Estate property types"
+ _order = "sequence, name"
name = fields.Char(required=True)
+ sequence = fields.Integer('Sequence', default=1, help="Used to order types.")
+ property_ids = fields.One2many("estate.property", "property_type_id", string="Properties", copy=False)
+ offer_ids = fields.One2many(related="property_ids.offer_ids")
+ offer_count = fields.Integer(compute="_compute_offer_count")
_unique_name = models.Constraint(
'UNIQUE(name)',
'Each property type should have a unique name.'
)
+
+ def _compute_offer_count(self):
+ for record in self:
+ record.offer_count = len(record.offer_ids)
diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml
index 82e95a0b961..aae82bef0db 100644
--- a/estate/views/estate_property_offer_views.xml
+++ b/estate/views/estate_property_offer_views.xml
@@ -17,4 +17,28 @@
+
+
+ estate.property.offer.list
+ estate.property.offer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Property Offers
+ estate.property.offer
+ list
+ [('property_type_id', '=', active_id)]
+ {'default_property_type_id': active_id}
+
diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml
index 4dcd3674e34..05e9e2d50ff 100644
--- a/estate/views/estate_property_tag_views.xml
+++ b/estate/views/estate_property_tag_views.xml
@@ -3,6 +3,16 @@
Property Tags
estate.property.tag
- list,form
+ list
+
+
+
+ estate.property.tag.list
+ estate.property.tag
+
+
+
+
+
diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml
index 7b85f0ed65a..aea14e055a1 100644
--- a/estate/views/estate_property_type_views.xml
+++ b/estate/views/estate_property_type_views.xml
@@ -11,11 +11,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+