Skip to content
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
90 changes: 90 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from odoo import fields, models, api
from odoo.exceptions import UserError



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.
)

@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

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"
8 changes: 8 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from odoo import models, fields


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

name = fields.Char(required=True)
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 fields, models


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

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
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>
126 changes: 126 additions & 0 deletions estate/views/estate_property_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_action" model="ir.actions.act_window">
<field name="name">Properties</field>
<field name="res_model">estate.property</field>
<field name="view_mode">list,form</field>
</record>

<!-- List View -->
<record id="estate_property_list_view" model="ir.ui.view">
<field name="name">estate.property.list</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<list >
<field name="name"/>
<field name="property_type_id"/>
<field name="postcode"/>
<field name="bedrooms"/>
<field name="living_area"/>
<field name="expected_price" />
<field name="selling_price"/>
<field name="date_availability"/>
<field name="tag_ids" widget="many2many_tags"/>
</list>
</field>
</record>

<!-- Form View -->
<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>
<header>
<button name="action_sold" type="object" string="Sold" class="btn-primary"/>
<button name="action_cancel" type="object" string="Cancel" class="btn-secondary"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
<group>
<field name="tag_ids" widget="many2many_tags"/>
</group>
</div>
<group>
<group>
<field name="state"/>
<field name="property_type_id"/>
<field name="postcode"/>
<field name="date_availability"/>
</group>
<group>
<field name="expected_price"/>
<field name="selling_price"/>
<field name="best_price" readonly="1"/>
</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="total_area" />
</group>
</page>
<page string="Offers">
<field name="offer_ids">
<list editable="bottom">
<field name="price" default="0"/>
<field name="partner_id" default="0"/>
<field name="validity"/>
<field name="date_deadline"/>
<button name="action_accept" type="object" icon="fa-check"/>
<button name="action_refuse" type="object" icon="fa-times"/>
<field name="status"/>
</list>
</field>
</page>
<page string="Other Info">
<group>
<field name="sales_person"/>
<field name="buyer"/>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>

<!-- search -->
<record id="estate_property_view_search" model="ir.ui.view">
<field name="name">estate.property.search</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="property_type_id"/>
<field name="postcode"/>
<field name="expected_price"/>
<field name="bedrooms"/>
<field name="living_area"/>
<field name="facades"/>
<filter
string="Available"
name="available"
domain="[('state', 'in', ['new','offer_received'])]"/>

<filter
string="Postcode"
name="group_postcode"
context="{'group_by':'postcode'}"/>
</search>
</field>
</record>

</odoo>