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
2 changes: 2 additions & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from odoo import api, SUPERUSER_ID
25 changes: 25 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
'name':"estate",
'author':"Odoo S.A.",
'licence':"none",

'summary': """
Houses!
""",

'description': """
Lots of Houses!!"
""",

'application': True,
'installable': True,
'data':[
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_types_views.xml',
'views/estate_property_tags_views.xml',
'views/estate_property_offers_views.xml',
'views/estate_menus.xml',
'views/estate_users.xml',
],
}
5 changes: 5 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import est_property
from . import est_property_offers
from . import est_property_tags
from . import est_property_types
from . import inherited_model
110 changes: 110 additions & 0 deletions estate/models/est_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from datetime import timedelta
from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_compare, float_is_zero


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

_check_expected_price = models.Constraint(
'CHECK (expected_price >= 0)',
'The Expected Price should be positive!',
)

_check_selling_price = models.Constraint(
'CHECK (selling_price >= 0)',
'The Selling Price should be positive!',
)
def _today_plus_90days(self):
return fields.Date.today(self) + timedelta(days=90)


name = fields.Char('Property Name', required=True)
description = fields.Text(required=True)
post_code = fields.Char(required=True)
date_availability = fields.Date(default=(_today_plus_90days)) #add 90 days which is about 3 months
expected_price = fields.Float(required=True)
selling_price = fields.Float()
bedrooms = fields.Integer(default=2)
living_area = fields.Integer("Living Area (sqm)")
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")])
active = fields.Boolean("Active",default=True)
state = fields.Selection(
selection = [("new","New"),("offer_received","Offer Received"),("offer_accepted","Offer Accepted"),("sold","Sold"),("cancelled","Cancelled")],
default = "new"
)
partner_id = fields.Many2one("res.partner", string="Buyer")
user_id = fields.Many2one("res.users", string="Salesman")
property_type_id = fields.Many2one("est.property.type", string="Property Type")

tag_ids = fields.Many2many("est.property.tag",string="Tags")

offers_ids = fields.One2many("est.property.offer","property_id",string="Offers")

total_area = fields.Integer(compute="_compute_total", readonly=True)
max_offer = fields.Integer(compute="_compute_best_offer", readonly=True)

@api.constrains("selling_price")
def _check_selling_price(self):
for property in self:
## if different than 0 and selling_price < 90% of expected_price
if (not float_is_zero(self.selling_price,precision_digits=3) and (1 == float_compare(self.expected_price * .9, self.selling_price, precision_digits=3))):
raise ValidationError(r"Selling price can't be less than 90% expected price")


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

@api.depends("offers_ids")
def _compute_best_offer(self):

for property in self:

property.max_offer = max(property.offers_ids.mapped("price"),default=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 = None

@api.ondelete(at_uninstall=False)
def _ondelete(self):
for property in self:
if not(property.state == "new" or property.state == "cancelled"):
raise UserError("You can only delete properties that are 'New' or 'Cancelled'.")


# ------------- Actions -------------------------------

def sell_action(self):
for property in self:
if self.state == "cancelled":
#Raise error
raise UserError("Can't change to Sold if current state is Cancelled!")
else:
self.state = "sold"

def cancel_action(self):
for property in self:
if self.state =="sold":
#Raise error
raise UserError("Can't change to Cancel if current state is Sold!")
else:
self.state = "cancelled"

82 changes: 82 additions & 0 deletions estate/models/est_property_offers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from datetime import timedelta
from odoo import models, fields, api
from odoo.exceptions import UserError


class EstateOffer(models.Model):
_name = "est.property.offer"
_description = "Property Offers"
_order = "price desc"

_check_price = models.Constraint(
'CHECK (price >= 0)',
'The Price should be positive!',
)


name = fields.Char("name")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The string attribute defaults to the name of the field - suffix such as _id.s. Thus, when you want the string to be the same as the field name, you don't need to add it

Suggested change
name = fields.Char("name")
description = fields.Text()

price = fields.Integer("price",required=True)
status = fields.Selection(
string="status",
default="waiting",
selection = [
("accepted","Accepted"),
("refused","Refused"),
("waiting","Waiting"),
],
)
partner_id = fields.Many2one("res.partner")
property_id = fields.Many2one("est.property",required=True)

property_type_id = fields.Many2one("est.property.type", related="property_id.property_type_id", stored=True, compute="_compute_property_type_id")


validity_date = fields.Date(compute="_compute_validity_date",inverse="_inverse_validity_duration")
validity_duration = fields.Integer(default=7)

@api.depends("validity_duration")
def _compute_validity_date(self):
for offer in self:
offer.validity_date = fields.Date.today() + timedelta(days=offer.validity_duration)

@api.depends("property_id")
def _compute_property_type_id(self):
for offer in self:
if offer.property_id.property_type_id:
offer.property_type_id = offer.property_id.property_type_id
else:
offer.property_type_id = False

def _inverse_validity_duration(self):
for offer in self:
offer.validity_duration = (offer.validity_date - fields.Date.today()).days

def action_confirm(self):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

self is a recordset, i.e. a collection of records. Your method would failed if called on several records when trying to perform self.state.

Currently, through the UI, you cannot trigger the error, but from the command line or a server action, users could get the traceback.

side note: you can run odoo in terminal mode by adding shell after odoo-bin: python odoo-bin shell --addons-path .... From there, you could perform

estates = self.env['estate.property'].create([{'name': 'TEST1', ADD the other relevant key-value pairs}, {'name': 'TEST2', ADD the other relevant key-value pairs}])
estates.action_confirm()  # This should trigger the traceback

There are two ways to solve the issue:

  • begin your method with self.ensure_one(), quite self explanatory
  • or use a for loop like in _compute methods, we did but only in the else condition, the traceback will be triggered when executing self.status == "Refused" - a read of a value on the recordset

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Functionally speaking, could you confirm an offer if the property was cancelled ?

for offer in self:
if offer.property_id.state == "sold":
raise UserError(self.env._("Property was already sold!"))
elif offer.property_id.state == "cancelled":
raise UserError(self.env._("Property was already cancelled!"))
elif offer.status == "refused":
raise UserError(self.env._("Cannot accept a proposal that was already refused!"))
else:
offer.status = "accepted"
offer.property_id.selling_price = offer.price
offer.property_id.partner_id = offer.partner_id
offer.property_id.state = "sold"

def action_reject(self):
self.status = "refused"

@api.model
def create(self,vals_list):
for vals in vals_list:
property = self.env['est.property'].browse(vals.get('property_id'))

if property.state == 'new':
property.state = 'offer_received'

if property.max_offer > vals.get('price',0):
raise UserError(self.env._("Cannot create an offer with a lower value than a previous one!"))

return super().create(vals_list)
14 changes: 14 additions & 0 deletions estate/models/est_property_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from odoo import models, fields

class EstateTag(models.Model):
_name = "est.property.tag"
_description = "Property tags"
_order = "name asc"

_check_name = models.Constraint(
'unique(name)',
'There is already a property tag with that name!',
)

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

class EstateType(models.Model):
_name = "est.property.type"
_description = "Property types"
_order = "sequence,name"

_check_name = models.Constraint(
'unique(name)',
'There is already a property type with that name!',
)

name = fields.Char("name", required=True)
sequence = fields.Integer(default=1)


offer_ids = fields.One2many("est.property.offer","property_type_id")
offer_count = fields.Integer(compute="_compute_count")

@api.depends("offer_ids")
def _compute_count(self):
for type in self:
type.offer_count = len(type.offer_ids)
7 changes: 7 additions & 0 deletions estate/models/inherited_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from odoo import models, fields


class User(models.Model):
_inherit = "res.users"

property_ids = fields.One2many("est.property","user_id",string="Properties")
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_model,access_estate_model,model_est_property,base.group_user,1,1,1,1
access_type,access_type,model_est_property_type,base.group_user,1,1,1,1
access_tag,access_tag,model_est_property_tag,base.group_user,1,1,1,1
access_offer,access_offer,model_est_property_offer,base.group_user,1,1,1,1
16 changes: 16 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<menuitem id="est_test_model_menu" name="Real Estate">
<menuitem id="est_testing_stuff" name='Advertisements'>
<menuitem id="est_test_model_menu_action" action="est_test_model_action"/>
</menuitem>
<menuitem id="est_Settings" name='Settings'>
<menuitem id="est_property_type_menu_action" action="est_property_type_model_action"/>
<menuitem id="est_property_tag_menu_action" action="est_property_tag_model_action"/>
<menuitem id="est_property_offer_menu_action" action="est_property_offer_model_action"/>

</menuitem>
</menuitem>

</odoo>
25 changes: 25 additions & 0 deletions estate/views/estate_property_offers_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="est_test_view_tree" model="ir.ui.view">
<field name="name">est_property_offer_view_list</field>
<field name="model">est.property.offer</field>
<field name="arch" type="xml">
<list string="Tests">


<field name="name"/>
<field name="price" />
<field name="status"/>

</list>
</field>
</record>

<record id="est_property_offer_model_action" model="ir.actions.act_window">
<field name="name">Property offers</field>
<field name="res_model">est.property.offer</field>
<field name="view_mode">list,form</field>
</record>

</odoo>
10 changes: 10 additions & 0 deletions estate/views/estate_property_tags_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="est_property_tag_model_action" model="ir.actions.act_window">
<field name="name">Property tags</field>
<field name="res_model">est.property.tag</field>
<field name="view_mode">list,form</field>
</record>

</odoo>
58 changes: 58 additions & 0 deletions estate/views/estate_property_types_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>


<record id="est_property_type_view_list" model="ir.ui.view">
<field name="name">est.property.type.list</field>
<field name="model">est.property.type</field>
<field name="arch" type="xml">
<list string="Tests">

<field name="sequence" widget="handle"/>

<field name="name"/>


</list>
</field>
</record>

<record id="est_property_type_from_offer_event" model="ir.actions.act_window">
<field name="res_model">est.property.offer</field>
<field name="name">Property Types</field>
<field name="view_mode">list</field>
<field name="domain">[('property_type', '=', active_id)]</field>
</record>


<record id="est_property_type_view_form" model="ir.ui.view">
<field name="name">est.property.type.form</field>
<field name="model">est.property.type</field>
<field name="arch" type="xml">
<form string="Something">
<header>

<button type="action"
name="%(est_property_type_from_offer_event)d"
string="View Offer"
class="oe_highlight"/>

</header>
<group>
<field name="name"/>

<field name="offer_ids"/>
</group>
</form>
</field>
</record>


<record id="est_property_type_model_action" model="ir.actions.act_window">
<field name="name">Property types</field>
<field name="res_model">est.property.type</field>
<field name="view_mode">list,form</field>
</record>


</odoo>
Loading