Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

18.0 tutorial drod #178

Open
wants to merge 9 commits into
base: 18.0
Choose a base branch
from
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
25 changes: 25 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# A custom real estate module, created for learning purposes

{
'name': '[TUTO] Estate',
'summary': 'Track your real estate properties',
'depends': [
'base_setup'
],
'license': "LGPL-3",
'data': [
'security/ir.model.access.csv',

'views/estate_property_views.xml',
'views/estate_property_offer_views.xml',
'views/estate_settings_views.xml',
'views/res_user_views.xml',
'views/estate_menu_views.xml'
],
'installable': True,
'application': True,
'demo': [
"demo/estate_demo.xml",
],
'auto_install': True
}
121 changes: 121 additions & 0 deletions estate/demo/estate_demo.xml
vandroogenbd marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>

<!-- Demo Estate Property Types -->
<record id="estate_property_type_1" model="estate_property_type">
<field name="name">Appartment</field>
</record>
<record id="estate_property_type_2" model="estate_property_type">
<field name="name">House</field>
</record>

<!-- Demo Estate Property Tags -->
<record id="estate_property_tag_1" model="estate_property_tag">
<field name="name">cosy</field>
<field name="color">5</field>
</record>
<record id="estate_property_tag_2" model="estate_property_tag">
<field name="name">renovated</field>
<field name="color">3</field>
</record>
<record id="estate_property_tag_3" model="estate_property_tag">
<field name="name">renovation needed</field>
<field name="color">4</field>
</record>

<!-- Demo Estate Properties -->
<record id="estate_property_1" model="estate_property">
<field name="name">Beach House</field>
<field name="status">new</field>
<field name="description">A brand new house by the beach</field>
<field name="postcode">8300</field>
<field name="date_availability" eval="(DateTime.today() + relativedelta(months=3)).strftime('%Y-%m-%d %H:%M')" />
<field name="expected_price">2500000</field>
<field name="selling_price">0</field>
<field name="bedrooms">6</field>
<field name="living_area">1050</field>
<field name="facades">4</field>
<field name="garage">True</field>
<field name="garden">True</field>
<field name="garden_area">420</field>
<field name="garden_orientation">south</field>
<field name="type_id" ref="estate_property_type_2" />
<field name="salesperson" ref="base.user_demo"/>
<field name="tag_ids" eval="[(6, 0, [ref('estate_property_tag_1'), ref('estate_property_tag_2')])]"/>
</record>
<record id="estate_property_2" model="estate_property">
<field name="name">City Apt</field>
<field name="status">offer_accepted</field>
<field name="description">A shitty appartment in Brussels</field>
<field name="postcode">1090</field>
<field name="date_availability" eval="(DateTime.today() + relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')" />
<field name="expected_price">250000</field>
<field name="selling_price">0</field>
<field name="bedrooms">2</field>
<field name="living_area">95</field>
<field name="facades">1</field>
<field name="garage">False</field>
<field name="garden">False</field>
<field name="garden_area">0</field>
<field name="type_id" ref="estate_property_type_1"/>
<field name="salesperson" ref="base.user_admin"/>
<field name="tag_ids" eval="[(6, 0, [ref('estate_property_tag_3')])]"/>
</record>
<record id="estate_property_3" model="estate_property">
<field name="name">Small House</field>
<field name="status">new</field>
<field name="description">A small house in the centre of Brussels</field>
<field name="postcode">1000</field>
<field name="date_availability" eval="(DateTime.today() + relativedelta(days=5, months=3)).strftime('%Y-%m-%d %H:%M')" />
<field name="expected_price">450000</field>
<field name="selling_price">0</field>
<field name="bedrooms">2</field>
<field name="living_area">120</field>
<field name="facades">2</field>
<field name="garage">True</field>
<field name="garden">False</field>
<field name="garden_area">0</field>
<field name="type_id" ref="estate_property_type_2"/>
<field name="salesperson" ref="base.user_admin"/>
<field name="tag_ids" eval="[(6, 0, [ref('estate_property_tag_1'), ref('estate_property_tag_3')])]"/>
</record>

