Skip to content
Draft
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,4 @@ dmypy.json

# Pyre type checker
.pyre/
estate/models/ir_ui_view.py
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
19 changes: 19 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
'name': 'Real Estate',
'version': '1.0',
'category': 'tutorials',
'depends': ['base'],
Comment thread
mash-odoo marked this conversation as resolved.
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_offer_views.xml',
'views/estate_menus.xml',
],
'installable': True,
'application': True,
'author': 'Odoo S.A.',
'description': """Training module for real estate""",
'license': 'LGPL-3',
}
1 change: 1 addition & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer
110 changes: 110 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from odoo import fields, models, api
from odoo.exceptions import UserError
from odoo.tools import float_utils


class EstateProperties(models.Model):
_name = "estate.property"
_description = "Real Estate Properties"

active = fields.Boolean(default=True)
bedrooms = fields.Integer(default=2)
best_price = fields.Float(compute="_compute_best_price", string="Best Offer")
buyer = fields.Many2one("res.partner", string="Buyer", copy=False)
description = fields.Text()
date_availability = fields.Date(
copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3)
)
expected_price = fields.Float(required=True)
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection(
selection=[
('north', "North"),
('south', "South"),
('east', "East"),
('west', "West"),
]
Comment on lines +24 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
selection=[
("north", "North"),
("south", "South"),
("east", "East"),
("west", "West"),
]
selection=[
('north', "North"),
('south', "South"),
('east', "East"),
('west', "West"),
]

Try to keep the key i.e the technical strings in single quotes and the values which are to be displayed to the user in double quotes

)
living_area = fields.Integer()
name = fields.Char(required=True)
postcode = fields.Char()
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
property_type_id = fields.Many2one("estate.property.type", string="Property Type")
selling_price = fields.Float(readonly=True, copy=False)
state = fields.Selection(
selection=[
('new', "New"),
('offer received', "Offer Received"),
('offer accepted', "Offer Accepted"),
('sold', "Sold"),
('canceled', "Canceled"),
],
required=True,
copy=False,
default="new",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What if we don't add a default value?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Without a default value, the state field would be empty when creating a new record until the user explicitly selects a value.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

And what if the required field is also readonly?

)
sales_person = fields.Many2one(
"res.users", string="Sales Person", default=lambda self: self.env.user
)
tag_ids = fields.Many2many("estate.property.tag", string="Tags")
total_area = fields.Float(
compute="_compute_total_area", string="Total Area", store=True
Comment thread
mash-odoo marked this conversation as resolved.
)

_check_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.depends("living_area", "garden_area")
def _compute_total_area(self):
for record in self:
record.total_area = record.living_area + record.garden_area

@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

@api.onchange("garden")
def _onchange_garden(self):
for record in self:
if record.garden:
record.garden_area = 10
record.garden_orientation = "north"
else:
record.garden_area = 0
record.garden_orientation = False

@api.constrains("selling_price", "expected_price")
def _check_selling_price_constraint(self):
for record in self:
if float_utils.float_compare(
record.selling_price, record.expected_price * 0.9, precision_digits=2
) < 0:
raise UserError(
"The selling price cannot be lower than 90% of the expected price."
)

def action_sold(self):
for record in self:
if record.state == "canceled":
raise UserError("Canceled properties cannot be sold.")
record.state = "sold"

def action_cancel(self):
for record in self:
if record.state == "sold":
raise UserError("Sold properties cannot be canceled.")
record.state = "canceled"
45 changes: 45 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from odoo import models, fields, api


class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Estate Property Offer"

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

@api.depends("create_date", "validity")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Where does this create_date field come from? 🤔
You haven't added it while creating your model.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

create_date is a built-in magical field automatically added by models.Model, so it is available on this model without being explicitly declared.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Are you aware of how magic fields are declared when creating a table?

def _compute_date_deadline(self):
for record in self:
create_date = record.create_date or fields.Date.today()
record.date_deadline = fields.Date.add(
fields.Date.to_date(create_date), days=record.validity
)

def _inverse_date_deadline(self):
for record in self:
create_date = record.create_date or fields.Date.today()
record.validity = (
record.date_deadline - fields.Date.to_date(create_date)
).days

def action_accept(self):
for record in self:
record.status = "accepted"
record.property_id.selling_price = record.price
record.property_id.buyer = record.partner_id
record.property_id.state = "offer accepted"

def action_refuse(self):
for record in self:
record.status = "refused"
13 changes: 13 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from odoo import models, fields


class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Estate Property Tag"

name = fields.Char(required=True)

_name_uniq = models.Constraint(
'unique(name)',
'Property tag name must be unique.',
)
13 changes: 13 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from odoo import fields, models


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

name = fields.Char(required=True)

_name_uniq = models.Constraint(
'unique(name)',
'Property type name must be unique.',
)
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
estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,
access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1
estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1
estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1
9 changes: 9 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<odoo>
<menuitem id="estate_menu_root" name="Real Estate"/>
<menuitem id="estate_menu_advertisement" name="Advertisement" parent="estate_menu_root" />
<menuitem id="estate_menu_property" name="Properties" parent="estate_menu_advertisement" action="estate_property_action" />
<menuitem id="estate_menu_settings" name="Settings" parent="estate_menu_root" />
<menuitem id="estate_menu_property_type" name="Property Types" parent="estate_menu_settings" action="estate_property_type_action" />
<menuitem id="estate_menu_property_tag" name="Property Tags" parent="estate_menu_settings" action="estate_property_tag_action" />
</odoo>
37 changes: 37 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0"?>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why do we need to write this line?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It's the XML declaration that identifies the file as an XML 1.0 document. It's the standard way to start an XML file.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What if we skip writing this?

<odoo>

<!-- List View -->
<record id="estate_property_offer_list_view" model="ir.ui.view">
<field name="name">estate.property.offer.list</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<list>
<field name="price"/>
<field name="date_deadline"/>
<field name="status"/>
<field name="partner_id"/>
</list>
</field>
</record>

<!-- Form View -->
<record id="estate_property_offer_view_form" model="ir.ui.view">
<field name="name">estate.property.offer.form</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<field name="status"/>
</group>
</sheet>
</form>
</field>
</record>

</odoo>
8 changes: 8 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_tag_action" model="ir.actions.act_window">
<field name="name">Property Tags</field>
<field name="res_model">estate.property.tag</field>
<field name="view_mode">list,form</field>
</record>
</odoo>
8 changes: 8 additions & 0 deletions estate/views/estate_property_type_views.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">
<field name="name">Property Types</field>
<field name="res_model">estate.property.type</field>
<field name="view_mode">list,form</field>
</record>
</odoo>
Loading