Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added estate /__init__.py
Empty file.
Empty file added estate /__manifest__.py
Empty file.
1 change: 1 addition & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
20 changes: 20 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
'name': 'Odoo Tutorial Real Estate',
'category': 'Real Estate',
'version': '19.0.1.0',
'author': 'Hazei',
'license': 'LGPL-3',
'summary': 'Real Estate Management Tutorial',
'depends': [
'base',
],
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_type.xml',
'views/estate_property_tags_view.xml',
'views/menu_views.xml',
],
'installable': True,
'application': True,
Comment on lines +17 to +19

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
],
'installable': True,
'application': True,
],
'application': True,

default value for installable is already True

}
4 changes: 4 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import estate_property
from . import estate_property_offer
from . import estate_property_tags
from . import estate_property_type
102 changes: 102 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from datetime import date

from dateutil.relativedelta import relativedelta

from odoo import api, fields, models
from odoo.exceptions import UserError
from odoo.tools import float_compare


class EstateProperty(models.Model):
_name = "estate.property"
_description = "Real Estate Property"
_order = "id desc"

name = fields.Char(string="Title", required=True)
property_type = fields.Selection(
string='Property Type',
selection=[('house', 'House'), ('apartment', 'Apartment')])
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(copy=False, readonly=True, default=lambda self: date.today() + relativedelta(months=3))
expected_price = fields.Float("Expected Price", required=True)
selling_price = fields.Float("Selling Price", readonly=True, copy=False)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer(string='Living Area (m2)')
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer(string='Garden Area (m2)')
garden_orientation = fields.Selection(
string='Garden Orientation',
selection=[('north', 'North'), ('south', 'South'),
('east', 'East'), ('west', 'West')])
active = fields.Boolean(default=True)
state = fields.Selection(
copy=False,
readonly=True,
default='new',
string='States',
selection=[
('new', 'New'),
('offer_received', 'Offer Received'),
('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled')]
)
Comment on lines +40 to +46

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a constant, so it should go outside the class and be given a more consistent name like PROPERTY_STATES


def action_set_sold(self):
if self.state != "cancelled":
self.state = "sold"
else:
raise UserError("A cancelled property can not be sold")
return True
Comment on lines +48 to +53

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def action_set_sold(self):
if self.state != "cancelled":
self.state = "sold"
else:
raise UserError("A cancelled property can not be sold")
return True
def action_set_sold(self):
if self.state == "cancelled":
raise UserError("A cancelled property can not be sold")
self.state = "sold"
return True

More concise and extendable

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also what would happen here if self contains more than 1 property ?


def action_set_cancelled(self):
if (self.state != "sold"):
self.state = "cancelled"
else:
raise UserError("A sold property can not be cancelled")
return True
Comment on lines +48 to +60

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per attribute ordering (coding guidelines), this should go lower in the file

Comment on lines +55 to +60

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def action_set_cancelled(self):
if (self.state != "sold"):
self.state = "cancelled"
else:
raise UserError("A sold property can not be cancelled")
return True
def action_set_cancelled(self):
if self.state == "sold":
raise UserError("A sold property can not be cancelled")
self.state = "cancelled"
return True

No parentheses around if, and more concise

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also what would happen here if self contains more than 1 property ?


property_type_id = fields.Many2one('estate.property.type', string='Type')
buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False)
salesman_id = fields.Many2one('res.users', string='Salesman', default=lambda self: self.env.user)
tags_ids = fields.Many2many('estate.property.tags', string='Tags')
offer_ids = fields.One2many("estate.property.offer", "property_id", string="offer")
total_area = fields.Integer(string='Total Area(m2)', compute='_compute_total_area', store=True)
best_price = fields.Float(string='Best Offer', compute='_compute_best_price', store=True)

@api.depends('living_area', 'garden_area')
def _compute_total_area(self):
for property in self:
property.total_area = property.living_area + (property.garden_area or 0)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
property.total_area = property.living_area + (property.garden_area or 0)
property.total_area = property.living_area + property.garden_area

garden_area is an integer, it cannot be False


