-
Notifications
You must be signed in to change notification settings - Fork 2.7k
WIMAR - Onboarding #1005
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?
WIMAR - Onboarding #1005
Conversation
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.
First set of comments :) Overall, it's fine!
Here are a few general comments:
- Try to be consistent with the use of single quotes
'and". If you start using one of them, be consistent throughout :) - Be careful with commit conventions (you can check the documentation here: Documentation )
For example [ADD] Chapter 1: Init and manifest => [ADD] estate: app creation
[ADD] Chapter 3: Models and Basic Fields => [IMP] estate: add models & basic fields etc.
Create empty __init__.py and __manifest__.py with module name, set as application.
Created the Estate Property model and added it to the __init__.py file. Defined the model’s attributes and fields.
Created access rights granting full permissions to base.group_user and added them to the __manifest__.py file.
Created the first action and menu, and added them to the __manifest__.py file. Linked the action to the menu.
- Added custom views: List, Form, and Search - Fixed empty line at EOF to comply with PEP8
8e27485 to
bd3010f
Compare
bd3010f to
db9ded5
Compare
- Added property types such as House, Apartment, etc., with corresponding Menus, Actions, and Views - Added property tags such as Renovated, etc., with corresponding Menus, Actions, and Views - Added an Offers tab on properties to display all offers related to a specific property
- Added computed, inverse, and onchange fields to improve user convenience and automation: total_area, best_price, validity, date_deadline, and garden
- Added buttons to sell or cancel a property, and to accept or refuse an offer
- Added SQL constraints to ensure positive prices and unique names - Added Python constraints to enforce offers at least 90% of the expected price
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.
New batch of comments but looking good so far :)
estate/models/estate_property.py
Outdated
| for record in self: | ||
| if record.state == "cancelled": | ||
| raise UserError("Cancelled properties cannot be sold.") | ||
| record.state = "sold" | ||
| return True |
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.
It's better to make the error message translatable :)
raise UserError(_("Cancelled properties cannot be sold."))You could also do something like this
| for record in self: | |
| if record.state == "cancelled": | |
| raise UserError("Cancelled properties cannot be sold.") | |
| record.state = "sold" | |
| return True | |
| if self.filtered(lambda x: x.state == "cancelled"): | |
| raise UserError(_("Cancelled properties cannot be sold.")) | |
| self.state = "sold" | |
| return True |
| selection=[("accepted", "Accepted"), ("refused", "Refused")], | ||
| copy=False, | ||
| ) | ||
| partner_id = fields.Many2one("res.partner", string="Buyer", required=True) |
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.
linter cries about this space 😅
| partner_id = fields.Many2one("res.partner", string="Buyer", required=True) | |
| partner_id = fields.Many2one("res.partner", string="Buyer", required=True) |
estate/security/ir.model.access.csv
Outdated
| access_estate_property_user,access_estate_property_user,model_estate_property,base.group_user,1,1,1,1 | ||
| access_estate_property_type_user,access_estate_property_type_user,model_estate_property_type,base.group_user,1,1,1,1 | ||
| access_estate_property_tag_user,access_estate_property_tag_user,model_estate_property_tag,base.group_user,1,1,1,1 | ||
| access_estate_property_offer_user,access_estate_property_offer_user,model_estate_property_offer,base.group_user,1,1,1,1 No newline at end of file |
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.
missing line break 😄
- Automatic and manual ordering - Decorations and invisible attributes - Stat button with action - Domain filters
- Override create method with ondelete constraint - Inherit res.users to display properties on user form
e876115 to
d87db09
Compare
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.
new batch of comments 😄
- this commit
[IMP] estate: Chapter 13 - Other modulescould/should be sth like[ADD] estate_account: Chapter 13 - Other modules
estate/models/estate_property.py
Outdated
| _positive_expected_price = models.Constraint( | ||
| "CHECK(expected_price > 0)", | ||
| "The expected price must be strictly positive.", | ||
| ) |
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.
just a small comment like that but nothing mandatory: I feel like it's more readable to have the constraints just under the fields, so they are not "hidden" between all those functions :)
but it's just a personal preference
| @api.model | ||
| def create(self, vals_list): | ||
| for vals in vals_list: | ||
| if vals["price"] <= self.env["estate.property"].browse(vals["property_id"]).best_price: |
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.
it's better to make the orm method outside of the loop.
For example, you could retrieve every "estate.property" outside of the loop and then filtered them in your loop
| for vals in vals_list: | ||
| if vals["price"] <= self.env["estate.property"].browse(vals["property_id"]).best_price: | ||
| raise UserError(_("The offer price must be strictly greater than the current best offer.")) | ||
| self.env["estate.property"].browse(vals["property_id"]).state = "received" |
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.
same with this line, if you use an intermediate variable with your filtered property
| <field name="living_area" filter_domain="[('living_area', '>', self)]"/> | ||
| <field name="facades"/> | ||
| <separator/> | ||
| <filter string="Available" name="state" domain="['|',('state', '=', 'new'),('state', '=', 'received')]"/> |
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.
it could be
| <filter string="Available" name="state" domain="['|',('state', '=', 'new'),('state', '=', 'received')]"/> | |
| <filter string="Available" name="state" domain="[('state', 'in', ['new', 'received'])]"/> |
- Create module to generate invoices in Invoicing
9ba22fe to
b48fa58
Compare
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.
new batch of comments, wp :p
|
|
||
| <t t-name="awesome_owl.counter"> | ||
| <div> | ||
| Counter: <t t-esc="state.value"/> |
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.
| Counter: <t t-esc="state.value"/> | |
| Counter: <t t-out="state.value"/> |
| if (this.props.onChange) { | ||
| this.props.onChange(); | ||
| } |
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.
just for your information, it's maybe less readable in this case but in JS you can do something like this
| if (this.props.onChange) { | |
| this.props.onChange(); | |
| } | |
| this.props.onChange?.(); |
| .chapter_tuto { | ||
| border: solid; padding: 10px; border-radius: 5px; margin-bottom: 5px | ||
| } |
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.
just for lisibility :)
| .chapter_tuto { | |
| border: solid; padding: 10px; border-radius: 5px; margin-bottom: 5px | |
| } | |
| .chapter_tuto { | |
| border: solid; | |
| padding: 10px; | |
| border-radius: 5px; | |
| margin-bottom: 5px | |
| } |
| <div class="chapter_tuto"> | ||
| <Counter onChange.bind="incrementSum" t-props="{'buttonText': 'Custom Incr.'}"/> | ||
| <Counter onChange.bind="incrementSum"/> | ||
| <p>The sum id: <t t-esc="state.value"/></p> |
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.
| <p>The sum id: <t t-esc="state.value"/></p> | |
| <p>The sum id: <t t-out="state.value"/></p> |
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.
Oups I missed this one
| <button class="btn btn-primary" t-on-click="increment" style="margin-left: 5px"> | ||
| <t t-out="props.buttonText"/> | ||
| </button> |
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 can "inline" the t-out like this
| <button class="btn btn-primary" t-on-click="increment" style="margin-left: 5px"> | |
| <t t-out="props.buttonText"/> | |
| </button> | |
| <button class="btn btn-primary" t-on-click="increment" style="margin-left: 5px" t-out="props.buttonText" /> |
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!
Lastest batch of comments
| } | ||
|
|
||
| getItemDescription(item) { | ||
| return item.description || "Dashboard item"; |
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.
| return item.description || "Dashboard item"; | |
| return item.description || _t("Dashboard item"); |
| t-on-change="() => this.toggleItem(item.backend_attribute)" | ||
| /> | ||
| <label class="form-check-label" t-att-for="'item_' + item.backend_attribute"> | ||
| <t t-esc="getItemDescription(item)"/> |
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.
| <t t-esc="getItemDescription(item)"/> | |
| <t t-out="getItemDescription(item)"/> |
| type: Number, | ||
| } | ||
| } | ||
| } No newline at end of file |
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.
Missing EOL
| class="form-check-input" | ||
| t-att-id="'item_' + item.backend_attribute" | ||
| t-att-checked="state.selectedItems[item.backend_attribute]" | ||
| t-on-change="() => this.toggleItem(item.backend_attribute)" |
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.
No need to change, but another pattern could have been used like.
Sometimes, it can be useful so you've seen this at least once :)
| t-on-change="() => this.toggleItem(item.backend_attribute)" | |
| t-att-data-backend-attribute="item.backend_attribute" | |
| t-on-change="toggleItem" |
And then update your JS function like this
toggleItem(event) {
const backendAttribute = event.currentTarget.getAttribute("backend-attribute")
this.state.selectedItems[backendAttribute] = !this.state.selectedItems[backendAttribute];
}| <templates xml:space="preserve"> | ||
|
|
||
| <t t-name="awesome_dashboard.NumberCard"> | ||
| <p class="text-center"><t t-out="props.title"/></p> |
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.
could be simplified like this
| <p class="text-center"><t t-out="props.title"/></p> | |
| <p class="text-center" t-out="props.title"/> |
| const visibleItems = {}; | ||
| Object.entries(this.allItems).forEach(([itemId, item]) => { | ||
| if (!this.state.hiddenItems.includes(item.backend_attribute)) { | ||
| visibleItems[itemId] = item; | ||
| } | ||
| }); | ||
| return visibleItems; |
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.
In general, when you want to "migrate" from an array to a single value (here a dict), .reduce(...) is the solution :)
(just for your information)
| const visibleItems = {}; | |
| Object.entries(this.allItems).forEach(([itemId, item]) => { | |
| if (!this.state.hiddenItems.includes(item.backend_attribute)) { | |
| visibleItems[itemId] = item; | |
| } | |
| }); | |
| return visibleItems; | |
| return Object.entries(this.allItems).reduce((acc, [itemId, item]) => { | |
| if (!this.state.hiddenItems.includes(item.backend_attribute)) { | |
| acc[itemId] = item; | |
| } | |
| return acc; | |
| }, {}); |
| const config = {}; | ||
| for (const item of Object.values(this.allItems)) { | ||
| config[item.backend_attribute] = !hiddenItems.includes(item.backend_attribute); | ||
| } |
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.
Same here for the reduce (but nothing mandatory)
| const config = {}; | |
| for (const item of Object.values(this.allItems)) { | |
| config[item.backend_attribute] = !hiddenItems.includes(item.backend_attribute); | |
| } | |
| const config = Object.values(this.allItems).reduce((acc, item) => { | |
| acc[item.backend_attribute] = !hiddenItems.includes(item.backend_attribute); | |
| return acc; | |
| }, {}); |
| margin: 5px; | ||
| padding: 10px; | ||
| color: black; | ||
| } No newline at end of file |
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.
missing eol
|
|
||
| <t t-name="awesome_dashboard.dashboarditem"> | ||
| <div class="item_card" t-att-style="`width: ${18 * props.size}rem;`"> | ||
| <t t-slot="default"></t> |
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.
could be autoclosing tag
| <t t-slot="default"></t> | |
| <t t-slot="default" /> |
| }, | ||
| }; | ||
|
|
||
| registry.category("services").add("awesome_dashboard.statistics", statisticsService); No newline at end of file |
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.
missing EOL 😄

No description provided.