-
Notifications
You must be signed in to change notification settings - Fork 2.8k
[ADD] Estate Module (Python Framework Training) #1068
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
e6f8dbd
4a69d2f
e47cf30
bf9de48
ce471ef
0225b68
cb0c70a
1a772bd
17ffc1d
657bfe4
0f04b0e
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 @@ | ||
| from . import models |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "name": "Real Estate", | ||
| "description": "Real Estate Management System", | ||
| "category": "Tutorials", | ||
| "version": "1.1", | ||
| "application": True, | ||
| "data": [ | ||
| "security/ir.model.access.csv", | ||
| "views/views.xml", | ||
| "views/menus.xml" | ||
| ], | ||
| "author": "Odoo S.A.", | ||
| "license": "LGPL-3", | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| from . import building | ||
| from . import building_type | ||
| from . import tag | ||
| from . import offer |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| from odoo import models, fields, api | ||
| from odoo.exceptions import UserError | ||
| from datetime import timedelta | ||
|
|
||
|
|
||
| class Building(models.Model): | ||
| _name = "estate.buildings" | ||
| _description = "Buildings" | ||
|
|
||
| name = fields.Char() | ||
| description = fields.Text() | ||
| value = fields.Integer(copy=False) | ||
| availability_date = fields.Date( | ||
| default=lambda self: fields.Date.today() + timedelta(days=90), copy=False | ||
| ) | ||
| number_of_rooms = fields.Integer(default=2) | ||
| garden_area = fields.Integer() | ||
| building_area = fields.Integer() | ||
| garden_orientation = fields.Selection( | ||
| [("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")], | ||
| "garden Orientation", | ||
| ) | ||
| active = fields.Boolean(default=True) | ||
| state = fields.Selection( | ||
| [ | ||
| ("new", "New"), | ||
| ("offer_received", "Offer Received"), | ||
| ("offer_accepted", "Offer Accepted"), | ||
| ("sold", "Sold"), | ||
| ("canceled", "Canceled"), | ||
| ], | ||
| default="new", | ||
| ) | ||
| post_code = fields.Integer(default=1000) | ||
| building_type_id = fields.Many2one("estate.building_type", string="Building Type") | ||
| buyer_id = fields.Many2one("res.partner", string="Buyer") | ||
| salesperson_id = fields.Many2one( | ||
| "res.users", string="Salesperson", default=lambda self: self.env.user | ||
| ) | ||
| tag_ids = fields.Many2many("estate.building_tags", string="Tags") | ||
| offer_ids = fields.One2many("estate.offers", "building_id", string="Offers") | ||
|
|
||
| total_area = fields.Integer(string="Total Area", compute="_compute_total_area") | ||
|
|
||
| best_price = fields.Integer( | ||
| string="Best Offer Price", | ||
| compute="_compute_best_price", | ||
| ) | ||
| has_garden = fields.Boolean(string="Has Garden", default=False) | ||
|
|
||
| _price_constraint = models.Constraint( | ||
| "CHECK (value > 0)", "Price must be POSITIVE." | ||
| ) | ||
| _name_constraint = models.Constraint( | ||
| "UNIQUE(name)", "Building name must be UNIQUE." | ||
| ) | ||
|
|
||
| @api.depends("building_area", "garden_area") | ||
| def _compute_total_area(self): | ||
| for record in self: | ||
| record.total_area = record.building_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("has_garden") | ||
| def _onchange_garden_area(self): | ||
| if self.has_garden: | ||
| self.garden_area = 10 | ||
| self.garden_orientation = "north" | ||
| else: | ||
| self.garden_area = 0 | ||
| self.garden_orientation = False | ||
|
|
||
| def action_set_sold(self): | ||
| for record in self: | ||
| if record.state == "canceled": | ||
| raise UserError(self.env._("Canceled buildings cannot be sold.")) | ||
| record.state = "sold" | ||
|
|
||
| def action_set_canceled(self): | ||
| for record in self: | ||
| if record.state == "sold": | ||
| raise UserError(self.env._("Sold buildings cannot be canceled.")) | ||
| record.state = "canceled" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class BuildingType(models.Model): | ||
| _name = "estate.building_type" | ||
| _description = "Building Type" | ||
|
|
||
| name = fields.Char(required=True) | ||
|
|
||
| _name_uniqueness_constraint = models.Constraint( | ||
| "UNIQUE (name)", "Building type name must be UNIQUE." | ||
| ) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,85 @@ | ||||||||||||||||||||
| from odoo import models, fields, api | ||||||||||||||||||||
| from odoo.exceptions import UserError | ||||||||||||||||||||
| from datetime import timedelta | ||||||||||||||||||||
| from odoo.tools.float_utils import float_compare | ||||||||||||||||||||
|
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. Nit here
Suggested change
|
||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| class Offer(models.Model): | ||||||||||||||||||||
| _name = "estate.offers" | ||||||||||||||||||||
|
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 as for buildings, make it consistent
Suggested change
|
||||||||||||||||||||
| _description = "Offers" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| price = fields.Integer(required=True) | ||||||||||||||||||||
| status = fields.Selection( | ||||||||||||||||||||
| [("accepted", "Accepted"), ("refused", "Refused")], | ||||||||||||||||||||
| string="Status", | ||||||||||||||||||||
| required=False, | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| building_id = fields.Many2one("estate.buildings", string="Building") | ||||||||||||||||||||
| partner_id = fields.Many2one("res.partner", string="Partner") | ||||||||||||||||||||
| validity = fields.Integer(string="Validity (days)", default=7) | ||||||||||||||||||||
| date_deadline = fields.Date( | ||||||||||||||||||||
| string="Deadline", | ||||||||||||||||||||
| compute="_compute_date_deadline", | ||||||||||||||||||||
| inverse="_inverse_date_deadline", | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| _price_positive_constraint = models.Constraint( | ||||||||||||||||||||
| "CHECK (price > 0)", "Offer price must be positive." | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| @api.depends("validity") | ||||||||||||||||||||
| def _compute_date_deadline(self): | ||||||||||||||||||||
| for record in self: | ||||||||||||||||||||
| record.date_deadline = fields.Date.today() + timedelta(days=record.validity) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def _inverse_date_deadline(self): | ||||||||||||||||||||
| for record in self: | ||||||||||||||||||||
| record.validity = (record.date_deadline - fields.Date.today()).days | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def action_accept_offer(self): | ||||||||||||||||||||
| for record in self: | ||||||||||||||||||||
| if record.status != "accepted" and record.building_id.state not in [ | ||||||||||||||||||||
| "sold", | ||||||||||||||||||||
| "canceled", | ||||||||||||||||||||
| ]: | ||||||||||||||||||||
|
Comment on lines
+41
to
+44
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. IMHO this is more readable
Suggested change
|
||||||||||||||||||||
| record.status = "accepted" | ||||||||||||||||||||
| record.building_id.state = "offer_accepted" | ||||||||||||||||||||
| record.building_id.buyer_id = record.partner_id | ||||||||||||||||||||
| record.building_id.value = record.price | ||||||||||||||||||||
| other_offers = self.search( | ||||||||||||||||||||
| [ | ||||||||||||||||||||
| ("building_id", "=", record.building_id.id), | ||||||||||||||||||||
| ("id", "!=", record.id), | ||||||||||||||||||||
| ] | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| other_offers.write({"status": "refused"}) | ||||||||||||||||||||
| elif record.building_id.state in ["sold", "canceled"]: | ||||||||||||||||||||
| raise UserError( | ||||||||||||||||||||
| self.env._("Cannot accept offers for sold or canceled buildings.") | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
Comment on lines
+57
to
+59
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
|
||||||||||||||||||||
| else: | ||||||||||||||||||||
| raise UserError(self.env._("Offer is already accepted.")) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def action_refuse_offer(self): | ||||||||||||||||||||
| for record in self: | ||||||||||||||||||||
| if record.status != "refused": | ||||||||||||||||||||
| record.status = "refused" | ||||||||||||||||||||
| record.building_id.state = "offer_received" | ||||||||||||||||||||
| record.building_id.buyer_id = False | ||||||||||||||||||||
| else: | ||||||||||||||||||||
| raise UserError(self.env._("Offer is already refused.")) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| @api.constrains("building_id", "price") | ||||||||||||||||||||
| def _check_price(self): | ||||||||||||||||||||
| for record in self: | ||||||||||||||||||||
| if ( | ||||||||||||||||||||
| float_compare( | ||||||||||||||||||||
| 0.9 * record.building_id.value, record.price, precision_digits=2 | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| == 1 | ||||||||||||||||||||
| ): | ||||||||||||||||||||
|
Comment on lines
+75
to
+80
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. As we know
Suggested change
|
||||||||||||||||||||
| raise UserError( | ||||||||||||||||||||
| self.env._( | ||||||||||||||||||||
| "Offer price must be at least 90% of the building's value." | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class BuildingTag(models.Model): | ||
| _name = "estate.building_tags" | ||
| _description = "Building Tags" | ||
|
|
||
| name = fields.Char(required=True) | ||
|
|
||
| _name_uniqueness_constraint = models.Constraint( | ||
| "UNIQUE (name)", "Building tag name must be UNIQUE." | ||
| ) |
| 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_first_model,access_first_model,model_estate_buildings,base.group_user,1,1,1,1 | ||
| access_building_type_model,access_building_type_model,model_estate_building_type,base.group_user,1,1,1,1 | ||
| access_building_tags_model,access_building_tags_model,model_estate_building_tags,base.group_user,1,1,1,1 | ||
| access_offers_model,access_offers_model,model_estate_offers,base.group_user,1,1,1,1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <odoo> | ||
| <menuitem id="test_menu_root" name="Estate FelBeit"> | ||
| <menuitem id="test_first_level_menu" name="First Level"> | ||
| <menuitem id="test_model_menu_action" action="some_model_action_1"/> | ||
| </menuitem> | ||
| </menuitem> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <odoo> | ||
| <record id="some_model_action_1" model="ir.actions.act_window"> | ||
| <field name="name">Test action 1</field> | ||
| <field name="res_model">estate.buildings</field> | ||
| <field name="view_mode">list,form</field> | ||
| </record> | ||
| <record id="list_view" model="ir.ui.view"> | ||
| <field name="name">estate.buildings.list</field> | ||
| <field name="model">estate.buildings</field> | ||
| <field name="arch" type="xml"> | ||
| <list string="test_list"> | ||
| <field name="name"/> | ||
| <field name="number_of_rooms"/> | ||
| <field name="value"/> | ||
| <field name="state"/> | ||
| <field name="building_type_id"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
|
|
||
| <record id="first_form_view" model="ir.ui.view"> | ||
| <field name="name">estate.buildings.form</field> | ||
| <field name="model">estate.buildings</field> | ||
| <field name="arch" type="xml"> | ||
| <form string="Test"> | ||
| <header> | ||
| <button name="action_set_sold" type="object" string="Sell Building"/> | ||
| <button name="action_set_canceled" type="object" string="Cancel Building"/> | ||
| </header> | ||
| <sheet> | ||
| <group> | ||
| <group> | ||
| <field name="name"/> | ||
| <field name="tag_ids" widget="many2many_tags"/> | ||
| <field name="value"/> | ||
| <field name="state"/> | ||
| <field name="availability_date"/> | ||
| <field name="building_type_id"/> | ||
| </group> | ||
| <group> | ||
| <field name="number_of_rooms"/> | ||
| <field name="post_code"/> | ||
| </group> | ||
| </group> | ||
| <notebook> | ||
| <page string="Description"> | ||
| <group> | ||
| <field name="description"/> | ||
| <field name="building_area"/> | ||
| <field name="has_garden"/> | ||
| <field name="garden_area"/> | ||
| <field name="garden_orientation"/> | ||
| <field name="total_area"/> | ||
| </group> | ||
| </page> | ||
| <page string="Info"> | ||
| <group> | ||
| <field name="buyer_id"/> | ||
| <field name="salesperson_id"/> | ||
| </group> | ||
| </page> | ||
| <page string="Offers"> | ||
| <field name="offer_ids"> | ||
| <list> | ||
| <field name="price"/> | ||
| <field name="status"/> | ||
| <field name="partner_id"/> | ||
| <field name="date_deadline"/> | ||
| <field name="validity"/> | ||
| <button name="action_accept_offer" type="object" string="Accept" icon="fa-check"/> | ||
| <button name="action_refuse_offer" type="object" string="Refuse" icon="fa-times"/> | ||
| </list> | ||
| </field> | ||
| <group> | ||
| <field name="best_price"/> | ||
| </group> | ||
| </page> | ||
| </notebook> | ||
| </sheet> | ||
| </form> | ||
| </field> | ||
| </record> | ||
|
|
||
| <record id="first_search_view" model="ir.ui.view"> | ||
| <field name="name">estate.buildings.search</field> | ||
| <field name="model">estate.buildings</field> | ||
| <field name="arch" type="xml"> | ||
| <search string="test_search"> | ||
| <field name="name"/> | ||
| <field name="value"/> | ||
| <separator/> | ||
| <filter string="Available" name="Available" domain="[('state', 'in', ['new', 'offer_received'])]"/> | ||
| <filter string="postcode" name="postcode" context="{'group_by':'post_code'}"/> | ||
| </search> | ||
|
|
||
| </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.
Good job on your refactoring but still some small consistency issues like here (and the file names)