<!-- Demo Estate Property Offers -->
<record id="estate_property_offer_1" model="estate_property_offer">
<field name="price">2000000</field>
<field name="partner_id" ref="base.res_partner_4"/>
<field name="validity">12</field>
<field name="property_id" ref="estate_property_1"/>
</record>
<record id="estate_property_offer_2" model="estate_property_offer">
<field name="price">200000</field>
<field name="partner_id" ref="base.res_partner_3"/>
<field name="validity">10</field>
<field name="property_id" ref="estate_property_2"/>
</record>
<record id="estate_property_offer_3" model="estate_property_offer">
<field name="price">220000</field>
<field name="partner_id" ref="base.res_partner_5"/>
<field name="validity">25</field>
<field name="property_id" ref="estate_property_2"/>
</record>
<record id="estate_property_offer_4" model="estate_property_offer">
<field name="price">260000</field>
<field name="status">accepted</field>
<field name="partner_id" ref="base.res_partner_4"/>
<field name="deadline" eval="(DateTime.today() + relativedelta(days=7)).strftime('%Y-%m-%d %H:%M')" />
<field name="property_id" ref="estate_property_2"/>
</record>

<!-- Mass cancel -->
<record id="model_estate_property_action_cancel" model="ir.actions.server">
<field name="name">Mass cancel</field>
<field name="model_id" ref="estate.model_estate_property"/>
<field name="binding_model_id" ref="estate.model_estate_property"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">action = records.action_cancel()</field>
</record>
</data>
</odoo>
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_infos, estate_property, estate_property_offer, res_user
Copy link

Choose a reason for hiding this comment

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

Better to give each its own line: clearer and later, if some files have been added, people can find more easily who was at the origin of a specific file with a git blame

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


class Estate_Property(models.Model):
_name = "estate_property"
Copy link

Choose a reason for hiding this comment

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

Suggested change
_name = "estate_property"
_name = "estate.property"

We use _ only in specific cases, in a model name. It is better to use a . in between words (you could think of it as the model "property" of the module "estate" -> estate.property).

This is the same for all other model names

_description = "Estate properties"
_order = "id desc"
active = False

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

Copy link

Choose a reason for hiding this comment

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

Avoid unnecessary empty lines between fields declarations, it takes a lot of space and doesn't bring much clarity :D

