Skip to content

Commit f96eeb3

Browse files
committed
[IMP] estate: interact with external modules and code cleanup
- Implemented cross-module field access and method calls to demonstrate interaction with other modules. - Cleaned codebase to fix linter errors and improve readability.
1 parent 8235948 commit f96eeb3

12 files changed

+216
-114
lines changed

estate/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from . import models
1+
from . import models

estate/__manifest__.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
{
2-
'name': 'Real Estate',
3-
'category': 'All',
4-
'summary': 'Demo app for estate',
5-
'description': "This is the demo app ",
6-
'installable': True,
7-
'depends': ['base'],
8-
'application': True,
9-
'auto_install': False,
10-
'data' : [
11-
'security/ir.model.access.csv',
12-
'views/estate_property_offer_views.xml',
13-
'views/estate_property_views.xml',
14-
'views/estate_menus.xml',
15-
'views/estate_settings_views.xml',
16-
'views/estate_property_tags_views.xml',
17-
'views/res_users_views.xml'
18-
]
19-
}
2+
"name": "Real Estate",
3+
"category": "All",
4+
"summary": "Demo app for estate",
5+
"description": "This is the demo app ",
6+
"installable": True,
7+
"depends": ["base"],
8+
"application": True,
9+
"auto_install": False,
10+
"license": "AGPL-3",
11+
"data": [
12+
"security/ir.model.access.csv",
13+
"views/estate_property_offer_views.xml",
14+
"views/estate_property_views.xml",
15+
"views/estate_settings_views.xml",
16+
"views/estate_property_tags_views.xml",
17+
"views/res_users_views.xml",
18+
"views/estate_menus.xml",
19+
],
20+
}

estate/models/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
from . import estate_property_type
33
from . import estate_property_tag
44
from . import estate_property_offer
5-
from . import inherited_model
5+
from . import inherited_model

estate/models/estate_property.py

Lines changed: 61 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
from dateutil.relativedelta import relativedelta
22
from odoo import fields, models, api, _
33
from odoo.exceptions import UserError, ValidationError
4-
from odoo.tools.float_utils import float_compare, float_is_zero, float_round
5-
6-
4+
from odoo.tools.float_utils import float_compare, float_is_zero
75

86

97
class RecurringPlan(models.Model):
108
_name = "estate.property"
119
_description = "estate property revenue plans"
12-
_order = "id desc" # For ordering in ascending opr descending order one more way to do so is from view like: <list default_order="date desc">
10+
_order = "id desc" # For ordering in ascending opr descending order one more way to do so is from view like: <list default_order="date desc">
1311

1412
name = fields.Char(required=True)
1513
description = fields.Text(string="Description")
1614
postcode = fields.Char(string="Postcode")
17-
date_availability = fields.Date(string="Available From", copy=False, default=lambda self: fields.Date.today() + relativedelta(months=3))
15+
date_availability = fields.Date(
16+
string="Available From",
17+
copy=False,
18+
default=lambda self: fields.Date.today() + relativedelta(months=3),
19+
)
1820
expected_price = fields.Float(string="Expected Price", required=True)
1921
selling_price = fields.Float(string="Selling Price", readonly=True, copy=False)
2022
bedrooms = fields.Integer(string="Bedrooms", default=2)
@@ -25,94 +27,104 @@ class RecurringPlan(models.Model):
2527
garden_area = fields.Integer(string="Garden Area (sqm)")
2628
garden_orientation = fields.Selection(
2729
selection=[
28-
('north', 'North'),
29-
('south', 'South'),
30-
('east', 'East'),
31-
('west', 'West')
30+
("north", "North"),
31+
("south", "South"),
32+
("east", "East"),
33+
("west", "West"),
3234
],
33-
string='Garden Orientation',
34-
help="Orientation of the garden relative to the property"
35+
string="Garden Orientation",
36+
help="Orientation of the garden relative to the property",
3537
)
3638
active = fields.Boolean(default=True)
3739
state = fields.Selection(
3840
selection=[
39-
('new', 'NEW'),
40-
('offer_received', 'Offer Received'),
41-
('offer_accepted', 'Offer Accepted'),
42-
('sold', 'Sold'),
43-
('cancelled', 'Cancelled')
41+
("new", "NEW"),
42+
("offer_received", "Offer Received"),
43+
("offer_accepted", "Offer Accepted"),
44+
("sold", "Sold"),
45+
("cancelled", "Cancelled"),
4446
],
4547
string="Status",
46-
required=True,
47-
default='new',
48+
required=True,
49+
default="new",
4850
copy=False,
4951
# readonly=True
50-
5152
)
5253

5354
total_area = fields.Integer(string="Total Area", compute="_compute_total_area")
5455
best_price = fields.Float(string="Best Offer", compute="_compute_best_price")
5556

