-
Notifications
You must be signed in to change notification settings - Fork 2.7k
19.0 tutorial roole #1026
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?
19.0 tutorial roole #1026
Changes from 14 commits
98643d9
bcaa21c
c0d3abf
fcc0b54
e0c8d41
8d81369
975c280
2db9f2e
34dff5f
8e360b3
0593194
b51f568
eb4b23b
8fa5e7c
31b424d
6c79c93
f0f7842
f061d13
d147bb8
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/ | ||
|
|
||
| #vscode env | ||
| .vscode/ | ||
| 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': "My Awesome module Roole", | ||
| 'version': '1.0', | ||
| 'depends': ['base'], | ||
| 'author': "Odoo S.A.", | ||
| 'application': True, | ||
| 'category': 'Tutorials', | ||
| 'license': 'LGPL-3', | ||
| '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', | ||
| ], | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| from . import estate_property | ||
| from . import estate_type | ||
| from . import estate_tag | ||
| from . import estate_offer |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,63 @@ | ||||||||||||||||||
| from odoo import fields, models, api | ||||||||||||||||||
| from odoo.exceptions import UserError | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class EstateOffer(models.Model): | ||||||||||||||||||
| _name = "estate.property.offer" | ||||||||||||||||||
| _description = "property offer" | ||||||||||||||||||
|
|
||||||||||||||||||
| price = fields.Float(string='Price') | ||||||||||||||||||
| partner_id = fields.Many2one('res.partner', required=True, string='Partner') | ||||||||||||||||||
| property_id = fields.Many2one('estate.property', required=True, string='Property') | ||||||||||||||||||
| date_creation = fields.Date(readonly=True, default=fields.Date.today) | ||||||||||||||||||
| status = fields.Selection( | ||||||||||||||||||
| string='Status', | ||||||||||||||||||
| copy=False, | ||||||||||||||||||
| selection=[('accepted', 'Accepted'), ('refused', 'Refused')], | ||||||||||||||||||
| ) | ||||||||||||||||||
| validity = fields.Integer(default=7) | ||||||||||||||||||
| date_deadline = fields.Date( | ||||||||||||||||||
| string='Deadline', compute="_compute_deadline", inverse="_inverse_deadline" | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| _check_price = models.Constraint( | ||||||||||||||||||
| 'CHECK(price >= 0)', 'The offer price must be positive' | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| @api.depends('validity') | ||||||||||||||||||
| def _compute_deadline(self): | ||||||||||||||||||
| for record in self: | ||||||||||||||||||
|
Contributor
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
We more and more try to avoid generic "record" naming in those cases to make it more readable. |
||||||||||||||||||
| record.date_deadline = fields.Date.add( | ||||||||||||||||||
| fields.Date.today(), days=record.validity | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| @api.onchange('date_deadline') | ||||||||||||||||||
| def _inverse_deadline(self): | ||||||||||||||||||
| for record in self: | ||||||||||||||||||
| record.validity = (record.date_deadline - fields.Date.today()).days | ||||||||||||||||||
|
|
||||||||||||||||||
| def action_accept(self): | ||||||||||||||||||
| for record in self: | ||||||||||||||||||
| for offers in record.property_id.offer_ids: | ||||||||||||||||||
| offers.status = "refused" | ||||||||||||||||||
|
Comment on lines
+43
to
+44
Contributor
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 can actually do this since you're setting all records of the recordset to the same value. And take care of the plural misused:
Suggested change
|
||||||||||||||||||
| record.status = "accepted" | ||||||||||||||||||
| record.property_id.state = "offer_accepted" | ||||||||||||||||||
| record.property_id.selling_price = record.price | ||||||||||||||||||
| record.property_id.buyer_id = record.partner_id | ||||||||||||||||||
|
Comment on lines
+46
to
+48
Contributor
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
You can also do this in one call. Tbh the ORM will most likely process all the write in one batch anyway but still, it's a good practice to do this. |
||||||||||||||||||
| return True | ||||||||||||||||||
|
|
||||||||||||||||||
| def action_refuse(self): | ||||||||||||||||||
| for record in self: | ||||||||||||||||||
| if record.status == "accepted": | ||||||||||||||||||
| record.property_id.state = "offer_received" | ||||||||||||||||||
| record.property_id.selling_price = 0.0 | ||||||||||||||||||
| record.property_id.buyer_id = None | ||||||||||||||||||
|
|
||||||||||||||||||
| record.status = "refused" | ||||||||||||||||||
| return True | ||||||||||||||||||
|
|
||||||||||||||||||
| @api.ondelete(at_uninstall=False) | ||||||||||||||||||
| def _unlink_offer(self): | ||||||||||||||||||
| for record in self: | ||||||||||||||||||
| if record.status == "accepted": | ||||||||||||||||||
| raise UserError("You can't delete an accepted offer.") | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,112 @@ | ||||||
| from odoo import fields, models, api | ||||||
| from odoo.exceptions import UserError, ValidationError | ||||||
| from odoo.tools.float_utils import float_compare | ||||||
|
|
||||||
|
|
||||||
| class EstateProperty(models.Model): | ||||||
| _name = "estate.property" | ||||||
| _description = "Estate description" | ||||||
|
|
||||||
| name = fields.Char(required=True) | ||||||
| tag_ids = fields.Many2many("estate.property.tag") | ||||||
| property_type_id = fields.Many2one("estate.property.type", string="Type") | ||||||
| user_id = fields.Many2one('res.users', string='Salesman', index=True, default=lambda self: self.env.user) | ||||||
| buyer_id = fields.Many2one('res.partner', string='Buyer', index=True) | ||||||
| offer_ids = fields.One2many( | ||||||
| 'estate.property.offer', inverse_name='property_id', string="Offers" | ||||||
| ) | ||||||
| description = fields.Text() | ||||||
| postcode = fields.Char() | ||||||
| date_availability = fields.Date( | ||||||
| copy=False, | ||||||
| default=fields.Date.add(fields.Date.today(), months=3), | ||||||
| string="Available from", | ||||||
| ) | ||||||
| expected_price = fields.Float(required=True) | ||||||
| selling_price = fields.Float(readonly=True, copy=False) | ||||||
| bedrooms = fields.Integer(default=2) | ||||||
| living_area = fields.Integer() | ||||||
| facades = fields.Integer() | ||||||
| garage = fields.Boolean() | ||||||
| garden = fields.Boolean() | ||||||
| total_area = fields.Integer(compute="_compute_total_area") | ||||||
| garden_area = fields.Integer() | ||||||
| best_price = fields.Float(compute="_compute_best_price") | ||||||
| garden_orientation = fields.Selection( | ||||||
| string='Orientation', | ||||||
| selection=[ | ||||||
| ('north', 'North'), | ||||||
| ('south', 'South'), | ||||||
| ('west', 'West'), | ||||||
| ('east', 'East') | ||||||
| ]) | ||||||
| active = fields.Boolean(default=True) | ||||||
| state = fields.Selection( | ||||||
| string="Estate status", | ||||||
| selection=[ | ||||||
| ('new', 'New'), | ||||||
| ('offer_received', 'Offer Received'), | ||||||
| ('offer_accepted', 'Offer Accepted'), | ||||||
| ('sold', 'Sold'), | ||||||
| ('cancelled', 'Cancelled'), | ||||||
| ], | ||||||
| help='This field explain the estate status.', | ||||||
| required=True, | ||||||
| copy=False, | ||||||
| default='new', | ||||||
| ) | ||||||
|
|
||||||
| _check_expected_price = models.Constraint( | ||||||
| 'CHECK(expected_price > 0)', 'The expected price must be strictly positive' | ||||||
| ) | ||||||
| _check_selling_price = models.Constraint( | ||||||
| 'CHECK(selling_price >= 0)', 'The selling price must be positive' | ||||||
| ) | ||||||
|
|
||||||
| @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 len(record.offer_ids) > 0: | ||||||
|
Contributor
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
an empty recordset will return a Falsy value when evaluated like this, no need to check the lenght. |
||||||
| record.best_price = max(record.offer_ids.mapped("price")) | ||||||
|
Contributor
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 can avoid the if_else and do a one liner like this if you want 😉
Suggested change
|
||||||
| else: | ||||||
| record.best_price = 0.0 | ||||||
|
|
||||||
| @api.onchange('garden') | ||||||
| def _inverse_garden(self): | ||||||
| for record in self: | ||||||
| if record.garden: | ||||||
| record.garden_orientation = 'north' | ||||||
| record.garden_area = 10 | ||||||
| else: | ||||||
| record.garden_orientation = '' | ||||||
| record.garden_area = 0 | ||||||
|
|
||||||
| def action_sold(self): | ||||||
| for record in self: | ||||||
| if record.state == "canceled": | ||||||
| raise UserError("Canceled property can't be sold.") | ||||||
| else: | ||||||
| record.state = "sold" | ||||||
| return True | ||||||
|
Contributor
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. That return should not be in the else clause but at the very end of the method. |
||||||
|
|
||||||
| def action_cancel(self): | ||||||
| for record in self: | ||||||
| if record.state == "sold": | ||||||
| raise UserError("Sold property can't be canceled.") | ||||||
| else: | ||||||
| record.state = "canceled" | ||||||
| return True | ||||||
|
|
||||||
| @api.constrains('selling_price') | ||||||
| def _check_selling_price(self): | ||||||
| for record in self: | ||||||
| if float_compare( record.selling_price, record.expected_price * 0.9, precision_digits=4) == -1 \ | ||||||
| and (self.state == "offer_accepted"): | ||||||
| raise ValidationError( | ||||||
| "The selling price cannot be lower than 90% of the expected price." | ||||||
| ) | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class EstateTag(models.Model): | ||
| _name = "estate.property.tag" | ||
| _description = "property tag" | ||
|
|
||
| name = fields.Char(required=True) | ||
|
|
||
| _check_name = models.Constraint('UNIQUE(name)', 'The tag name must be unique') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| from odoo import fields, models | ||
|
|
||
|
|
||
| class EstateType(models.Model): | ||
| _name = "estate.property.type" | ||
| _description = "property type" | ||
|
|
||
| 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 | ||
| access_estate_user,estate.property,model_estate_property,base.group_user,1,1,1,1 | ||
| access_estate_type_user,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 | ||
| access_estate_tag_user,estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1 | ||
| access_estate_offer_user,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,12 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <menuitem id="estate_menu_root" name="Estate"> | ||
| <menuitem id="estate_first_level_menu" name="Advertisements"> | ||
| <menuitem id="estate_model_menu_action" action="estate_model_action"/> | ||
| </menuitem> | ||
| <menuitem id="estate_settings_menu" name="Settings"> | ||
| <menuitem id="estate_model_settings_type_action" action="estate_model_type_action"/> | ||
| <menuitem id="estate_model_settings_tag_action" action="estate_model_tag_action"/> | ||
| </menuitem> | ||
| </menuitem> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <!-- List view of the estate offer --> | ||
| <record id="estate_properties_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" > | ||
| <field name="price" string="Price"/> | ||
| <field name="partner_id" string="Partner"/> | ||
| <field name="validity" string="Validity"/> | ||
| <field name="date_deadline"/> | ||
| <button name="action_accept" type="object" icon="fa-check" title="accept"/> | ||
| <button name="action_refuse" type="object" icon="fa-times" title="refuse"/> | ||
| <field name="status" string="Status"/> | ||
| </list> | ||
| </field> | ||
| </record> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <record id="estate_model_tag_action" model="ir.actions.act_window"> | ||
| <field name="name">Estate Tags</field> | ||
| <field name="res_model">estate.property.tag</field> | ||
| <field name="view_mode">list,form</field> | ||
| </record> | ||
| <!-- From view of the estate Tags --> | ||
| <record id="estate_property_tags_form_view" model="ir.ui.view"> | ||
| <field name="name">estate.property.tag.form</field> | ||
| <field name="model">estate.property.tag</field> | ||
| <field name="arch" type="xml"> | ||
| <form string="name"> | ||
| <sheet> | ||
| <group> | ||
| <h1><field name="name" placeholder="e.g. Cozy"/></h1> | ||
| </group> | ||
| </sheet> | ||
| </form> | ||
| </field> | ||
| </record> | ||
| </odoo> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| <?xml version="1.0"?> | ||
| <odoo> | ||
| <record id="estate_model_type_action" model="ir.actions.act_window"> | ||
| <field name="name">Estate Types</field> | ||
| <field name="res_model">estate.property.type</field> | ||
| <field name="view_mode">list,form</field> | ||
| </record> | ||
| <!-- Form view of the estate types --> | ||
| <record id="estate_property_types_form_view" 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 string="name"> | ||
| <sheet> | ||
| <group> | ||
| <h1><field name="name" placeholder="e.g. House Type"/></h1> | ||
| </group> | ||
| </sheet> | ||
| </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.
avoid pushing your personal config 😉