status = fields.Selection(
[
("new", "New"),
("offer_received", "Offer received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("canceled", "Canceled"),
],
default="new",
readonly=True,
copy=False,
string="Sale State",
)

description = fields.Text(copy=False, string="Description")

postcode = fields.Char(
help="Une adresse serait mieux mais bon...", string="Postcode"
)

date_availability = fields.Date(
default=(date.today() + relativedelta(months=+3)),
Copy link

Choose a reason for hiding this comment

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

You could also use fields.Date.today() + relativedelta(months=3) if you wanted, it would use one less import

copy=False,
string="Available From",
)

expected_price = fields.Float(
default=0.0, required=True, copy=False, string="Expected Price"
Copy link

Choose a reason for hiding this comment

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

Fields will always default to a "nothing" state (0 for integers and floats, False for boolean, '' for strings) so you don't have to specify the default parameter to set fields to those values ;)

)

selling_price = fields.Float(readonly=True, string="Selling Price")

bedrooms = fields.Integer(default=2, string="Bedrooms")

living_area = fields.Integer(string="Living Area (sqm)")

facades = fields.Integer(default=2, string="Facades")

garage = fields.Boolean(default=False, string="Garage")

garden = fields.Boolean(default=False, string="Garden")

garden_area = fields.Integer(default=0, string="Garden Area (sqm)")

garden_orientation = fields.Selection(
[("north", "North"), ("south", "South"), ("west", "West"), ("east", "East")],
string="Garden Orientation",
)

type_id = fields.Many2one(
"estate_property_type", required=True, string="Property Type"
)

buyer = fields.Many2one("res.partner", copy=False, string="Buyer")

salesperson = fields.Many2one(
"res.users", default=(lambda self: self.env.user), string="Salesman"
Copy link

Choose a reason for hiding this comment

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

Suggested change
"res.users", default=(lambda self: self.env.user), string="Salesman"
"res.users", default=lambda self: self.env.user, string="Salesman"

Not wrong, but the parenthesis are not needed

)

tag_ids = fields.Many2many("estate_property_tag", string="Tags")

offer_ids = fields.One2many("estate_property_offer", "property_id", string="Offers")

total_area = fields.Integer(compute="_compute_total_area", string="Total Area")

best_offer = fields.Float(
compute="_compute_best_offer", default=0.0, string="Best Offer"
)

_sql_constraints = [
(
"check_positive_expected_price",
"CHECK(expected_price >= 0.0)",
"Expected Price should be a positive number.",
),
(
"check_positive_selling_price",
"CHECK(selling_price >= 0.0)",
"Selling Price should be a positive number.",
),
]

@api.depends("garden_area", "living_area")
def _compute_total_area(self):
for record in self:
record.total_area = record.garden_area + record.living_area

@api.depends("offer_ids")
def _compute_best_offer(self):
for record in self:
if record.offer_ids:
record.best_offer = max(record.offer_ids.mapped("price"))
else:
record.best_offer = .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 = ""

@api.constrains("selling_price", "expected_price")
def _check_expected_vs_selling_price(self):
for record in self:
if (record.selling_price > 0.0) and (
record.selling_price < 0.9 * record.expected_price
Copy link

Choose a reason for hiding this comment

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

Better to use float_compare from odoo.tools.float_utils to avoid rounding errors

):
raise exceptions.ValidationError(
r"Cannot sell for less than 90% of expected price."
)

@api.ondelete(at_uninstall=False)
def _delete_only_new_canceled(self):
for record in self:
if not (record.status == "new" or record.status == "canceled"):
raise exceptions.UserError("Sold properties / properties with pending offers cannot be deleted.")

def action_sold(self):
for record in self:
if record.status == "canceled":
raise exceptions.UserError("Canceled properties cannot be sold.")
elif record.status == "offer_accepted":
record.status = "sold"
else:
raise exceptions.UserError("An offer must be accepted for a property to be sold.")
return True
Copy link

Choose a reason for hiding this comment

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

No need to return True for actions, but it won't create any issue, so 🤷‍♂️


def action_cancel(self):
for record in self:
if record.status != "sold":
record.status = "canceled"
else:
raise exceptions.UserError("Sold properties cannot be canceled.")
return True
52 changes: 52 additions & 0 deletions estate/models/estate_property_infos.py
Copy link

Choose a reason for hiding this comment

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

Usually we prefer to put a model per file, unless their definition doesn't take much space... As it is the case here, not need to change anything 👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from odoo import api, fields, models


class Estate_Property_Type(models.Model):
_name = "estate_property_type"
_description = "Estate property Types"
_order = "name"

sequence = fields.Integer(
"Sequence", default=1, help="Used to order types. Lower is better."
)

name = fields.Char(required=True, string="Type")
Copy link

Choose a reason for hiding this comment

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

Try duplicating a property type: you'll have an issue due to the constraint. How can you resolve that ?


property_ids = fields.One2many(
"estate_property", "type_id", string="Estate Properties"
)

offer_ids = fields.One2many(
"estate_property_offer", "property_type_id", string="Offer IDs"
)

offer_count = fields.Integer(
compute="_count_offers", default=0, string="Offers"
)

_sql_constraints = [
("check_unique_type", "UNIQUE(name)", "Property types must be unique.")
]

@api.depends("offer_ids")
def _count_offers(self):
Copy link

Choose a reason for hiding this comment

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

Compute functions should always start with _compute ;)

for record in self:
record.offer_count = len(record.offer_ids) if record.offer_ids else 0


class Estate_Property_Tag(models.Model):
_name = "estate_property_tag"
_description = "Estate property Tags"
_order = "name"

name = fields.Char(required=True, string="Type")
Copy link

Choose a reason for hiding this comment

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

Same duplication error as for the property type, but as this model doesn't have a lot to duplicate, the feature is less used so the problem is less important (but still)...


color = fields.Integer(string="Colour")

property_estate_ids = fields.Many2many(
Copy link

Choose a reason for hiding this comment

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

This is unnecessary, we just want the tags on the properties, but we don't really care to find properties from a tag (a search by tags on the properties list view will suffice if we want to find properties with a special tag

"estate_property", string="Estate Properties"
)

_sql_constraints = [
("check_unique_tag", "UNIQUE(name)", "Property tags must be unique.")
]
Loading