Skip to content

Commit 2e3d703

Browse files
committed
[IMP] real_estate: add UI polish, constraints, ordering, and smart buttons
- Added inline views, statusbar, widget options - Set SQL & Python constraints for data validity - Defined default ordering for all models - Conditional button visibility & field behaviors - Editable/color-coded list views with decorations - Stat button for offer count on property type
1 parent d63b5b2 commit 2e3d703

9 files changed

+220
-62
lines changed

real_estate/__manifest__.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
{
2-
'name': "Dream Homes",
3-
'version': '1.0',
4-
'depends': ['base'],
5-
'author': "Megha Tulsyani",
6-
'category': 'Ecommerce For Properties',
7-
'data': [
8-
9-
'security/ir.model.access.csv',
10-
'views/estate_property_views.xml',
11-
'views/estate_property_type_views.xml',
12-
"views/estate_property_tag_views.xml",
2+
"name": "Dream Homes",
3+
"version": "1.0",
4+
"depends": ["base"],
5+
"author": "Megha Tulsyani",
6+
"category": "Ecommerce For Properties",
7+
"data": [
8+
"security/ir.model.access.csv",
9+
"views/estate_property_views.xml",
1310
"views/estate_property_offer_views.xml",
14-
'views/estate_property_menus.xml'
11+
"views/estate_property_type_views.xml",
12+
"views/estate_property_tag_views.xml",
13+
"views/estate_property_menus.xml",
1514
],
16-
'license': 'LGPL-3',
17-
'installable': True,
18-
'application': True
15+
"license": "LGPL-3",
16+
"installable": True,
17+
"application": True,
1918
}

real_estate/models/estate_property.py

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from odoo import api, fields, models
22
from dateutil.relativedelta import relativedelta
3-
from odoo.exceptions import UserError
3+
from odoo.exceptions import UserError, ValidationError
4+
from odoo.tools.float_utils import float_compare, float_is_zero
45

56

67
class EstateProperty(models.Model):
78
_name = "estate.property"
89
_description = "This is a real estate module"
10+
_order = "id desc"
911

1012
name = fields.Char(required=True)
1113
description = fields.Text()
@@ -20,8 +22,8 @@ class EstateProperty(models.Model):
2022
garden_area = fields.Integer()
2123
active = fields.Boolean(default=True)
2224
buyer_id = fields.Many2one(comodel_name="res.partner", string="Buyer", copy=False)
23-
total_area = fields.Float(compute='_compute_total_area', string="Total Area" )
24-
best_offer = fields.Float(compute='_compute_best_price',string="Best Offer")
25+
total_area = fields.Float(compute="_compute_total_area", string="Total Area")
26+
best_offer = fields.Float(compute="_compute_best_price", string="Best Offer")
2527
date_availability = fields.Date(
2628
string="Availability From",
2729
default=lambda self: fields.Date.today() + relativedelta(months=3),
@@ -41,8 +43,7 @@ class EstateProperty(models.Model):
4143
comodel_name="estate.property.types", string="Property Type"
4244
)
4345
estate_property_tag_ids = fields.Many2many(
44-
comodel_name="estate.property.tags",
45-
string="Tags"
46+
comodel_name="estate.property.tags", string="Tags"
4647
)
4748
garden_orientation = fields.Selection(
4849
selection=[
@@ -65,13 +66,12 @@ class EstateProperty(models.Model):
6566
default="new",
6667
)
6768

68-
@api.depends('living_area','garden_area')
69+
@api.depends("living_area", "garden_area")
6970
def _compute_total_area(self):
7071
for record in self:
7172
record.total_area = record.living_area + record.garden_area
7273

73-
74-
@api.depends('estate_property_offer_ids')
74+
@api.depends("estate_property_offer_ids")
7575
def _compute_best_price(self):
7676
for record in self:
7777
prices = record.estate_property_offer_ids.mapped("price")
@@ -86,15 +86,34 @@ def _onchange_garden(self):
8686
self.garden_area = 0
8787
self.garden_orientation = False
8888

89-
9089
def action_sold(self):
9190
for record in self:
92-
if record.state == 'cancelled':
91+
if record.state == "cancelled":
9392
raise UserError("You cannot mark a cancelled property as sold.")
94-
record.state = 'sold'
93+
record.state = "sold"
9594

9695
def action_cancel(self):
9796
for record in self:
98-
if record.state == 'sold':
97+
if record.state == "sold":
9998
raise UserError("You cannot mark a sold property as cancelled.")
100-
record.state = 'cancelled'
99+
record.state = "cancelled"
100+
101+
_sql_constraints = [
102+
(
103+
"check_expected_price_positive",
104+
"CHECK(expected_price > 0)",
105+
"Expected price must be strictly positive.",
106+
),
107+
]
108+
109+
@api.constrains("expected_price", "selling_price")
110+
def _check_selling_price(self):
111+
for record in self:
112+
if float_is_zero(record.selling_price, precision_digits=2):
113+
continue
114+
115+
min_price = record.expected_price * 0.9
116+
if float_compare(record.selling_price, min_price, precision_digits=2) < 0:
117+
raise ValidationError(
118+
"Selling price must be at least 90% of the expected price."
119+
)

real_estate/models/estate_property_offers.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,65 @@
55
class EstatePropertyOffers(models.Model):
66
_name = "estate.property.offers"
77
_description = "Estate Property Offers"
8+
_order = "price desc"
89

910
price = fields.Float()
1011
partner_id = fields.Many2one(comodel_name="res.partner", required=True)
1112
property_id = fields.Many2one(comodel_name="estate.property", required=True)
12-
validity = fields.Integer(default='7')
13+
validity = fields.Integer(default="7")
14+
property_type_id = fields.Many2one(
15+
related="property_id.estate_property_type_id", store=True
16+
)
17+
1318
offer_deadline = fields.Date(
14-
compute='_compute_date_deadline',
15-
inverse='_inverse_date_deadline'
16-
)
19+
compute="_compute_date_deadline", inverse="_inverse_date_deadline"
20+
)
1721
status = fields.Selection(
18-
selection=[
19-
("accepted", "Accepted"),
20-
("refused", "Refused")
21-
]
22-
)
22+
selection=[("accepted", "Accepted"), ("refused", "Refused")]
23+
)
2324