@api.depends('offer_ids.price')
def _compute_best_price(self):
for record in self:
if record.offer_ids:
record.best_price = max(record.offer_ids.mapped('price')) if record.offer_ids else 0
Comment on lines +76 to +79

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _compute_best_price(self):
for record in self:
if record.offer_ids:
record.best_price = max(record.offer_ids.mapped('price')) if record.offer_ids else 0
def _compute_best_price(self):
for record in self:
record.best_price = max(record.offer_ids.mapped('price')) if record.offer_ids else 0

First check not needed


@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 = False

_check_expected_price = models.Constraint(
'CHECK(expected_price >= 0)', 'The expected price must be strictly positive.')

_check_selling_price = models.Constraint(
'CHECK(selling_price > 0)', 'The selling price must be positive.')

@api.constrains('selling_price', 'expected_price')
def _check_selling_price_expected_price(self):
for record in self:
if record.selling_price == 0:
continue
if float_compare(record.selling_price, 0.9 * record.expected_price, precision_digits=2) == -1:
raise UserError("The selling price must be at least 90% of the expected price.")
Comment on lines +90 to +102

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per attribute ordering this needs to be higher

43 changes: 43 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from odoo import models, fields, api
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError


class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = 'Estate Property Offer'
_order = 'price desc'

price = fields.Float(string='price', required=True)
state = fields.Selection(selection=[('accepted', 'Accepted'),
('refused', 'Refused')],
string="Status",
copy=False,
default='accepted',
)
validity = fields.Integer(string='Validity(days)', default=7)
partner_id = fields.Many2one('res.partner', string='Partner', required=True)
property_id = fields.Many2one('estate.property', required=True)
date_deadline = fields.Date(string='Deadline', compute='_compute_date_deadline', store=True)

@api.depends('validity', 'create_date')
def _compute_date_deadline(self):
for offer in self:
if not offer.create_date:
offer.date_deadline = fields.Date.today() + relativedelta(days=offer.validity)

def action_accept(self):
for offer in self:
if offer.state != 'refused':
offer.state = 'accepted'
else:
raise UserError("A refused offer cannot be accepted.")
return True

def action_refuse(self):
for offer in self:
if offer.state != 'accepted':
offer.state = 'refused'
else:
raise UserError("An accepted offer cannot be refused.")
return True
Comment on lines +37 to +43

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def action_refuse(self):
for offer in self:
if offer.state != 'accepted':
offer.state = 'refused'
else:
raise UserError("An accepted offer cannot be refused.")
return True
def action_refuse(self):
if any(offer.state == 'accepted' for offer in self):
raise UserError("An accepted offer cannot be refused.")
self.state = 'refused'
return True

More concise

9 changes: 9 additions & 0 deletions estate/models/estate_property_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from odoo import models, fields


class PropertyTypeTags(models.Model):
_name = 'estate.property.tags'
_description = 'Estate Property Tags'
_order = 'name'

name = fields.Char(required=True)
11 changes: 11 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from odoo import models, fields


class PropertyType(models.Model):
_name = 'estate.property.type'
_description = 'Estate Property Type'
_order = 'sequence, name'

name = fields.Char(required=True)
property_ids = fields.One2many('estate.property', 'property_type_id', string='Properties')
sequence = fields.Integer(string="Sequence", default=10)
5 changes: 5 additions & 0 deletions estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1
access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1
access_estate_property_tags,access_estate_property_tags,model_estate_property_tags,base.group_user,1,1,1,1
access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1
7 changes: 7 additions & 0 deletions estate/views/estate_property_tags_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<odoo>
<record id="estate_property_tags_action" model="ir.actions.act_window">
<field name="name">Property Tags</field>
<field name="res_model">estate.property.tags</field>
<field name="view_mode">list,form</field>
</record>
</odoo>
39 changes: 39 additions & 0 deletions estate/views/estate_property_type.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_type_action" model="ir.actions.act_window">
<field name="name">Property Type</field>
<field name="res_model">estate.property.type</field>
<field name="view_mode">list,form</field>
</record>
<record id="estate_property_type_list" model="ir.ui.view">
<field name="name">estate.property.list</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<list string="Property_type">
<field name ="sequence" widget="handle"/>
<field name="name"/>
<field name="property_ids" widget="one2many_list" mode="list"/>
</list>
</field>
</record>
<record id="estate_property_type_form" model="ir.ui.view">
<field name="name">estate.property.type.form</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<form string="Property Type">
<sheet>
<group>
<field name="name"/>
<field name="property_ids" widget="one2many_list" mode="list,form">
<list editable="bottom">
<field name="name"/>
<field name="expected_price"/>
<field name="state"/>
</list>
</field>
</group>
</sheet>
</form>
</field>
</record>
</odoo>
125 changes: 125 additions & 0 deletions estate/views/estate_property_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_action" model="ir.actions.act_window">
<field name="name">Property</field>
<field name="res_model">estate.property</field>
<field name="view_mode">list,form</field>
</record>

