Skip to content

Commit d74ba13

Browse files
committed
[IMP] estate: integrate website and kanban views, add module settings
- Add kanban view - Add a new settings view for the module - Reorganized menu items for clarity - Enabled website access to real estate content - Add invoice stat button in property form view - Allow users to add a property image
1 parent b128776 commit d74ba13

19 files changed

+540
-45
lines changed

estate/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from . import models
2+
from . import controllers

estate/__manifest__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
'name': 'Real Estate',
33
'version': '1.0',
4-
'depends': ['base'],
4+
'depends': ['base', 'mail', 'website'],
55
'author': 'Aryan Donga (ardo)',
66
'description': 'Real Estate advertisement module',
77
'application': True,
@@ -17,8 +17,10 @@
1717
'views/estate_property_tag_views.xml',
1818
'views/estate_property_offer_views.xml',
1919
'views/estate_property_views.xml',
20-
'views/estate_menus.xml',
20+
'views/estate_property_web_views.xml',
2121
'views/res_users_views.xml',
22+
'views/res_config_settings_views.xml',
23+
'views/estate_menus.xml',
2224
'data/estate.property.type.csv',
2325
],
2426
'demo': ['data/estate_property_demo.xml', 'data/estate_property_offer_demo.xml'],

estate/controllers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import estate

estate/controllers/estate.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from odoo.http import Controller, request, route
2+
3+
4+
class EstateController(Controller):
5+
@route(['/properties', '/properties/page/<int:page>'], auth='public', website=True)
6+
def fetch_properties(self, page=1, **kwargs):
7+
search_domains = [('state', 'in', ['new', 'offer_received'])]
8+
query_params = {}
9+
10+
min_price_str = kwargs.get('min_price')
11+
max_price_str = kwargs.get('max_price')
12+
date_str = kwargs.get('date')
13+
14+
if date_str:
15+
search_domains.append(('create_date', '>=', date_str))
16+
query_params['date'] = date_str
17+
18+
if min_price_str:
19+
try:
20+
float(min_price_str)
21+
search_domains.append(('expected_price', '>=', min_price_str))
22+
query_params['min_price'] = min_price_str
23+
except ValueError:
24+
pass
25+
26+
if max_price_str:
27+
try:
28+
float(max_price_str)
29+
search_domains.append(('expected_price', '<=', max_price_str))
30+
query_params['max_price'] = max_price_str
31+
except ValueError:
32+
pass
33+
34+
properties = (
35+
request.env['estate.property']
36+
.sudo()
37+
.search(search_domains, order='create_date desc')
38+
)
39+
40+
items_per_page = 10
41+
total_items = len(properties)
42+
start_index = items_per_page * (page - 1)
43+
page_items = properties[start_index : start_index + items_per_page]
44+
45+
pager = request.website.pager(
46+
url='/properties',
47+
total=total_items,
48+
page=page,
49+
step=items_per_page,
50+
url_args=query_params,
51+
)
52+
53+
return request.render(
54+
'estate.property_template',
55+
{
56+
'properties': page_items,
57+
'pager': pager,
58+
'date': date_str,
59+
'min_price': min_price_str,
60+
'max_price': max_price_str,
61+
},
62+
)
63+
64+
@route('/property/<int:property_id>', auth='public', website=True)
65+
def property_detail(self, property_id):
66+
estate = request.env['estate.property'].sudo().browse(property_id)
67+
if not estate.exists():
68+
return request.not_found()
69+
70+
return request.render('estate.property_details_template', {'property': estate})

estate/data/estate_property_demo.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,19 @@
6565
})
6666
]"/>
6767
</record>
68+
69+
<record id="demo_property_4" model="estate.property">
70+
<field name="name">Forest Cabin</field>
71+
<field name="state">new</field>
72+
<field name="description">A cabin in the woods</field>
73+
<field name="postcode">524321</field>
74+
<field name="date_availability" eval="datetime(1980, 1, 1)"/>
75+
<field name="expected_price">100000</field>
76+
<field name="selling_price">120000</field>
77+
<field name="bedrooms">1</field>
78+
<field name="living_area">10</field>
79+
<field name="facades">4</field>
80+
<field name="garage">False</field>
81+
<field name="property_type_id" ref="estate.estate_property_type_residential"/>
82+
</record>
6883
</odoo>

estate/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
from . import estate_property_offer
44
from . import estate_property
55
from . import res_users
6+
from . import res_config_settings