24-
@api.depends('create_date', 'validity')
25+
@api.depends("create_date", "validity")
2526
def _compute_date_deadline(self):
2627
for record in self:
2728
if record.create_date and record.validity:
28-
record.offer_deadline = fields.Date.add(record.create_date, days=record.validity)
29+
record.offer_deadline = fields.Date.add(
30+
record.create_date, days=record.validity
31+
)
2932
else:
30-
record.offer_deadline = fields.Date.add(fields.Date.today(), days=record.validity)
33+
record.offer_deadline = fields.Date.add(
34+
fields.Date.today(), days=record.validity
35+
)
3136

3237
def _inverse_date_deadline(self):
3338
for record in self:
3439
if record.create_date and record.offer_deadline:
35-
record.validity = (record.offer_deadline - fields.Date.to_date(record.create_date)).days
40+
record.validity = (
41+
record.offer_deadline - fields.Date.to_date(record.create_date)
42+
).days
3643

3744
def action_accept_offer(self):
3845
for offer in self:
39-
accepted_offer = self.env['estate.property.offers'].search([
40-
('property_id', '=', offer.property_id.id),
41-
('status', '=', 'accepted')
42-
])
46+
accepted_offer = self.env["estate.property.offers"].search(
47+
[
48+
("property_id", "=", offer.property_id.id),
49+
("status", "=", "accepted"),
50+
]
51+
)
4352
if accepted_offer:
4453
raise UserError("Only one offer can be accepted per property.")
45-
offer.status = 'accepted'
54+
offer.status = "accepted"
4655
offer.property_id.selling_price = offer.price
4756
offer.property_id.buyer_id = offer.partner_id
48-
offer.property_id.state = 'offer_accepted'
57+
offer.property_id.state = "offer_accepted"
4958

5059
def action_refuse_offer(self):
5160
for offer in self:
52-
offer.status = 'refused'
61+
offer.status = "refused"
62+
63+
_sql_constraints = [
64+
(
65+
"check_offer_price_positive",
66+
"CHECK(price > 0)",
67+
"Offer price must be strictly positive.",
68+
)
69+
]

real_estate/models/estate_property_tags.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,15 @@
44
class EstatePropertyTags(models.Model):
55
_name = "estate.property.tags"
66
_description = "Estate Property Tags"
7+
_order = "name"
78

89
name = fields.Char(required=True)
10+
color = fields.Integer()
11+
12+
_sql_constraints = [
13+
(
14+
"unique_tag_name",
15+
"UNIQUE(name)",
16+
"This property tag already exits, create a unique one.",
17+
)
18+
]
Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
1-
from odoo import fields, models
1+
from odoo import api, fields, models
22

33

