-
Notifications
You must be signed in to change notification settings - Fork 3k
Tutorials #1202
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?
Tutorials #1202
Changes from 21 commits
39c9e64
9448e18
c8a3e91
077ffd7
b266f26
1d80309
e9e2d4c
cde4aaf
5bc4790
f7a41c9
a4e18a2
6d77cf4
0b90b2f
f7402bc
d2011ff
286105a
80e0a16
c267b65
aa77f29
9ce82fb
dcdfb4c
55b2bc5
0edbed8
b1bc98e
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,6 @@ dmypy.json | |
|
|
||
| # Pyre type checker | ||
| .pyre/ | ||
|
|
||
| # Linting | ||
| pyproject.toml | ||
| 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,17 @@ | ||
| { | ||
| 'name': 'Real Estate', | ||
| 'version': '1.0', | ||
| 'depends': ['base'], | ||
| 'author': 'Anmol Dhaliwal', | ||
| 'category': 'Category', | ||
| 'data': [ | ||
| 'security/ir.model.access.csv', | ||
| 'views/estate_property_views.xml', | ||
| 'views/estate_property_offer_views.xml', | ||
| 'views/estate_property_type_views.xml', | ||
| 'views/estate_property_tag_views.xml', | ||
| 'views/estate_menus.xml', | ||
| 'views/res_users_views.xml', | ||
| ], | ||
| 'license': 'OEEL-1', | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| from . import estate_property | ||
lost-odoo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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,131 @@ | ||
| from odoo import api, fields, models, exceptions | ||
| from dateutil.relativedelta import relativedelta | ||
| from odoo.tools.float_utils import float_compare, float_is_zero | ||
|
|
||
|
|
||
| class EstateProperty(models.Model): | ||
| _name = 'estate.property' | ||
| _description = 'A specific property' | ||
|
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. Double quotes here as it is a string that will be shown to the user |
||
| _order = 'id desc' | ||
|
|
||
| name = fields.Char('Title', required=True) | ||
| description = fields.Text() | ||
| postcode = fields.Char() | ||
| date_availability = fields.Date( | ||
| 'Available From', | ||
| copy=False, | ||
| default=lambda _: fields.Date.today() + relativedelta(months=3), | ||
| ) | ||
| expected_price = fields.Float(required=True) | ||
| _expected_price = models.Constraint( | ||
| 'CHECK(expected_price > 0)', | ||
| 'The expected price must be strictly positive.', | ||
| ) | ||
| selling_price = fields.Float(readonly=True, copy=False) | ||
| _selling_price = models.Constraint( | ||
| 'CHECK(expected_price >= 0)', | ||
| 'The selling price must be positive.', | ||
| ) | ||
| 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(string='Garden Area (sqm)') | ||
| garden_orientation = fields.Selection( | ||
| string='Garden Orientation', | ||
| selection=[ | ||
| ('north', 'North'), | ||
| ('east', 'East'), | ||
| ('south', 'South'), | ||
| ('west', 'West'), | ||
| ], | ||
| ) | ||
| active = fields.Boolean(default=True) | ||
| state = fields.Selection( | ||
| string='State', | ||
| selection=[ | ||
| ('new', 'New'), | ||
| ('offer_received', 'Offer Received'), | ||
| ('offer_accepted', 'Offer Accepted'), | ||
| ('sold', 'Sold'), | ||
| ('cancelled', 'Cancelled'), | ||
| ], | ||
| required=True, | ||
| copy=False, | ||
| default='new', | ||
| ) | ||
| property_type_id = fields.Many2one('estate.property.type', string='Property Type') | ||
| salesperson_id = fields.Many2one( | ||
| 'res.users', string='Salesperson', default=lambda self: self.env.uid | ||
| ) | ||
| buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False) | ||
| tag_ids = fields.Many2many('estate.property.tag', string='Tags') | ||
| offer_ids = fields.One2many( | ||
| 'estate.property.offer', 'property_id', string='Offers' | ||
| ) | ||
| total_area = fields.Integer( | ||
| compute='_compute_total_area', string='Total Area (sqm)' | ||
| ) | ||
| best_price = fields.Float(string='Best Price', compute='_compute_best_price') | ||
|
|
||
| @api.depends('living_area', 'garden_area', 'garden') | ||
| def _compute_total_area(self): | ||
| for record in self: | ||
| record.total_area = record.living_area + ( | ||
| record.garden_area if record.garden else 0 | ||
| ) | ||
|
|
||
| @api.depends('offer_ids') | ||
| def _compute_best_price(self): | ||
| for record in self: | ||
| record.best_price = ( | ||
| max(record.offer_ids.mapped('price')) if record.offer_ids else 0 | ||
| ) | ||
|
Comment on lines
+82
to
+84
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. Could be on one line ^^ |
||
|
|
||
| @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 | ||
|
|
||
| def action_sold(self): | ||
| for record in self: | ||
| if record.state == 'cancelled': | ||
| raise exceptions.UserError('A cancelled listing 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 should import UserError directly instead of using 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. Also it needs to be translated using Do not forget about double quotes also 😄 |
||
| elif record.state == 'sold': | ||
| raise exceptions.UserError('This listing has already been sold') | ||
| else: | ||
| record.state = 'sold' | ||
| return True | ||
|
|
||
| def action_cancel(self): | ||
| for record in self: | ||
| if record.state == 'sold': | ||
| raise exceptions.UserError('Sold listings cannot be cancelled') | ||
| elif record.state == 'cancelled': | ||
| raise exceptions.UserError('This listing is already cancelled') | ||
| else: | ||
| record.state = 'cancelled' | ||
| return True | ||
|
|
||
| @api.constrains('selling_price', 'expected_price') | ||
| def _validate_selling_price(self): | ||
| for record in self: | ||
| if float_compare( | ||
| record.selling_price, record.expected_price * 0.9, 2 | ||
| ) == -1 and not float_is_zero(record.selling_price, 2): | ||
|
Comment on lines
+118
to
+120
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 is easier to read IMO 😄 |
||
| raise exceptions.ValidationError( | ||
| 'The selling price must be at least 90%% of the expected price' | ||
| ) | ||
|
|
||
|
Comment on lines
+121
to
+124
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 here and for every other exceptions ^^ |
||
| @api.ondelete(at_uninstall=False) | ||
| def _check_before_delete(self): | ||
| for record in self: | ||
| if record.state not in ('new', 'cancelled'): | ||
| raise exceptions.UserError( | ||
| 'A property cannot be deleted unless its state is New or Cancelled' | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| from odoo import api, exceptions, fields, models | ||
| from dateutil.relativedelta import relativedelta | ||
| from odoo.tools import float_compare | ||
|
|
||
|
|
||
| class EstatePropertyOffer(models.Model): | ||
| _name = 'estate.property.offer' | ||
| _description = 'An offer made on a property' | ||
|
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 here it should be double quotes. It happens at other places also. |
||
| _order = 'price desc' | ||
|
|
||
| price = fields.Float(string='Price') | ||
| _price = models.Constraint( | ||
| 'CHECK(price > 0)', | ||
| 'The offer price must be strictly positive.', | ||
|
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. Should be double quotes ^^ |
||
| ) | ||
| status = fields.Selection( | ||
| copy=False, | ||
| selection=[ | ||
| ('accepted', 'Accepted'), | ||
| ('refused', 'Refused'), | ||
| ], | ||
| ) | ||
| partner_id = fields.Many2one('res.partner', string='Partner', required=True) | ||
| property_id = fields.Many2one( | ||
| 'estate.property', string='Property', required=True, ondelete='cascade' | ||
| ) | ||
| validity = fields.Integer(string='Validity (days)', default=7) | ||
| date_deadline = fields.Date( | ||
| string='Deadline', | ||
| compute='_compute_date_deadline', | ||
| inverse='_inverse_date_deadline', | ||
| ) | ||
| property_type_id = fields.Many2one( | ||
| 'estate.property.type', related='property_id.property_type_id', store=True | ||
| ) | ||
|
|
||
| @api.depends('validity') | ||
| def _compute_date_deadline(self): | ||
| for record in self: | ||
| record.date_deadline = ( | ||
| (record.create_date + relativedelta(days=record.validity)) | ||
| if record.create_date | ||
| else (fields.Date.today() + relativedelta(days=record.validity)) | ||
| ) | ||
|
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. Honestly, it might be easier to read using a normal condition. |
||
|
|
||
| def _inverse_date_deadline(self): | ||
| for record in self: | ||
| record.validity = relativedelta( | ||
| record.date_deadline, | ||
| record.create_date if record.create_date else fields.Date.today(), | ||
| ).days | ||
|
|
||
| def action_accept(self): | ||
| for record in self: | ||
| for offer in record.property_id.offer_ids: | ||
| if offer.status == 'accepted': | ||
| raise exceptions.UserError( | ||
| 'An offer has already been accepted for this property' | ||
| ) | ||
| else: | ||
| record.status = 'accepted' | ||
| record.property_id.state = 'offer_accepted' | ||
| record.property_id.buyer_id = record.partner_id | ||
| record.property_id.selling_price = record.price | ||
| return True | ||
|
|
||
| def action_refuse(self): | ||
| for record in self: | ||
| record.status = 'refused' | ||
| return True | ||
|
|
||
| @api.model_create_multi | ||
| def create(self, vals_list): | ||
| for val in vals_list: | ||
| property_for_offer = self.env['estate.property'].browse(val['property_id']) | ||
|
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. Avoid doing a browse in a loop because that way you make a lot of sql queries. You can browse all of them beforehand then use the records in the loop. |
||
| if float_compare(val['price'], property_for_offer.best_price, 2) == -1: | ||
| raise exceptions.UserError( | ||
| 'New offers cannot be lower than existing offers' | ||
| ) | ||
| property_for_offer.state = 'offer_received' | ||
|
|
||
| return super().create(vals_list) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class EstatePropertyType(models.Model): | ||
| _name = 'estate.property.tag' | ||
| _description = 'A property tag' | ||
|
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. The Description is not really a description I know it is confusing haha. It should be the Display String of the Model. Don't forget to change it for all models ^^ |
||
| _order = 'name' | ||
|
|
||
| name = fields.Char(string='Tag Name', required=True) | ||
| _unique_name = models.Constraint('UNIQUE(name)', 'The name must be unique.') | ||
| color = fields.Integer() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| from odoo import api, fields, models | ||
|
|
||
|
|
||
| class EstatePropertyType(models.Model): | ||
| _name = 'estate.property.type' | ||
| _description = 'A type of property' | ||
| _order = 'sequence asc' | ||
|
|
||
| name = fields.Char(string='Title', required=True) | ||
| _unique_name = models.Constraint('UNIQUE(name)', 'The name must be unique.') | ||
| property_ids = fields.One2many( | ||
| 'estate.property', 'property_type_id', string='Properties' | ||
| ) | ||
| sequence = fields.Integer('Sequence', default=1) | ||
| offer_ids = fields.One2many('estate.property.offer', 'property_type_id') | ||
| offer_count = fields.Integer(compute='_compute_offer_count') | ||
|
|
||
| @api.depends('offer_ids') | ||
| def _compute_offer_count(self): | ||
| for record in self: | ||
| record.offer_count = len(record.offer_ids) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class ResUsers(models.Model): | ||
| _inherit = 'res.users' | ||
|
|
||
| property_ids = fields.One2many( | ||
| 'estate.property', | ||
| 'salesperson_id', | ||
| string='Property', | ||
| domain="['|', ('state', '=', 'New'), ('state', '=', 'Offer Received')]", | ||
|
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. The domain should not be in a string. You can directly pass the list otherwise it should not work. |
||
| ) | ||
| 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_property_user,estate_property_user,model_estate_property,base.group_user,1,1,1,1 | ||
| estate_property_type_user,estate_property_type_user,model_estate_property_type,base.group_user,1,1,1,1 | ||
| estate_property_tag_user,estate_property_tag_user,model_estate_property_tag,base.group_user,1,1,1,1 | ||
| estate_property_offer_user,estate_property_offer_user,model_estate_property_offer,base.group_user,1,1,1,1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <odoo> | ||
| </odoo> | ||
|
Comment on lines
+2
to
+3
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. Empty file here 👀 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <odoo> | ||
| <menuitem id="estate_property_menu_root" name="Estate Properties"> | ||
| <menuitem id="estate_properties_menu" name="Advertisements"> | ||
| <menuitem id="estate_property_menu_action" action="estate_property_action" /> | ||
| </menuitem> | ||
| <menuitem id="estate_properties_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,48 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <odoo> | ||
| <record id="estate_property_offer_action" model="ir.actions.act_window"> | ||
| <field name="name">View Property Offers</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> | ||
| </record> | ||
|
|
||
| <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 string="Offers" editable="bottom" decoration-success="status == 'accepted'" | ||
| decoration-danger="status == 'refused'"> | ||
| <field name="price" /> | ||
| <field name="partner_id" /> | ||
| <field name="validity" /> | ||
| <field name='date_deadline' /> | ||
| <button name="action_accept" string="Accept" type="object" icon="fa-check" | ||
| invisible="status == 'accepted' or status == 'refused'" /> | ||
| <button name="action_refuse" string="Refuse" type="object" icon="fa-times" | ||
| invisible="status == 'accepted' or status == 'refused'" /> | ||
| </list> | ||
| </field> | ||
| </record> | ||
|
|
||
| <record id="estate_property_offer_form_view" 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 string="Properties"> | ||
| <sheet> | ||
| <h1 class="mb32"> | ||
| <field name="property_id" class="mb16" /> | ||
| </h1> | ||
| <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,19 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <odoo> | ||
| <record id="estate_property_tag_action" model="ir.actions.act_window"> | ||
| <field name="name">View Property Tags</field> | ||
| <field name="res_model">estate.property.tag</field> | ||
| <field name="view_mode">list,form</field> | ||
| </record> | ||
|
|
||
| <record id="estate_property_tag_list_view" model="ir.ui.view"> | ||
| <field name="name">Property Tags</field> | ||
| <field name="model">estate.property.tag</field> | ||
| <field name="arch" type="xml"> | ||
| <list string="Tags" editable="bottom"> | ||
| <field name="name" /> | ||
| <field name="color" widget="color_picker" /> | ||
| </list> | ||
| </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.
You should not push this