estate/models/estate_property.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
class EstateProperty(models.Model):
88
_name = 'estate.property'
99
_description = 'An individual estate property listing'
10+
_inherit = ['mail.thread', 'mail.activity.mixin']
1011
_order = 'id desc'
1112
_sql_constraints = [
1213
(
@@ -30,11 +31,12 @@ def _check_selling_price(self):
3031
):
3132
raise ValidationError(
3233
'The selling price must be greater than 90% of the expected price.'
33-
'You must reduce the expected price if you want to accept this offer'
34+
'You must reduce the expected price to accept this offer'
3435
)
3536

3637
name = fields.Char('Title', required=True, default='Unknown')
3738
description = fields.Text('Description')
39+
image = fields.Image(string='Property Image', max_width=2048, max_height=2048)
3840
postcode = fields.Char('Postcode')
3941
date_availability = fields.Date(
4042
'Available From',
@@ -65,6 +67,7 @@ def _check_selling_price(self):
6567
],
6668
'Status',
6769
default='new',
70+
tracking=True,
6871
copy=False,
6972
required=True,
7073
)
@@ -116,7 +119,8 @@ def _onchange_offer_ids(self):
116119

117120
if self.state != 'new':
118121
raise UserError(
119-
'You cannot modify offers when the property is not in New or Offer Received state.'
122+
'You cannot modify offers when the property'
123+
' is not in New or Offer Received state.'
120124
)
121125

122126
def action_cancel(self):

estate/models/estate_property_offer.py

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -80,41 +80,39 @@ def action_refuse(self):
8080
for record in self:
8181
record_property = record.property_id
8282
property_state = record_property.state
83-
8483
if property_state in ['sold', 'cancelled']:
8584
raise UserError(
8685
'You cannot refuse an offer on a sold or cancelled property.'
8786
)
8887
if property_state == 'new':
89-
raise ValidationError(
90-
'You cannot refuse an offer on a new property. (New properties are not supposed to have offers.)'
91-
)
88+
raise ValidationError('You cannot refuse an offer on a new property.')
9289
if record.status == 'accepted':
9390
raise UserError('You cannot refuse an already accepted offer.')
9491

9592
record.status = 'refused'
9693
return True
9794

9895
@api.model_create_multi
99-
def create(self, vals_list):
100-
curr_max_price = 0
101-
estate_property_model = self.env['estate.property']
102-
103-
for vals in vals_list:
104-
estate_property = estate_property_model.browse(vals.get('property_id'))
105-
106-
if not estate_property.exists():
107-
raise ValidationError('The specified property does not exist.')
108-
109-
if estate_property.state in ['sold', 'cancelled']:
110-
raise UserError(
111-
'You cannot create an offer on a sold or cancelled property.'
112-
)
96+
def create(self, offers):
97+
estate = self.env['estate.property'].browse(offers[0].get('property_id'))
98+
curr_max_price = estate.best_price or 0.0
99+
100+
if not estate.exists():
101+
raise ValidationError('The specified property does not exist.')
102+
if estate.state in ['sold', 'cancelled']:
103+
raise UserError(
104+
'You cannot create an offer on a sold or cancelled property.'
105+
)
106+
if estate.state == 'offer_accepted':
107+
raise UserError(
108+
'You cannot create an offer on a property with an accepted offer.'
109+
)
110+
for vals in offers:
113111
if curr_max_price >= vals['price']:
114112
raise UserError(
115113
'The offer price must be higher than the current best price.'
116114
)
117115
curr_max_price = max(curr_max_price, vals['price'])
118116

119-
estate_property.state = 'offer_received'
120-
return super().create(vals_list)
117+
estate.state = 'offer_received'
118+
return super().create(offers)

estate/models/res_config_settings.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from odoo import fields, models
2+
3+
4+
class ResConfigSettings(models.TransientModel):
5+
_inherit = 'res.config.settings'
6+
7+
module_estate_account = fields.Boolean('Enable Invoicing')
8+
9+
module_estate_auction = fields.Boolean('Automated Auctions')

estate/static/description/icon.png

19.6 KB
Loading

estate/views/estate_menus.xml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
<odoo>
22
<menuitem id="estate_property_menu_root" name="Real Estate">
3-
<menuitem id="estate_property_menu_advertisements" name="Advertisements">
3+
<menuitem id="estate_property_menu_advertisements" name="Advertisements" sequence="1">
44
<menuitem id="estate_property_action_properties" action="estate_property_action"/>
55
</menuitem>
66

7-
<menuitem id="estate_property_menu_settings" name="Settings" groups="estate.estate_group_manager">
8-
<menuitem id="estate_property_action_property_types" action="estate_property_type_action"/>
9-
<menuitem id="estate_property_action_property_tags" action="estate_property_tag_action"/>
7+
<menuitem id="estate_menu_website" name="Website" sequence="2">
8+
<menuitem id="estate_property_menu_website" name="View Properties" action="open_url_action"/>
9+
</menuitem>
10+
11+
<menuitem id="estate_property_menu_settings" name="Configuration" groups="estate.estate_group_manager"
12+
sequence="3">
13+
<menuitem id="estate_settings" action="estate_settings_action"/>
14+
<menuitem id="properties_category" name="Properties">
15+
<menuitem id="estate_property_action_property_types" action="estate_property_type_action"/>
16+
<menuitem id="estate_property_action_property_tags" action="estate_property_tag_action"/>
17+
</menuitem>
1018
</menuitem>
1119
</menuitem>
1220
</odoo>

