-
Notifications
You must be signed in to change notification settings - Fork 2.2k
First and second chapter of the training #230
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
base: 18.0
Are you sure you want to change the base?
Changes from all commits
a6f7cc5
dace4d8
de9ee05
471b0f4
0edf76f
3506143
c7f14d0
bb8e04c
91d57ab
cff053b
4c73aeb
72b9caf
c1a28f3
ea98248
41835ab
6a86e61
f7b3ff8
aaae53f
ccc40e6
4cedb35
1aedbed
be5c265
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.vscode/launch.json | ||
launch.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"name": "python:odoo", | ||
"type": "debugpy", | ||
"request": "launch", | ||
"stopOnEntry": false, | ||
"python": "/usr/bin/python3", | ||
"program": "/home/odoo/odoo-training/odoo/odoo-bin", | ||
"console": "integratedTerminal", | ||
"args": [ | ||
"--limit-time-cpu=99999", | ||
"--limit-time-real=99999", | ||
"--limit-request=10000", | ||
"--addons-path=/home/odoo/odoo-training/odoo/addons,/home/odoo/odoo-training/enterprise,/home/odoo/odoo-training/tutorials", | ||
"--dev=all", | ||
"-d rd-demo", | ||
], | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"python.languageServer": "None" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
'name': "Real estate module!!", | ||
'version': '1.0', | ||
'depends': ['base'], | ||
'author': "Arthur Nanson", | ||
'category': 'Category', | ||
'description': """ | ||
With this awesome module you can do awesome things like buy a house somehow maybe | ||
""", | ||
'application' : True, | ||
# data files always loaded at installation | ||
#'data' : ['data/estate.property.csv'], | ||
'data' : ['security/ir.model.access.csv', | ||
'views/estate_property_view.xml', | ||
'views/estate_property_tag_view.xml', | ||
'views/estate_property_offer_view.xml', | ||
'views/estate_property_type_view.xml', | ||
'views/estate_menu.xml', | ||
'views/res.users.view.xml' | ||
], | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from . import estate_property | ||
from . import estate_property_type | ||
from . import estate_property_tag | ||
from . import estate_property_offer | ||
from . import res_users |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
from odoo import models, fields, api, exceptions | ||
from datetime import date, timedelta | ||
from odoo.exceptions import ValidationError | ||
class EstateProperty(models.Model): | ||
|
||
_name = "estate.property" | ||
_description = "Damn this model is good for doing real estate related stuff" | ||
_order = "id desc" | ||
|
||
name = fields.Char(name = "Title", required=True) | ||
description = fields.Text(name = "Description", required = True) | ||
postcode = fields.Char(name = "PostCode", required = True) | ||
date_availability = fields.Date(name = "Availability", default=lambda self: date.today() + timedelta(days=90), copy=False) | ||
expected_price = fields.Float(name="Expected Price") | ||
selling_price = fields.Float(name = "Selling Price", readonly = True, copy = False) | ||
bedrooms = fields.Integer(name = "Bedrooms", default = 2) | ||
living_area = fields.Integer(name = "Living Area (m²)") | ||
facades = fields.Integer(name = "Facades") | ||
garage = fields.Boolean(name = "Garage") | ||
garden = fields.Boolean(name = "Garden") | ||
garden_area = fields.Integer(name = "Garden Area (m²)") | ||
garden_orientation = fields.Selection(string='Garden orientation', | ||
selection=[('north', 'North'), | ||
('west', 'West'), ('south', 'South'), | ||
('east', 'East') | ||
], | ||
help="Chose the direct which the garden is facing") | ||
|
||
active = fields.Boolean(default=True) | ||
state = fields.Selection(string='Status', | ||
selection=[('new', 'New'), | ||
('offer received', 'Offer received'), | ||
('offer accepted', 'Offer accepted'), | ||
('sold', 'Sold'), | ||
('cancelled', 'Cancelled') | ||
], | ||
default='new', | ||
help="Is the house sold already ?") | ||
type_id = fields.Many2one("estate.property.types", string = "Property Type") | ||
sales_person_id = fields.Many2one("res.users", string = "Sales Person", default=lambda self: self.env.user) | ||
buyer_id = fields.Many2one("res.partner", string = "Buyer", copy=False) | ||
tag_ids = fields.Many2many("estate.property.tag", string="Tags", name="Tags") | ||
offer_ids = fields.One2many("estate.property.offer", "property_id", name="Offers") | ||
total_area = fields.Float(compute="_compute_total_area", string="Total Area (m²)") | ||
best_price = fields.Float(compute="_compute_best_price", string="Best price") | ||
salesperson_id = fields.Many2one("res.users", string="Salesperson") | ||
|
||
|
||
_sql_constraints = [ | ||
('positive_expected_price', | ||
'CHECK (expected_price>0)', | ||
'The expected price should always be positive!'), | ||
('positive_selling_price', | ||
'CHECK (selling_price>0)', | ||
'The selling price should always be positive!'), | ||
] | ||
|
||
@api.depends("living_area", "garden_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_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): | ||
if self.garden: | ||
self.garden_area = 10 | ||
self.garden_orientation = "north" | ||
else: | ||
self.garden_area = 0 | ||
self.garden_orientation = False | ||
|
||
@api.constrains('selling_price') | ||
def check_selling_price_not_lower_than_90_percent_of_expected_price(self): | ||
for record in self: | ||
if record.selling_price < 0.9 * record.expected_price: | ||
raise ValidationError("The selling price must be at least 90% of the expected price") | ||
|
||
@api.ondelete(at_uninstall=False) | ||
def _unlink_if_state_is_new_or_cancelled(self): | ||
for record in self: | ||
if record.state not in ['new', 'cancelled']: | ||
raise ValidationError("You can't delete a property in this state") | ||
|
||
def cancel_property(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leave a newline betwixt method definitions |
||
for record in self: | ||
if record.state == "sold": | ||
raise exceptions.UserError("A sold property can't be cancelled") | ||
else: | ||
record.state = "cancelled" | ||
def sold_property(self): | ||
for record in self: | ||
if record.state == "cancelled": | ||
raise exceptions.UserError("A cancelled property can't be sold") | ||
else: | ||
record.state = "sold" | ||
def set_buyer(self, buyer): | ||
self.buyer_id=buyer | ||
def set_status(self, status): | ||
self.state = status | ||
def set_sold_price(self, price): | ||
self.selling_price = price | ||
def get_status(self): | ||
return self.state |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
from odoo import models, fields, api, exceptions | ||
from datetime import date, timedelta | ||
|
||
class EstatePropertyOffer(models.Model): | ||
|
||
_name = "estate.property.offer" | ||
_description = "The offers for a property" | ||
_order = "price desc" | ||
|
||
price = fields.Float(name = "Price", required = True) | ||
status = fields.Selection(string='Status', | ||
selection=[('accepted', 'Accepted'), | ||
('refused', 'Refused'), | ||
], | ||
help="What was the answer to the offer ?") | ||
partner_id = fields.Many2one("res.partner", required=True, name="Partner") | ||
property_id = fields.Many2one("estate.property", required=True) | ||
validity = fields.Integer(name="Validity", default=7) | ||
date_deadline = fields.Date(name="Deadline", compute="_compute_deadline", inverse="_inverse_validity") | ||
property_type_id = fields.Many2one( | ||
"estate.property.types", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't have to change it all at this point, but we prefer single quotes in python files unless using double is required for some semantic reasons |
||
related="property_id.type_id", | ||
store=True, | ||
string="Property Type" | ||
) | ||
_sql_constraints = [ | ||
('positive_offer_price', | ||
'CHECK(price > 0)', | ||
'The offer price must be strictly positive!') | ||
] | ||
|
||
@api.depends("validity") | ||
def _compute_deadline(self): | ||
for record in self: | ||
if isinstance(record.create_date, fields.Date): | ||
record.date_deadline = record.date_deadline + timedelta(days=record.validity) | ||
else: | ||
record.date_deadline = fields.Date.today() + timedelta(days=record.validity) | ||
|
||
@api.model_create_multi | ||
def create(self, vals): | ||
if vals['price'] < self.env['estate.property'].browse(vals['property_id']).best_price: | ||
raise ValueError("Can't make an offer this low") | ||
return super(EstatePropertyOffer, self).create(vals) | ||
|
||
def _inverse_validity(self): | ||
for record in self: | ||
if record.date_deadline: | ||
create_date = record.create_date.date() if record.create_date else fields.Date.today() | ||
record.validity = (record.date_deadline - create_date).days | ||
else: | ||
record.validity = 0 | ||
|
||
def action_reject_offer(self): | ||
self.status = "refused" | ||
|
||
def action_accept_offer(self): | ||
if not self.property_id.get_status(): | ||
self.status = "accepted" | ||
self.property_id.set_buyer(self.partner_id) | ||
self.property_id.set_status("sold") | ||
self.property_id.set_sold_price(self.price) | ||
else: | ||
raise exceptions.UserError("This property has already been sold !") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from odoo import models, fields | ||
|
||
class EstatePropertyTag(models.Model): | ||
|
||
_name = "estate.property.tag" | ||
_description = "All tags applicable to estate properties" | ||
_order = "name" | ||
|
||
name = fields.Char(name = "Tag name", required = True) | ||
color = fields.Integer(name="Color") | ||
_sql_constraints = [ | ||
('unique_tag_name', | ||
'UNIQUE(name)', | ||
'Tag names must be unique!') | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from odoo import models, fields, api | ||
|
||
class EstatePropertyType(models.Model): | ||
|
||
_name = "estate.property.types" | ||
_description = "Different kind of estate properties" | ||
_order = "name" | ||
|
||
name = fields.Char(name = "Type of Estate Property", required = True) | ||
sequence = fields.Integer('Sequence', default=1, help="Used to order stages. Lower is better.") | ||
property_ids = fields.One2many("estate.property", "type_id", name = "Properties") | ||
offer_ids = fields.One2many("estate.property.offer", "property_type_id", name="Offers") | ||
offer_number = fields.Integer(compute="_count_offers", name =" Offers") | ||
|
||
@api.depends("offer_ids") | ||
def _count_offers(self): | ||
for record in self: | ||
record.offer_number = len(record.offer_ids) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class ResUsers(models.Model): | ||
_inherit = "res.users" | ||
|
||
property_ids = fields.One2many("estate.property", "salesperson_id", string="Properties", domain=[('state', 'in', ['new', 'offer received'])]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink | ||
estate.access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 | ||
estate.access_estate_property_type,access_estate_property_type,model_estate_property_types,base.group_user,1,1,1,1 | ||
estate.access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 | ||
estate.access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 | ||
res.users,res.users,model_res_users,base.group_user,1,1,1,1 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<odoo> | ||
<data> | ||
<menuitem id="properties_menu_root" name="Estate Properties"> | ||
<menuitem id="properties_first_level_menu" name="Dashboard"> | ||
<menuitem id="properties_model_menu_action" action="action_estate_property"/> | ||
</menuitem> | ||
<menuitem id="properties_types_first_level_menu" name="Categories"> | ||
<menuitem id="properties_model_type_menu_action" action="action_estate_property_type"/> | ||
<menuitem id="properties_model_tag_menu_action" action="action_estate_property_tag"/> | ||
<menuitem id="properties_model_offer_menu_action" action="action_estate_property_offer"/> | ||
</menuitem> | ||
</menuitem> | ||
</data> | ||
</odoo> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<odoo> | ||
<data> | ||
<record id="view_estate_property_offer_list" model="ir.ui.view"> | ||
<field name="name">estate.property.offer.list</field> | ||
<field name="model">estate.property.offer</field> | ||
<field name="type">list</field> | ||
<field name="arch" type="xml"> | ||
<list editable="bottom" decoration-success="status in ['accepted']" decoration-danger="status in ['refused']"> | ||
<field name="price"/> | ||
<field name="partner_id"/> | ||
<field name="date_deadline"/> | ||
<button name="action_accept_offer" string="Accept" type="object" icon="fa-check" invisible="status in ['accepted', 'refused']"/> | ||
<button name="action_reject_offer" string="Reject" type="object" icon="fa-times" invisible="status in ['accepted', 'refused']"/> | ||
<field name="property_type_id" width="120px"/> | ||
<field name="status" column_invisible="1"/> | ||
</list> | ||
</field> | ||
</record> | ||
<record id="action_estate_property_offer" model="ir.actions.act_window"> | ||
<field name="name">Offers</field> | ||
<field name="res_model">estate.property.offer</field> | ||
<field name="view_mode">list,form</field> | ||
<field name="view_id" ref="view_estate_property_offer_list"/> | ||
</record> | ||
<record id="action_estate_property_offer_from_types" model="ir.actions.act_window"> | ||
<field name="name">Offers From Types</field> | ||
<field name="res_model">estate.property.offer</field> | ||
<field name="view_mode">list,form</field> | ||
<field name="domain">[('property_type_id', '=', active_id)]</field> | ||
<field name="view_id" ref="view_estate_property_offer_list"/> | ||
</record> | ||
|
||
</data> | ||
</odoo> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<odoo> | ||
<data> | ||
<record id="view_estate_property_tag_list" model="ir.ui.view"> | ||
<field name="name">estate.property.tag.list</field> | ||
<field name="model">estate.property.tag</field> | ||
<field name="type">list</field> | ||
<field name="arch" type="xml"> | ||
<list editable="bottom"> | ||
<field name="name"/> | ||
</list> | ||
</field> | ||
</record> | ||
<record id="action_estate_property_tag" model="ir.actions.act_window"> | ||
<field name="name">Tags</field> | ||
<field name="res_model">estate.property.tag</field> | ||
<field name="view_mode">list,form</field> | ||
<field name="view_id" ref="view_estate_property_tag_list"/> | ||
</record> | ||
|
||
</data> | ||
</odoo> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove extra lines