44
class EstatePropertyTypes(models.Model):
55
_name = "estate.property.types"
66
_description = "Estate Property Types"
7+
_order = "sequence, name"
78

89
name = fields.Char(required=True)
10+
sequence = fields.Integer(default=1)
11+
offer_ids = fields.One2many(
12+
comodel_name="estate.property.offers", inverse_name="property_type_id"
13+
)
14+
offer_count = fields.Integer(
15+
string="Number of Offers", compute="_compute_offer_count"
16+
)
17+
property_ids = fields.One2many(
18+
"estate.property", "estate_property_type_id", string="Properties"
19+
)
20+
_sql_constraints = [
21+
(
22+
"unique_type_name",
23+
"UNIQUE(name)",
24+
"This property type already exits, create a unique one.",
25+
)
26+
]
927

28+
@api.depends("offer_ids")
29+
def _compute_offer_count(self):
30+
for record in self:
31+
record.offer_count = len(record.offer_ids)

real_estate/views/estate_property_offer_views.xml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<odoo>
3+
<record id="estate_property_offer_action" model="ir.actions.act_window">
4+
<field name="name">estate.property.offer.action</field>
5+
<field name="res_model">estate.property.offers</field>
6+
<field name="view_mode">list,form</field>
7+
<field name="domain">[('property_type_id', '=', active_id)]</field>
8+
</record>
9+
310
<record id="estate_property_offer_view" model="ir.ui.view">
411
<field name="name">estate.property.offer.list</field>
512
<field name="model">estate.property.offers</field>
613
<field name="arch" type="xml">
7-
<list editable="bottom">
14+
<list editable="bottom"
15+
decoration-danger="status == 'refused'"
16+
decoration-success="status == 'accepted'">
817
<field name="price" />
918
<field name="partner_id" />
1019
<field name="status" />
1120
<field name="validity" string="Validity(Days)" />
1221
<field name="offer_deadline" />
13-
<button name="action_accept_offer" string="Accept" type="object" icon="fa-check" invisible="status in ('accepted','refused')"/>
14-
<button name="action_refuse_offer" string="Refuse" type="object" icon="fa-close" invisible="status in ('accepted','refused')"/>
22+
<button name="action_accept_offer" string="Accept" type="object" icon="fa-check"
23+
invisible="status in ('accepted','refused')" />
24+
<button name="action_refuse_offer" string="Refuse" type="object" icon="fa-close"
25+
invisible="status in ('accepted','refused')" />
1526
</list>
1627
</field>
1728
</record>

real_estate/views/estate_property_tag_views.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,14 @@
1515
</field>
1616
</record>
1717

18+
<record id="view_estate_property_tag_list" model="ir.ui.view">
19+
<field name="name">estate.property.tag.list</field>
20+
<field name="model">estate.property.tags</field>
21+
<field name="arch" type="xml">
22+
<list editable="bottom">
23+
<field name="name" />
24+
</list>
25+
</field>
26+
</record>
27+
1828
</odoo>

real_estate/views/estate_property_type_views.xml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,52 @@
1414
</field>
1515
</record>
1616

17+
<record id="view_estate_property_type_form" model="ir.ui.view">
18+
<field name="name">estate.property.type.form</field>
19+
<field name="model">estate.property.types</field>
20+
<field name="arch" type="xml">
21+
<form string="Property Type">
22+
<sheet>
23+
<div class="oe_button_box" name="button_box">
24+
<button name="%(estate_property_offer_action)d"
25+
type="action"
26+
class="oe_stat_button"
27+
icon="fa-list">
28+
<div>
29+
<field name="offer_count" widget="statinfo" string="Offers" />
30+
</div>
31+
</button>
32+
</div>
33+
<group>
34+
<h1>
35+
<field name="name" />
36+
</h1>
37+
</group>
38+
<notebook>
39+
<page string="Properties">
40+
<field name="property_ids">
41+
<list>
42+
<field name="name" />
43+
<field name="expected_price" />
44+
<field name="state" />
45+
</list>
46+
</field>
47+
</page>
48+
</notebook>
49+
</sheet>
50+
</form>
51+
</field>
52+
</record>
53+
54+
<record id="view_estate_property_type_list" model="ir.ui.view">
55+
<field name="name">estate.property.type.list</field>
56+
<field name="model">estate.property.types</field>
57+
<field name="arch" type="xml">
58+
<list>
59+
<field name="sequence" widget="handle" />
60+
<field name="name" />
61+
</list>
62+
</field>
63+
</record>
64+
1765
</odoo>

0 commit comments

Comments
 (0)