-
Notifications
You must be signed in to change notification settings - Fork 3.4k
ardec - Technical Training #1423
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: 19.0
Are you sure you want to change the base?
Changes from all commits
7e5b1b1
9c3fc62
99d43a9
879ccf2
afc295c
47536b8
169d424
fd1f0c0
3048250
9615fa1
0fec7bb
ba9a63b
824761a
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 @@ | ||
| this file should not be removed. |
| 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,18 @@ | ||
| { | ||
| 'name': "Estate", | ||
| 'author': "Arthur De Clerck", | ||
| 'license': "LGPL-3", | ||
| 'depends': ['base'], | ||
| 'category': "Real Estate/Brokerage", | ||
| 'application': True, | ||
| 'data': [ | ||
| 'security/ir.model.access.csv', | ||
| # 'security/security.xml', | ||
|
|
||
| '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', | ||
| ] | ||
| } | ||
| 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 | ||
|
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. We usually tend to separate them on different lines, so when we add an import, we do not change git history on something we did not really change |
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,101 @@ | ||||||
| from odoo.exceptions import UserError, ValidationError | ||||||
| from odoo.tools import float_compare, float_is_zero | ||||||
|
|
||||||
| from odoo import api, fields, models | ||||||
|
Comment on lines
+1
to
+4
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. For imports, we usually try to separate and order them in different categories. And inside each of those categories we try to order them in alphabetical order |
||||||
|
|
||||||
|
|
||||||
| class EstateProperty(models.Model): | ||||||
| _name = "estate.property" | ||||||
| _description = "Estate property informations" | ||||||
|
Comment on lines
+8
to
+9
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. A good convention is to use single quotes for technical strings, and double for user interfaced strings. Basically _name would be single and _description double. This applies everywhere :) |
||||||
|
|
||||||
| name = fields.Char(required=True, string="Title") | ||||||
| description = fields.Text() | ||||||
| postcode = fields.Char() | ||||||
| date_availability = fields.Date(copy=False, default=fields.Date.add(fields.Date.today(), months=3), string="Available From") | ||||||
|
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 should use a lambda function for your default otherwise the default would be static and set up when the server starts not everytime we create a new record. |
||||||
| expected_price = fields.Float(required=True) | ||||||
| selling_price = fields.Float(readonly=True, copy=False) | ||||||
|
Comment on lines
+15
to
+16
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. For monetary values, you should use a Monetary field ans add a currency field. 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. This applies also at multiple places. |
||||||
| bedrooms = fields.Integer(default=2) | ||||||
| living_area = fields.Integer(string="Living Area (sqm)") | ||||||
| facades = fields.Integer() | ||||||
| garage = fields.Boolean() | ||||||
| garden = fields.Boolean() | ||||||
| garden_area = fields.Integer(string="Garden Area (sqm)") | ||||||
| garden_orientation = fields.Selection( | ||||||
| selection=[ | ||||||
| ('north', 'North'), | ||||||
| ('south', 'South'), | ||||||
| ('east', 'East'), | ||||||
| ('west', 'West'), | ||||||
| ], | ||||||
| help="Orientation precises where the garden is oriented" | ||||||
| ) | ||||||
| state = fields.Selection( | ||||||
| selection=[ | ||||||
| ('new', 'New'), | ||||||
| ('offer_received', 'Offer Received'), | ||||||
| ('offer_accepted', 'Offer Accepted'), | ||||||
| ('sold', 'Sold'), | ||||||
| ('cancelled', 'Cancelled'), | ||||||
| ], | ||||||
| required=True, | ||||||
| default='new', | ||||||
| ) | ||||||
| property_type_id = fields.Many2one("estate.property.type", string="Property Type") | ||||||
|
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. Try to always name your parameter so it is easier to read. |
||||||
| buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) | ||||||
| salesperson_id = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.user) | ||||||
| property_tag_ids = fields.Many2many("estate.property.tag") | ||||||
| offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers", copy=False) | ||||||
|
|
||||||
| total_area = fields.Float(compute="_compute_total_area", string="Total Area (sqm)") | ||||||
| best_price = fields.Float(compute="_compute_best_price") | ||||||
|
|
||||||
| @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: | ||||||
| record.best_price = max(record.offer_ids.mapped('price')) if len(record.offer_ids) > 0 else 0 | ||||||
|
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.
Suggested change
|
||||||
|
|
||||||
| @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 | ||||||
|
|
||||||
| active = fields.Boolean(default=True) | ||||||
|
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. Do not separate your fields like this put them together at the top of the class. Then you can put your computes, on changes, ... |
||||||
|
|
||||||
| def sell_property(self): | ||||||
| for record in self: | ||||||
| if record.state == "cancelled": | ||||||
| raise UserError("Cancelled properties cannot be sold.") | ||||||
|
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 need to translate this using self.env._("..."). This applies at other places too. |
||||||
| record.state = "sold" | ||||||
| return True | ||||||
|
|
||||||
| def cancel_property(self): | ||||||
| for record in self: | ||||||
| if record.state == "sold": | ||||||
| raise UserError("Sold properties cannot be cancelled.") | ||||||
| record.state = "cancelled" | ||||||
| return True | ||||||
|
|
||||||
| _check_positive_expected_price = models.Constraint( | ||||||
| 'CHECK(expected_price >= 0)', | ||||||
| 'The expected price should be positive.' | ||||||
| ) | ||||||
|
|
||||||
| _check_positive_selling_price = models.Constraint( | ||||||
| 'CHECK(selling_price >= 0)', | ||||||
| 'The selling price should be positive.' | ||||||
| ) | ||||||
|
Comment on lines
+87
to
+95
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. Same put them after the fields but before any functions. |
||||||
|
|
||||||
| @api.constrains('selling_price', 'expected_price') | ||||||
| def _check_selling_price(self): | ||||||
| for record in self: | ||||||
| if float_compare(record.selling_price, record.expected_price * 0.9, 2) <= 0 and not float_is_zero(record.selling_price, 2): | ||||||
|
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. if you use a currency, you should use the currency comparison which are more accurate and have different roundings per currencies |
||||||
| raise ValidationError(r'The selling price must be at least 90 % of the expected price.') | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| from odoo.exceptions import UserError | ||
|
|
||
| from odoo import api, fields, models | ||
|
|
||
|
|
||
| class EstatePropertyOffer(models.Model): | ||
| _name = "estate.property.offer" | ||
| _description = "Offers for real estate properties" | ||
|
|
||
| price = fields.Float() | ||
| status = fields.Selection( | ||
| selection=[ | ||
| ('accepted', 'Accepted'), | ||
| ('refused', 'Refused') | ||
| ], | ||
| copy=False | ||
| ) | ||
| partner_id = fields.Many2one("res.partner", string="Partner", required=True) | ||
| property_id = fields.Many2one("estate.property", string="Property", required=True) | ||
| validity = fields.Integer(default=7, string="Validity (days)") | ||
| date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline", string="Deadline", readonly=False) | ||
|
|
||
| @api.depends("create_date", "validity") | ||
| def _compute_date_deadline(self): | ||
| for record in self: | ||
| if record.create_date: | ||
| record.date_deadline = fields.Date.add(record.create_date, days=record.validity) | ||
| else: | ||
| record.date_deadline = fields.Date.add(fields.Date.today(), days=record.validity) | ||
|
|
||
| def _inverse_date_deadline(self): | ||
| for record in self: | ||
| if record.create_date: | ||
| record.validity = (record.date_deadline - record.create_date.date()).days | ||
| else: | ||
| record.validity = (record.date_deadline - fields.Date.today()).days | ||
|
|
||
| def accept_offer(self): | ||
| for record in self: | ||
| if any(status for status in record.property_id.offer_ids.mapped('status') if status == "accepted"): | ||
| raise UserError("Only one offer can be accepted by property.") | ||
| record.status = "accepted" | ||
| record.property_id.selling_price = record.price | ||
| record.property_id.buyer_id = record.partner_id | ||
| return True | ||
|
|
||
| def refuse_offer(self): | ||
| for record in self: | ||
| record.status = "refused" | ||
| return True | ||
|
|
||
| _check_price = models.Constraint( | ||
| 'CHECK(price >= 0)', | ||
| 'The offer price should be positive.' | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class EstatePropertyTag(models.Model): | ||
| _name = "estate.property.tag" | ||
| _description = "Tags for estate properties" | ||
|
|
||
| name = fields.Char(required=True) | ||
|
|
||
| _unique_name = models.Constraint( | ||
| 'UNIQUE(name)', | ||
| 'Each property tag should have a unique name.' | ||
| ) |
| 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 = "Estate property types" | ||
|
|
||
| name = fields.Char(required=True) | ||
|
|
||
| _unique_name = models.Constraint( | ||
| 'UNIQUE(name)', | ||
| 'Each property type should have a unique name.' | ||
| ) |
| 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_property,access.estate.property,model_estate_property,base.group_user,1,1,1,1 | ||
|
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 should suffix the id and name by the group name for instance here it should be |
||
| access_estate_property_type,access.estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 | ||
| access_estate_property_tag,access.estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1 | ||
| access_estate_property_offer,access.estate.property.offer,model_estate_property_offer,base.group_user,1,1,1,1 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <odoo> | ||
| <!-- <record id="groups_privilege_real_estate" model="base.module_category_real_estate_brokerage"> | ||
| <field name="name">Real Estate</field> | ||
| </record> --> | ||
|
Comment on lines
+3
to
+5
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. 👀 |
||
| </odoo> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <menuitem id="estate_menu_root" name="Estate"> | ||
| <menuitem id="estate_advertisements_menu" name="Advertisements"> | ||
| <menuitem id="estate_property_menu_action" action="estate_property_action"/> | ||
| </menuitem> | ||
| <menuitem id="estate_settings_menu" name="Settings"> | ||
| <menuitem id="estate_property_type_menu_action" action="estate_property_type_action"/> | ||
| <menuitem id="estate_property_tag_menu_action" action="estate_property_tag_action"/> | ||
| </menuitem> | ||
| </menuitem> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <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> |
| 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> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <record id="estate_property_type_view_form" model="ir.ui.view"> | ||
| <field name="name">estate.property.type.form</field> | ||
| <field name="model">estate.property.type</field> | ||
| <field name="arch" type="xml"> | ||
| <form> | ||
| <sheet> | ||
| <div class="oe_title"> | ||
| <h1 class="mb32"> | ||
|
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. Does this class mb32 exists ? |
||
| <field name="name" class="mb16"/> | ||
| </h1> | ||
| </div> | ||
| </sheet> | ||
| </form> | ||
| </field> | ||
| </record> | ||
|
|
||
| <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> | ||
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.
👀