5657
property_type_id = fields.Many2one("estate.property.type", string="Property Type")
5758
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)
58-
user_id = fields.Many2one("res.users", string="Salesman", copy=False, default=lambda self: self.env.user)
59-
60-
tag_id = fields.Many2many("estate.property.tag", string="Tags",copy=False)
59+
user_id = fields.Many2one(
60+
"res.users", string="Salesman", copy=False, default=lambda self: self.env.user
61+
)
6162

62-
offer_id = fields.One2many("estate.property.offer","property_id", string="Offer")
63+
tag_id = fields.Many2many("estate.property.tag", string="Tags", copy=False)
6364

65+
offer_id = fields.One2many("estate.property.offer", "property_id", string="Offer")
6466

6567
_sql_constraints = [
66-
('check_expected_price', 'CHECK(expected_price > 0)','The Expected price of a property should be strictly positive.'),
67-
('check_selling_price', 'CHECK(selling_price IS NULL OR selling_price >= 0)', 'Selling price must be positive when set.')
68+
(
69+
"check_expected_price",
70+
"CHECK(expected_price > 0)",
71+
"The Expected price of a property should be strictly positive.",
72+
),
73+
(
74+
"check_selling_price",
75+
"CHECK(selling_price IS NULL OR selling_price >= 0)",
76+
"Selling price must be positive when set.",
77+
),
6878
]
6979

70-
71-
@api.depends('living_area', 'garden_area')
80+
@api.depends("living_area", "garden_area")
7281
def _compute_total_area(self):
7382
for area in self:
7483
area.total_area = area.living_area + area.garden_area
7584

76-
77-
@api.depends('offer_id.price')
85+
@api.depends("offer_id.price")
7886
def _compute_best_price(self):
7987
for record in self:
8088
# Get all prices from offer_ids
81-
offer_prices = record.offer_id.mapped('price')
89+
offer_prices = record.offer_id.mapped("price")
8290
record.best_price = max(offer_prices) if offer_prices else 0.0
8391

84-
8592
@api.onchange("garden")
8693
def _onchange_garden(self):
87-
if(self.garden):
88-
self.garden_orientation = 'north'
94+
if self.garden:
95+
self.garden_orientation = "north"
8996
self.garden_area = 10
9097
else:
9198
self.garden_orientation = False
9299
self.garden_area = 0
93-
94-
def action_property_sold(self):
95-
for prop in self:
96-
if prop.state == 'cancelled':
100+
101+
def action_property_sold(self):
102+
for prop in self:
103+
if prop.state == "cancelled":
97104
raise UserError(_("Cancelled properties cannot be sold."))
98-
prop.state = 'sold'
99-
return True
105+
prop.state = "sold"
106+
return True
100107

101108
def action_property_cancel(self):
102109
for prop in self:
103-
if prop.state == 'sold':
110+
if prop.state == "sold":
104111
raise UserError(_("Sold properties cannot be cancelled."))
105-
prop.state = 'cancelled'
112+
prop.state = "cancelled"
106113
return True
107114

108-
@api.constrains('selling_price','expected_price')
115+
@api.constrains("selling_price", "expected_price")
109116
def _check_selling_price(self):
110117
for record in self:
111118
if float_is_zero(record.selling_price, precision_digits=2):
112119
continue
113120
min_allowed_price = record.expected_price * 0.9
114121

