-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[ADD] estate: add new module #1359
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
3494f5d
f229961
cce8358
1a3164e
828848e
970474a
71b0a79
b5fad8a
3a5ae91
df3ac4e
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 |
|---|---|---|
|
|
@@ -127,3 +127,4 @@ dmypy.json | |
|
|
||
| # Pyre type checker | ||
| .pyre/ | ||
| estate/models/ir_ui_view.py | ||
| 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,19 @@ | ||
| { | ||
| 'name': 'Real Estate', | ||
| 'version': '1.0', | ||
| 'category': 'tutorials', | ||
| 'depends': ['base'], | ||
| 'data': [ | ||
| 'security/ir.model.access.csv', | ||
| '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', | ||
| ], | ||
| 'installable': True, | ||
| 'application': True, | ||
| 'author': 'Odoo S.A.', | ||
| 'description': """Training module for real estate""", | ||
| 'license': 'LGPL-3', | ||
| } | ||
| 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 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,90 @@ | ||||||||||||||||||||||||||
| from odoo import fields, models, api | ||||||||||||||||||||||||||
| from odoo.exceptions import UserError | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| class EstateProperties(models.Model): | ||||||||||||||||||||||||||
| _name = "estate.property" | ||||||||||||||||||||||||||
| _description = "Real Estate Properties" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| active = fields.Boolean(default=True) | ||||||||||||||||||||||||||
| bedrooms = fields.Integer(default=2) | ||||||||||||||||||||||||||
| best_price = fields.Float(compute="_compute_best_price", string="Best Offer") | ||||||||||||||||||||||||||
| buyer = fields.Many2one("res.partner", string="Buyer", copy=False) | ||||||||||||||||||||||||||
| description = fields.Text() | ||||||||||||||||||||||||||
| date_availability = fields.Date( | ||||||||||||||||||||||||||
| copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3) | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| expected_price = fields.Float(required=True) | ||||||||||||||||||||||||||
| 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"), | ||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||
|
Comment on lines
+24
to
+29
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
Try to keep the key i.e the technical strings in single quotes and the values which are to be displayed to the user in double quotes |
||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| living_area = fields.Integer() | ||||||||||||||||||||||||||
| name = fields.Char(required=True) | ||||||||||||||||||||||||||
| postcode = fields.Char() | ||||||||||||||||||||||||||
| offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") | ||||||||||||||||||||||||||
| property_type_id = fields.Many2one("estate.property.type", string="Property Type") | ||||||||||||||||||||||||||
| selling_price = fields.Float(readonly=True, copy=False) | ||||||||||||||||||||||||||
| state = fields.Selection( | ||||||||||||||||||||||||||
| selection=[ | ||||||||||||||||||||||||||
| ('new', "New"), | ||||||||||||||||||||||||||
| ('offer received', "Offer Received"), | ||||||||||||||||||||||||||
| ('offer accepted', "Offer Accepted"), | ||||||||||||||||||||||||||
| ('sold', "Sold"), | ||||||||||||||||||||||||||
| ('canceled', "Canceled"), | ||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||
| required=True, | ||||||||||||||||||||||||||
| copy=False, | ||||||||||||||||||||||||||
| default="new", | ||||||||||||||||||||||||||
|
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. What if we don't add a default value?
Author
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. Without a default value, the state field would be empty when creating a new record until the user explicitly selects a value. 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. And what if the required field is also readonly? |
||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| sales_person = fields.Many2one( | ||||||||||||||||||||||||||
| "res.users", string="Sales Person", default=lambda self: self.env.user | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| tag_ids = fields.Many2many("estate.property.tag", string="Tags") | ||||||||||||||||||||||||||
| total_area = fields.Float( | ||||||||||||||||||||||||||
| compute="_compute_total_area", string="Total Area", store=True | ||||||||||||||||||||||||||
|
mash-odoo marked this conversation as resolved.
|
||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| @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: | ||||||||||||||||||||||||||
| 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): | ||||||||||||||||||||||||||
| for record in self: | ||||||||||||||||||||||||||
| if record.garden: | ||||||||||||||||||||||||||
| record.garden_area = 10 | ||||||||||||||||||||||||||
| record.garden_orientation = "north" | ||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||
| record.garden_area = 0 | ||||||||||||||||||||||||||
| record.garden_orientation = False | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def action_sold(self): | ||||||||||||||||||||||||||
| for record in self: | ||||||||||||||||||||||||||
| if record.state == "canceled": | ||||||||||||||||||||||||||
| raise UserError("Canceled properties cannot be sold.") | ||||||||||||||||||||||||||
| record.state = "sold" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def action_cancel(self): | ||||||||||||||||||||||||||
| for record in self: | ||||||||||||||||||||||||||
| if record.state == "sold": | ||||||||||||||||||||||||||
| raise UserError("Sold properties cannot be canceled.") | ||||||||||||||||||||||||||
| record.state = "canceled" | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| from odoo import models, fields, api | ||
|
|
||
|
|
||
| class EstatePropertyOffer(models.Model): | ||
| _name = "estate.property.offer" | ||
| _description = "Estate Property Offer" | ||
|
|
||
| date_deadline = fields.Date( | ||
| string="Deadline", | ||
| compute="_compute_date_deadline", | ||
| inverse="_inverse_date_deadline", | ||
| ) | ||
| price = fields.Float(string="Price") | ||
| partner_id = fields.Many2one("res.partner", string="Partner", required=True) | ||
| property_id = fields.Many2one("estate.property", string="Property", required=True) | ||
| status = fields.Selection( | ||
| selection=[('accepted', "Accepted"), ('refused', "Refused")], copy=False | ||
| ) | ||
| validity = fields.Integer(string="Validity (days)", default=7) | ||
|
|
||
| @api.depends("create_date", "validity") | ||
|
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. Where does this create_date field come from? 🤔
Author
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. create_date is a built-in magical field automatically added by models.Model, so it is available on this model without being explicitly declared. 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. Are you aware of how magic fields are declared when creating a table? |
||
| def _compute_date_deadline(self): | ||
| for record in self: | ||
| create_date = record.create_date or fields.Date.today() | ||
| record.date_deadline = fields.Date.add( | ||
| fields.Date.to_date(create_date), days=record.validity | ||
| ) | ||
|
|
||
| def _inverse_date_deadline(self): | ||
| for record in self: | ||
| create_date = record.create_date or fields.Date.today() | ||
| record.validity = ( | ||
| record.date_deadline - fields.Date.to_date(create_date) | ||
| ).days | ||
|
|
||
| def action_accept(self): | ||
| for record in self: | ||
| record.status = "accepted" | ||
| record.property_id.selling_price = record.price | ||
| record.property_id.buyer = record.partner_id | ||
| record.property_id.state = "offer accepted" | ||
|
|
||
| def action_refuse(self): | ||
| for record in self: | ||
| record.status = "refused" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| from odoo import models, fields | ||
|
|
||
|
|
||
| class EstatePropertyTag(models.Model): | ||
| _name = "estate.property.tag" | ||
| _description = "Estate Property Tag" | ||
|
|
||
| name = fields.Char(required=True) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class EstatePropertyType(models.Model): | ||
| _name = "estate.property.type" | ||
| _description = "Real Estate Property Types" | ||
|
|
||
| name = fields.Char(required=True) |
| 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 | ||
| estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1, | ||
| access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 | ||
| estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1 | ||
| estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <menuitem id="estate_menu_root" name="Real Estate"/> | ||
| <menuitem id="estate_menu_advertisement" name="Advertisement" parent="estate_menu_root" /> | ||
| <menuitem id="estate_menu_property" name="Properties" parent="estate_menu_advertisement" action="estate_property_action" /> | ||
| <menuitem id="estate_menu_settings" name="Settings" parent="estate_menu_root" /> | ||
| <menuitem id="estate_menu_property_type" name="Property Types" parent="estate_menu_settings" action="estate_property_type_action" /> | ||
| <menuitem id="estate_menu_property_tag" name="Property Tags" parent="estate_menu_settings" action="estate_property_tag_action" /> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| <?xml version="1.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. Why do we need to write this line?
Author
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. It's the XML declaration that identifies the file as an XML 1.0 document. It's the standard way to start an XML file. 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. What if we skip writing this? |
||
| <odoo> | ||
|
|
||
| <!-- List View --> | ||
| <record id="estate_property_offer_list_view" model="ir.ui.view"> | ||
| <field name="name">estate.property.offer.list</field> | ||
| <field name="model">estate.property.offer</field> | ||
| <field name="arch" type="xml"> | ||
| <list> | ||
| <field name="price"/> | ||
| <field name="date_deadline"/> | ||
| <field name="status"/> | ||
| <field name="partner_id"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
|
|
||
| <!-- Form View --> | ||
| <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,8 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <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> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <record id="estate_property_action" model="ir.actions.act_window"> | ||
| <field name="name">Properties</field> | ||
| <field name="res_model">estate.property</field> | ||
| <field name="view_mode">list,form</field> | ||
| </record> | ||
|
|
||
| <!-- List View --> | ||
| <record id="estate_property_list_view" model="ir.ui.view"> | ||
| <field name="name">estate.property.list</field> | ||
| <field name="model">estate.property</field> | ||
| <field name="arch" type="xml"> | ||
| <list > | ||
| <field name="name"/> | ||
| <field name="property_type_id"/> | ||
| <field name="postcode"/> | ||
| <field name="bedrooms"/> | ||
| <field name="living_area"/> | ||
| <field name="expected_price" /> | ||
| <field name="selling_price"/> | ||
| <field name="date_availability"/> | ||
| <field name="tag_ids" widget="many2many_tags"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
|
|
||
| <!-- Form View --> | ||
| <record id="estate_property_view_form" model="ir.ui.view"> | ||
| <field name="name">estate.property.form</field> | ||
| <field name="model">estate.property</field> | ||
| <field name="arch" type="xml"> | ||
| <form> | ||
| <header> | ||
| <button name="action_sold" type="object" string="Sold" class="btn-primary"/> | ||
| <button name="action_cancel" type="object" string="Cancel" class="btn-secondary"/> | ||
| </header> | ||
| <sheet> | ||
| <div class="oe_title"> | ||
| <h1> | ||
| <field name="name"/> | ||
| </h1> | ||
| <group> | ||
| <field name="tag_ids" widget="many2many_tags"/> | ||
| </group> | ||
| </div> | ||
| <group> | ||
| <group> | ||
| <field name="state"/> | ||
| <field name="property_type_id"/> | ||
| <field name="postcode"/> | ||
| <field name="date_availability"/> | ||
| </group> | ||
| <group> | ||
| <field name="expected_price"/> | ||
| <field name="selling_price"/> | ||
| <field name="best_price" readonly="1"/> | ||
| </group> | ||
| </group> | ||
| <notebook> | ||
| <page string="Description"> | ||
| <group> | ||
| <field name="description"/> | ||
| <field name="bedrooms"/> | ||
| <field name="living_area"/> | ||
| <field name="facades"/> | ||
| <field name="garage"/> | ||
| <field name="garden"/> | ||
| <field name="garden_area"/> | ||
| <field name="garden_orientation"/> | ||
| <field name="active"/> | ||
| <field name="total_area" /> | ||
| </group> | ||
| </page> | ||
| <page string="Offers"> | ||
| <field name="offer_ids"> | ||
| <list editable="bottom"> | ||
| <field name="price" default="0"/> | ||
| <field name="partner_id" default="0"/> | ||
| <field name="validity"/> | ||
| <field name="date_deadline"/> | ||
| <button name="action_accept" type="object" icon="fa-check"/> | ||
| <button name="action_refuse" type="object" icon="fa-times"/> | ||
| <field name="status"/> | ||
| </list> | ||
| </field> | ||
| </page> | ||
| <page string="Other Info"> | ||
| <group> | ||
| <field name="sales_person"/> | ||
| <field name="buyer"/> | ||
| </group> | ||
| </page> | ||
| </notebook> | ||
| </sheet> | ||
| </form> | ||
| </field> | ||
| </record> | ||
|
|
||
| <!-- search --> | ||
| <record id="estate_property_view_search" model="ir.ui.view"> | ||
| <field name="name">estate.property.search</field> | ||
| <field name="model">estate.property</field> | ||
| <field name="arch" type="xml"> | ||
| <search> | ||
| <field name="name"/> | ||
| <field name="property_type_id"/> | ||
| <field name="postcode"/> | ||
| <field name="expected_price"/> | ||
| <field name="bedrooms"/> | ||
| <field name="living_area"/> | ||
| <field name="facades"/> | ||
| <filter | ||
| string="Available" | ||
| name="available" | ||
| domain="[('state', 'in', ['new','offer_received'])]"/> | ||
|
|
||
| <filter | ||
| string="Postcode" | ||
| name="group_postcode" | ||
| context="{'group_by':'postcode'}"/> | ||
| </search> | ||
| </field> | ||
| </record> | ||
|
|
||
| </odoo> |
Uh oh!
There was an error while loading. Please reload this page.