18.0 server tutorial frva - #737
Conversation
There was a problem hiding this comment.
Hello :) good work !
I left some comments on the code.
The little red things at the end of your files in the github ui are probably because you're missing end of line characters at the end of the last line. Your editor can probably be configured so it doesn't do that.
| @@ -0,0 +1,3 @@ | |||
| from . import models | |||
|
|
|||
| from odoo import api, SUPERUSER_ID No newline at end of file | |||
There was a problem hiding this comment.
What are you trying to do with this line?
There was a problem hiding this comment.
I pasted it from somewhere I found and forgot to see what it does ... I erased it for the next commit since it doesn't look mandatory
| name = fields.Char('Estate Name',required=True, translate=True) | ||
| description = fields.Text('Description') | ||
| postcode = fields.Char('Postcode') | ||
| date_availability = fields.Date('Date Availability', copy=False, default=fields.Date.add(fields.Date.today(),months=3)) |
There was a problem hiding this comment.
Be careful for the default, if you do it this way fields.Date.today() will get evaled at the time where the class is evaled (so when odoo is started) instead of when we create a new record. default accepts lambda functions as well for this reason.
nitpick: for the label i think something like 'Date Available' or 'Availability Date' which is more natural-language-y would be better
| garden_area = fields.Integer('Garden Area') | ||
| garden_orientation = fields.Selection(string='Type', selection=[('north', 'North'), ('south', 'South'),('west', 'West'), ('east', 'East')]) | ||
| active = fields.Boolean(default=True) | ||
| state = fields.Selection(required=True, copy=False, default='New', selection=[('New', 'New'), ('Offer Received', 'Offer Received'),('Offer Accepted', 'Offer Accepted'), ('Sold', 'Sold'), ('Cancelled', 'Cancelled')]) No newline at end of file |
There was a problem hiding this comment.
nitpick: usually we like the technical names of a selection to feel more code-like, so lower_snake_case
| name = fields.Char('Title', required=True, translate=True) | ||
| description = fields.Text('Description') | ||
| postcode = fields.Char('Postcode') | ||
| date_availability = fields.Date('Date Available', copy=False, default=lambda self: self._get_day_in_3_months()) |
There was a problem hiding this comment.
Hello -- your answer to this #737 (comment) here is a bit redundant because you're both using a lambda and defining a fuction
You could either inline the functions return in the lambda or define the function above the fields and do default=_get_day_in_3_months.
By convention when we do the latter we usually name the function something like _default_<fieldname>
|
@vava-odoo I'll let you take over here :) |
| @@ -0,0 +1,3 @@ | |||
| from . import estate_property | |||
| from . import estate_property_type, estate_property_tag, estate_property_offer | |||
There was a problem hiding this comment.
Convention is one file imported at a time
| from odoo import fields, models | ||
| from odoo import api | ||
| from odoo import exceptions |
There was a problem hiding this comment.
But nor here 😇
Also, we usually import UserError and ValidationError direclty
| from odoo import fields, models | |
| from odoo import api | |
| from odoo import exceptions | |
| from odoo import api, fields, models | |
| from odoo.exceptions import UserError, ValidationError |
| garden_area = fields.Integer('Garden Area') | ||
| garden_orientation = fields.Selection(string='Orientation Type', selection=[('north', 'North'), ('south', 'South'), ('west', 'West'), ('east', 'East')]) | ||
| active = fields.Boolean(default=True) | ||
| state = fields.Selection(required=True, copy=False, default='new', selection=[('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled')]) |
There was a problem hiding this comment.
A bit long... Suggestion:
| state = fields.Selection(required=True, copy=False, default='new', selection=[('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled')]) | |
| state = fields.Selection( | |
| required=True, copy=False, default='new', | |
| selection=[ | |
| ('new', 'New'), | |
| ('offer_received', 'Offer Received'), | |
| ('offer_accepted', 'Offer Accepted'), | |
| ('sold', 'Sold'), | |
| ('cancelled', 'Cancelled'), | |
| ], | |
| ) |
| 'The selling price must be positive.') | ||
| ] | ||
|
|
||
| @api.onchange("selling_price", "expected_price") |
There was a problem hiding this comment.
Why this onchange? You only want to constrain, don't you?
There was a problem hiding this comment.
I thought it was necessary to be executed at each time the selling or expected price changes but I guess it's not then
|
|
||
| @api.depends("living_area", "garden_area") | ||
| def _compute_total_area(self): | ||
| self.total_area = self.living_area + self.garden_area |
There was a problem hiding this comment.
What if you edit several records at once?
| invoice_dictionary = { | ||
| "partner_id": record.buyer_id.id, | ||
| "move_type": "out_invoice", | ||
| "journal_id": self.env['account.journal'].search([("code", "=", "INV")]).id, |
There was a problem hiding this comment.
Why do you need a journal? What happen if there is none?
You could also add limit=1 to improve the perf
| ] | ||
| } | ||
| self.env["account.move"].create(invoice_dictionary) | ||
| super().action_sell_property() |
There was a problem hiding this comment.
You need to return super(), outside of the loop
Also, it would be interesting to call super first, to trigger the validation error if there is one before creating the invoice. It would be something like
| super().action_sell_property() | |
| res = super().action_sell_property() | |
| for record in self: | |
| ... | |
| return res |
| }) | ||
| ] | ||
| } | ||
| self.env["account.move"].create(invoice_dictionary) |
There was a problem hiding this comment.
It would be best to batch create with a list of invoice values, outside of the loop on self.
| 'estate', | ||
| 'account' | ||
| ], | ||
| 'data': [], |
There was a problem hiding this comment.
Empty, not in manifest... Can be removed 🙂
f9b9613 to
0475bd5
Compare
265d728 to
dda9737
Compare
| for val in invoice_values: | ||
| self.env["account.move"].create(val) |
There was a problem hiding this comment.
The purpose of my previous comment was to be able to create multiple invoices at once
| for val in invoice_values: | |
| self.env["account.move"].create(val) | |
| self.env["account.move"].create(invoice_values) |
| static template = "Card"; | ||
| static props = { | ||
| title: String, | ||
| slots: Object |
There was a problem hiding this comment.
| slots: Object | |
| slots: Object, |
Also: could you make the slots optional so that something like below doesn't give an error?
<Card title='...'/> | }; | ||
|
|
||
| setup(){ | ||
| this.state = useState( {open: true}); |
There was a problem hiding this comment.
nitpick: inconsistent spacing
| this.state = useState( {open: true}); | |
| this.state = useState({ open: true }); |
| if (this.props.onChange){ | ||
| this.props.onChange() | ||
| } |
There was a problem hiding this comment.
tip: js syntactic sugar 🌈 (subjective -- it's ok to not like/use it)
| if (this.props.onChange){ | |
| this.props.onChange() | |
| } | |
| this.props.onChange?.(); |
| increment(){ | ||
| this.state.value++; | ||
| if (this.props.onChange){ | ||
| this.props.onChange() |
There was a problem hiding this comment.
nitpick: inconsistent ; usage (do either always or never)
| this.props.onChange() | |
| this.props.onChange(); |
| <Counter onChange.bind="incrementSum"/> | ||
| </Card> | ||
| <Card title="'boring title'"> | ||
| Some text egjpojges |
| if (todoIdInArray >= 0) { | ||
| this.todos.splice(todoIdInArray, 1); | ||
| } |
There was a problem hiding this comment.
indentation
| if (todoIdInArray >= 0) { | |
| this.todos.splice(todoIdInArray, 1); | |
| } | |
| if (todoIdInArray >= 0) { | |
| this.todos.splice(todoIdInArray, 1); | |
| } |
| export function UseAutofocus(refString){ | ||
| var myRef = useRef(refString); | ||
| onMounted(() => { | ||
| console.log(myRef.el) |
| } | ||
|
|
||
| removeTodo(todoId){ | ||
| var todoIdInArray = this.todos.findIndex((todo) => todo.id == todoId); |
There was a problem hiding this comment.
| var todoIdInArray = this.todos.findIndex((todo) => todo.id == todoId); | |
| const todoIdInArray = this.todos.findIndex((todo) => todo.id == todoId); |
never use var; prefer const over let when possible
|
|
||
|
|
||
| export function UseAutofocus(refString){ | ||
| var myRef = useRef(refString); |
There was a problem hiding this comment.
| var myRef = useRef(refString); | |
| const myRef = useRef(refString); |
also myRef could use a better name
| type: 'ir.actions.act_window', | ||
| name: 'All leads', | ||
| res_model: 'crm.lead', | ||
| views: [[false, 'list'],[false, 'form']], |
There was a problem hiding this comment.
| views: [[false, 'list'],[false, 'form']], | |
| views: [[false, 'list'], [false, 'form']], |
or evn
| views: [[false, 'list'],[false, 'form']], | |
| views: [ | |
| [false, 'list'], | |
| [false, 'form'], | |
| ], |
vava-odoo
left a comment
There was a problem hiding this comment.
Data module looks great until now 👍
| @@ -0,0 +1,4 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <odoo> | |||
|
|
|||
| @@ -0,0 +1 @@ | |||
| from . import models | |||
There was a problem hiding this comment.
Nothing in the init
| from . import models |
| <field name="sequence">1</field> | ||
| </record> | ||
|
|
||
| <record id="selection_real_estate_property_garden_orientation" model="ir.model.fields.selection"> |
There was a problem hiding this comment.
same ID as previous one, will edit it instead of creating a new one...
| <record id="selection_real_estate_property_garden_orientation" model="ir.model.fields.selection"> | |
| <record id="selection_real_estate_property_garden_orientation_south" model="ir.model.fields.selection"> |
| <group> | ||
| <field name="x_offer_ids"/> | ||
| </group> |
| property['x_total_area'] = property.x_living_area + property.x_garden_area | ||
| ]]> | ||
| </field> | ||
| <field name="readonly">True</field> |
| <field name="on_delete">restrict</field> | ||
| </record> | ||
|
|
||
| <record id="action_real_estate_property_offer_accept_offer" model="ir.actions.server"> |
There was a problem hiding this comment.
Instead/on addition to a button, it could make sense to bind this as well, so that it is available from the gear
| <field name="state">code</field> | ||
| <field name="code"><![CDATA[ | ||
| for offer in records: | ||
| for property_offer in offer['x_property_id']['x_offer_ids']: |
There was a problem hiding this comment.
The Python ORM way of writing works for this reading case (I personally find it easier to read)
| for property_offer in offer['x_property_id']['x_offer_ids']: | |
| for property_offer in offer.x_property_id.x_offer_ids: |
| for property_offer in offer['x_property_id']['x_offer_ids']: | ||
| property_offer['x_status'] = 'refused' | ||
| offer['x_status'] = 'accepted' | ||
| offer['x_property_id']['x_partner_id'] = offer['x_partner_id'] |
| 'name': 'frva-estate-account', | ||
| 'license': 'LGPL-3', | ||
| 'depends': [ | ||
| 'estate', |
There was a problem hiding this comment.
🤷
| 'estate', | |
| 'estate_classic', |
|
|
||
| <record id="real_estate_property_public_users_write_record_rule" model="ir.rule"> | ||
| <field name="name">Public users cannot modify a real estate property</field> | ||
| <field ref="estate.model_real_estate_property" name="model_id"/> |
There was a problem hiding this comment.
Had to read it twice 😅
| <field ref="estate.model_real_estate_property" name="model_id"/> | |
| <field name="model_id" ref="estate.model_real_estate_property"/> |
| <field name="domain_force">[('id', '=', False)]</field> | ||
| <field name="groups" eval="[(4, ref('base.group_public'))]"/> | ||
| <field name="perm_write" eval="False"/> | ||
| <field name="active" eval="False"/> |
| self.property.state = 'sold' | ||
| with self.assertRaises(UserError): | ||
| self.offer = self.env['estate_classic.property.offer'].create({ | ||
| "property_id": self.property, |
There was a problem hiding this comment.
| "property_id": self.property, | |
| "property_id": self.property.id, |
| }), | ||
| }).id |
| }), | ||
| }).id |
vava-odoo
left a comment
There was a problem hiding this comment.
Only small comments about tests :-)
| }), | ||
| }).id | ||
| self.property.offer_ids = [self.offer] | ||
| self.offer.action_accept_offer() |
| from odoo.tests import tagged | ||
|
|
||
|
|
||
| @tagged('at_install') |
There was a problem hiding this comment.
No need as default one
| @tagged('at_install') |
| class EstateOfferTestCase(TransactionCase): | ||
|
|
||
| @classmethod | ||
| def setUpClass(cls): | ||
| super().setUpClass() | ||
| cls.property = cls.env['estate_classic.property'].create({ | ||
| "name": "Cool House", | ||
| "state": 'new', | ||
| "expected_price": 100000, | ||
| }) |
There was a problem hiding this comment.
The setup is the same between both test classes, you can inherit then
| class EstateOfferTestCase(TransactionCase): | |
| @classmethod | |
| def setUpClass(cls): | |
| super().setUpClass() | |
| cls.property = cls.env['estate_classic.property'].create({ | |
| "name": "Cool House", | |
| "state": 'new', | |
| "expected_price": 100000, | |
| }) | |
| class EstateOfferTestCase(EstateTestCase): |
There was a problem hiding this comment.
Is there no problem like duplicate function when we do that ? Because the parent's function will also be inherited no ?
| "partner_id": self.env['res.partner'].create({ | ||
| 'name': 'partner_a', | ||
| }).id, |
There was a problem hiding this comment.
Could be worth to create the partner in the setup, since you use 3 times.
| 'name': 'partner_a', | ||
| }).id, | ||
| }) | ||
| self.property.offer_ids = [self.offer.id] |
There was a problem hiding this comment.
Useless, since you already linked the property to the offer
| self.property.offer_ids = [self.offer.id] |

No description provided.