115-
if float_compare(record.selling_price, min_allowed_price, precision_digits=2) < 0:
122+
if (
123+
float_compare(
124+
record.selling_price, min_allowed_price, precision_digits=2
125+
)
126+
< 0
127+
):
116128
raise ValidationError(
117129
"Selling price cannot be lower than 90% of the expected price. "
118130
f"(Minimum allowed: {min_allowed_price:.2f})"
@@ -121,5 +133,9 @@ def _check_selling_price(self):
121133
@api.ondelete(at_uninstall=False)
122134
def _unlink_except_state_not_new(self):
123135
for rec in self:
124-
if rec.state not in ['new', 'cancelled']:
125-
raise UserError(_("You cannot delete a property unless its state is 'New' or 'Cancelled'."))
136+
if rec.state not in ["new", "cancelled"]:
137+
raise UserError(
138+
_(
139+
"You cannot delete a property unless its state is 'New' or 'Cancelled'."
140+
)
141+
)

estate/models/estate_property_offer.py

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,42 @@ class EstatePropertyOffer(models.Model):
1010

1111
price = fields.Float(string="Price")
1212
status = fields.Selection(
13-
selection = [
14-
('accepted','Accepted'),
15-
('refused','Refused')
16-
], copy=False,string='Status'
13+
selection=[("accepted", "Accepted"), ("refused", "Refused")],
14+
copy=False,
15+
string="Status",
1716
)
1817

1918
validity = fields.Integer(string="Validity", default=7)
20-
date_deadline = fields.Date(string="Deadline", compute="_compute_deadline_date", inverse="_inverse_deadline_date")
19+
date_deadline = fields.Date(
20+
string="Deadline",
21+
compute="_compute_deadline_date",
22+
inverse="_inverse_deadline_date",
23+
)
2124

22-
partner_id = fields.Many2one("res.partner", string="Partner",copy=False,required=True)
23-
property_id = fields.Many2one("estate.property",copy=False,required=True)
24-
property_type_id = fields.Many2one("estate.property.type", related="property_id.property_type_id",store=True,readonly=False)
25-
_sql_constraints =[
26-
('check_offer_price', 'CHECK(price > 0)', 'The Offer price must be strictly positive')
25+
partner_id = fields.Many2one(
26+
"res.partner", string="Partner", copy=False, required=True
27+
)
28+
property_id = fields.Many2one("estate.property", copy=False, required=True)
29+
property_type_id = fields.Many2one(
30+
"estate.property.type",
31+
related="property_id.property_type_id",
32+
store=True,
33+
readonly=False,
34+
)
35+
_sql_constraints = [
36+
(
37+
"check_offer_price",
38+
"CHECK(price > 0)",
39+
"The Offer price must be strictly positive",
40+
)
2741
]
2842

29-
30-
31-
@api.depends('create_date','validity')
43+
@api.depends("create_date", "validity")
3244
def _compute_deadline_date(self):
3345
for offer in self:
3446
create_dt = offer.create_date or fields.Date.today()
3547
offer.date_deadline = create_dt + relativedelta(days=offer.validity)
3648

37-
3849
def _inverse_deadline_date(self):
3950
for offer in self:
4051
if offer.date_deadline:
@@ -48,46 +59,46 @@ def action_accept(self):
4859
for offer in self:
4960
# Allow only if property has no accepted offer
5061
if offer.property_id.buyer_id:
51-
raise UserError(_("An offer has already been accepted for this property."))
62+
raise UserError(
63+
_("An offer has already been accepted for this property.")
64+
)
5265
# Set buyer and selling price
5366
offer.property_id.buyer_id = offer.partner_id
5467
offer.property_id.selling_price = offer.price
55-
offer.status = 'accepted'
56-
offer.property_id.state = 'offer_accepted'
68+
offer.status = "accepted"
69+
offer.property_id.state = "offer_accepted"
5770
# Setting remaining Offer as refused
5871
other_offers = offer.property_id.offer_id - offer
5972
# for other in other_offers: # -----> Normal for loop logic
6073
# other.status = 'refused'
61-
other_offers.write({'status': 'refused'}) # -----> Odoo ORM Method
74+
other_offers.write({"status": "refused"}) # -----> Odoo ORM Method
6275

6376
return True
6477

6578
def action_refused(self):
6679
for offer in self:
67-
offer.status = 'refused'
80+
offer.status = "refused"
6881
return True
6982

7083
@api.model_create_multi
7184
def create(self, vals_list):
7285
new_records = []
73-
print(f"vals_list---------> {vals_list}")
7486
for vals in vals_list:
75-
property_id = vals.get('property_id')
87+
property_id = vals.get("property_id")
7688

77-
property_rec = self.env['estate.property'].browse(property_id)
89+
property_rec = self.env["estate.property"].browse(property_id)
7890
# Business logic: set property state
79-
if property_rec.state == 'new':
80-
property_rec.state = 'offer_received'
81-
print("DEBUG: Property state set to 'offer_received'")
91+
if property_rec.state == "new":
92+
property_rec.state = "offer_received"
8293

8394
# Business logic: validate price
84-
if 'price' in vals and vals['price'] < property_rec.best_price:
85-
raise exceptions.ValidationError("Offer price must be higher than existing offers.")
95+
if "price" in vals and vals["price"] < property_rec.best_price:
96+
raise exceptions.ValidationError(
97+
"Offer price must be higher than existing offers."
98+
)
8699

87100
new_records.append(vals)
88101

89102
return super().create(new_records)
90103

91-
92-
93104
# vals_list---------> [{'partner_id': 23, 'price': 100, 'validity': 7, 'date_deadline': '2025-07-16', 'property_id': 19}]

estate/models/estate_property_tag.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
from odoo import fields, models
22

3+
34
class EstatePropertyTag(models.Model):
45
_name = "estate.property.tag"
56
_description = "Tags of Estate property "
67
_order = "name asc"
78

8-
name = fields.Char(required=True, string='Name')
9+
name = fields.Char(required=True, string="Name")
910
color = fields.Integer(default=3)
1011

11-
12-
13-
_sql_constraints=[
14-
('unique_property_tag_name', 'UNIQUE(name)', 'A property tag name must be unique.'),
15-
]
12+
_sql_constraints = [
13+
(
14+
"unique_property_tag_name",
15+
"UNIQUE(name)",
16+
"A property tag name must be unique.",
17+
),
18+
]

0 commit comments

Comments
 (0)