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,
}
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_type
from . import estate_property_tags
from . import estate_property_offer
Comment on lines +1 to +4

Choose a reason for hiding this comment

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

The imports should be sorted alphabetically. It is in the pep8. Those are guidelines to write clean python code 😃.

100 changes: 100 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from odoo import models, fields, api
from datetime import date
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError
Comment on lines +1 to +4

Choose a reason for hiding this comment

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

The imports should also be sorted alphabetically. I would add that external imports (all imports “date...” for example) must be imported first, followed by internal imports (starting with “from odoo”).

from odoo.tools import float_compare


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

name = fields.Char(string="Title", required=True)
Property_Type = fields.Text()

Choose a reason for hiding this comment

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

Should be property_type. Variables use only lowercase letters. Uppercase letters are used by constants and classes.

Variable -> this_is_a_variable =
constant -> THIS_IS_A_CONSTANT
class -> ThisIsAClass

description = fields.Text()
postcode = fields.Char()
state = fields.Text()
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)
status = fields.Selection(
copy=False,
readonly=True,
default='new',
string='Status',
selection=[
('new', 'New'),
('offer_received', 'Offer Received'),
('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled')]
)

def action_set_sold(self):
if (self.state != "Cancelled"):

Choose a reason for hiding this comment

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

No need of the parenthesis. The != evaluate if the two elements are similar and will return a boolean value in any case.

Suggested change
if (self.state != "Cancelled"):
if self.state != "Cancelled":

self.state = "Sold"
else:
raise UserError("A cancelled property can not be sold")
return True

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

Property_Type_id = fields.Many2one('estate.property.type', string='Type')

Choose a reason for hiding this comment

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

No uppercase letters here too 😃

Buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False)

Choose a reason for hiding this comment

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

Same here.

Salesman_id = fields.Many2one('res.users', string='Salesman', default=lambda self: self.env.user)

Choose a reason for hiding this comment

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

Same here.

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)

@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'))
else:
record.best_price = 0.0
Comment on lines +74 to +77

Choose a reason for hiding this comment

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

You can write it this way, a bit simpler. It is a conditional expressions, it is used when it is possible to avoid conditional statement.

Suggested change
if record.offer_ids:
record.best_price = max(record.offer_ids.mapped('price'))
else:
record.best_price = 0.0
if record.offer_ids:
record.best_price = max(record.offer_ids.mapped('price')) if record.offer_ids else 0


@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.")
46 changes: 46 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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'

price = fields.Float(string='price', required=True)
state = fields.Selection(selection=[('accepted', 'Accepted'),
('refused', 'Refused')],
string="Status",
copy=False,
default='accepted',
)

Choose a reason for hiding this comment

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

No need of this blank line 😃 . We try to keep the code as short as possible as there are many new developments every year.

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 offer.create_date:
create_date = offer.create_date.date()
else:
create_date = fields.Date.today()
offer.date_deadline = create_date + relativedelta(days=offer.validity)
Comment on lines +22 to +29

Choose a reason for hiding this comment

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

The variable created inside the if statement is not used so no need of it, then you can simplify your code. Also try to not create variable if it is only used once. Put it directly where it must be used if it is possible.

Suggested change
@api.depends('validity', 'create_date')
def _compute_date_deadline(self):
for offer in self:
if offer.create_date:
create_date = offer.create_date.date()
else:
create_date = fields.Date.today()
offer.date_deadline = create_date + relativedelta(days=offer.validity)
@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
8 changes: 8 additions & 0 deletions estate/models/estate_property_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from odoo import models, fields


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

name = fields.Char(string='tags', required=True)

Choose a reason for hiding this comment

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

Perhaps you could leave the default label and not use the string attribute? If we need to create tags, the label “Tags” will not indicate to the user that they need to enter the names of the tags.

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


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

name = fields.Char(required=True)
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
29 changes: 29 additions & 0 deletions estate/views/estate_property_tags_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<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>
<!-- <record id="estate_property_tags_list" model="ir.ui.view">
<field name="name">estate.property.tags.list</field>
<field name="model">estate.property.tags</field>
<field name="arch" type="xml">
<list string="Property Tags">
<field name="name"/>
</list>
</field>
</record>
<record id="estate_property_tags_form" model="ir.ui.view">
<field name="name">estate.property.tags.form</field>
<field name="model">estate.property.tags</field>
<field name="arch" type="xml">
<form string="Property Tags">
<sheet>
<group>
<field name="name"/>
</group>
</sheet>
</form>
</field>
</record> -->
</odoo>

Choose a reason for hiding this comment

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

Need of a blank line at the end of the file.

8 changes: 8 additions & 0 deletions estate/views/estate_property_type.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_type_action" model="ir.actions.act_window">
Comment on lines +2 to +3

Choose a reason for hiding this comment

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

There is an indentation issue here 😃

<field name="name">Property Type</field>
<field name="res_model">estate.property.type</field>
<field name="view_mode">list,form</field>
</record>
</odoo>

Choose a reason for hiding this comment

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

Same here.

122 changes: 122 additions & 0 deletions estate/views/estate_property_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?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">
Comment on lines +7 to +8

Choose a reason for hiding this comment

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

Maybe you can add a blank line between the records, so it will be a bit clearer ?

<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>
Comment on lines +32 to +34

Choose a reason for hiding this comment

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

Alignment issue here to 😃 , as is the parent of

and is the parent of sheet.

Suggested change
<field name="arch" type="xml">
<form string="Property Form">
<sheet>
<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"/>
</header>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<seperator/>
<group>
<group>
<field name="state"/>
<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="Other Info">
<group>
<field name="Salesman_id"/>
<field name="Buyer_id"/>
</group>
</page>
<page string="Offers">
<field name ="offer_ids">
<list>
<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>
</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="status" name="status" domain="[('status', '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>

Choose a reason for hiding this comment

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

Same here.