<record id="estate_property_view_list" model="ir.ui.view">
<field name="name">estate.property.list</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<list string="Property">
<field name="name"/>
<field name="bedrooms"/>
<field name="expected_price"/>
<field name="selling_price"/>
<field name="date_availability"/>
<field name="garden_area"/>
<field name="living_area"/>
<field name="total_area"/>
<field name="active"/>
<field name="property_type_id"/>
<field name="salesman_id"/>
<field name="best_price"/>
<field name="state"/>
</list>
</field>
</record>

<record id="estate_property_view_form" model="ir.ui.view">
<field name="name">estate.property.form</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<form string="Property Form">
<sheet>
<header>
<button name="action_set_sold" type="object" string="Sold"/>
<button name="action_set_cancelled" type="object" string="Cancel"/>
<field name="state" widget="statusbar" statusbar_visible="new,offer_received,offer_accepted,sold"/>
</header>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<seperator/>
<group>
<group>
<field name="property_type"/>
<field name="postcode"/>
<field name="date_availability"/>
</group>
<group>
<field name="expected_price"/>
<field name="best_price"/>
<field name="selling_price"/>
</group>
</group>
<notebook>
<page string="Description">
<group>
<field name="description"/>
<field name="bedrooms"/>
<field name="living_area"/>
<field name="facades"/>
<field name="garage"/>
<field name="garden"/>
<field name="garden_area"/>
<field name="garden_orientation"/>
<field name="active"/>
<field name="tags_ids" widget="many2many_tags"/>
<field name="total_area" readonly="1"/>
<field name="best_price" readonly="1"/>
</group>
</page>
<page string="Offers">
<field name ="offer_ids" widget="one2many_list" mode="list,form">
<list editable="bottom">
<field name="partner_id"/>
<field name="price"/>
<field name="state"/>
<field name="validity"/>
<field name="date_deadline"/>
<button name="action_accept" type="object" icon="fa-check" title="Accept"/>
<button name="action_refuse" type="object" icon="fa-times" title="Refuse"/>
</list>
<form>
<field name="partner_id"/>
<field name="price"/>
<field name="state"/>
<field name="validity"/>
<field name="date_deadline"/>
</form>
</field>
</page>
<page string="Other Info">
<group>
<field name="salesman_id"/>
<field name="buyer_id"/>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>

<record id="estate_property_view_search" model="ir.ui.view">
<field name="name">estate.property.views.search</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<search string="Estate Property">
<field name="name"/>
<field name="living_area"/>
<separator/>
<filter string="Archived" name="active" domain="[('active', '=', False)]"/>
<filter string="state" name="state" domain="[('state', 'in', ('new', 'offer_received'))]"/>
<filter string="active" name="group_active" context="{'group_by':'active'}"/>
<filter string="postcode" name="group_active" context="{'group_by':'postcode'}"/>
<filter string="bedrooms" name="group_bedrooms" context="{'group_by':'bedrooms'}"/>
</search>
</field>
</record>
</odoo>
12 changes: 12 additions & 0 deletions estate/views/menu_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="estate_menu_root" name="Real Estate">
<menuitem id="estate_first_level_menu" name="Advertisements">
<menuitem id="estate_model_menu_action" action="estate_property_action"/>
</menuitem>
<menuitem id="estate_sec_level_menu" name="Settings">
<menuitem id="estate_type_model_menu_action" action="estate_property_type_action"/>
<menuitem id="estate_tags_model_menu_action" action="estate_property_tags_action"/>
</menuitem>
</menuitem>
</odoo>