estate/views/estate_property_views.xml

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
<record id="estate_property_action" model="ir.actions.act_window">
33
<field name="name">Properties</field>
44
<field name="res_model">estate.property</field>
5-
<field name="view_mode">list,form</field>
5+
<field name="view_mode">list,form,kanban</field>
66
<field name="context">{'search_default_available': True}</field>
77
</record>
88

9+
<record id="open_url_action" model="ir.actions.act_url">
10+
<field name="name">Open External Website</field>
11+
<field name="url">/properties</field>
12+
<field name="target">new</field>
13+
</record>
14+
915
<record id="estate_property_view_list" model="ir.ui.view">
1016
<field name="name">estate.property.view.list</field>
1117
<field name="model">estate.property</field>
@@ -35,21 +41,27 @@
3541
<field name="model">estate.property</field>
3642
<field name="arch" type="xml">
3743
<form string="Properties">
44+
<chatter/>
3845
<header>
39-
<button name="action_sold" type="object" string="Sold" invisible="state in ['sold', 'cancelled']"/>
46+
<button name="action_sold" type="object" string="Sold" invisible="state != 'offer_accepted'"
47+
class="btn btn-primary"/>
48+
<button name="action_sold" type="object" string="Sold"
49+
invisible="state in ['sold', 'cancelled', 'offer_accepted']"/>
4050
<button name="action_cancel" type="object" string="Cancel"
4151
invisible="state in ['sold', 'cancelled']"/>
4252
<field name="state" widget="statusbar" statusbar_visible="new,offer_received,offer_accepted,sold"/>
4353
</header>
4454
<sheet>
45-
<div class="oe_title">
55+
<field name="image" widget="image" class="oe_avatar" nolabel='1'
56+
options="{'zoom': True, 'size': [0,270]}"/>
57+
<div class="oe_title pb-2">
4658
<h1>
4759
<field name="name"/>
4860
</h1>
49-
<group>
50-
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
51-
</group>
5261
</div>
62+
<group class="mt-4">
63+
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
64+
</group>
5365
<group>
5466
<group>
5567
<field name="property_type_id" options="{'no_create':True, 'no_open':True}"/>
@@ -65,15 +77,17 @@
6577
<notebook>
6678
<page name="description" string="Description">
6779
<group>
68-
<field name="description"/>
69-
<field name="bedrooms"/>
70-
<field name="living_area"/>
71-
<field name="facades"/>
72-
<field name="garage"/>
73-
<field name="garden"/>
74-
<field name="garden_area" invisible="not garden"/>
75-
<field name="garden_orientation" invisible="not garden"/>
76-
<field name="total_area"/>
80+
<group string="Property Details">
81+
<field name="description"/>
82+
<field name="bedrooms"/>
83+
<field name="living_area"/>
84+
<field name="facades"/>
85+
<field name="garage"/>
86+
<field name="garden"/>
87+
<field name="garden_area" invisible="not garden"/>
88+
<field name="garden_orientation" invisible="not garden"/>
89+
<field name="total_area"/>
90+
</group>
7791
</group>
7892
</page>
7993
<page name="offers" string="Offers">
@@ -117,4 +131,28 @@
117131
</search>
118132
</field>
119133
</record>
134+
135+
<record id="estate_property_view_kanban" model="ir.ui.view">
136+
<field name="name">estate.property.view.kanban</field>
137+
<field name="model">estate.property</field>
138+
<field name="arch" type="xml">
139+
<kanban default_group_by="state" records_draggable="false">
140+
<field name="state"/>
141+
<templates>
142+
<t t-name="card">
143+
<field name="name" class="fw-bolder"/>
144+
<div> Expected Price : <field name="expected_price"/>
145+
</div>
146+
<div t-if="record.state.raw_value === 'offer_received'">
147+
Best Price : <field name="best_price"/>
148+
</div>
149+
<div t-if="['offer_accepted', 'sold'].includes(record.state.raw_value)">
150+
Selling Price: <field name="selling_price"/>
151+
</div>
152+
<field name="tag_ids" widget="many2many_tags"/>
153+
</t>
154+
</templates>
155+
</kanban>
156+
</field>
157+
</record>
120158
</odoo>

0 commit comments

Comments
 (0)