Skip to content

18.0 server tutorial frva - #737

Open
frva-odoo wants to merge 35 commits into
odoo:18.0from
odoo-dev:18.0-server-tutorial-frva
Open

18.0 server tutorial frva#737
frva-odoo wants to merge 35 commits into
odoo:18.0from
odoo-dev:18.0-server-tutorial-frva

Conversation

@frva-odoo

Copy link
Copy Markdown

No description provided.

@robodoo

robodoo commented Apr 23, 2025

Copy link
Copy Markdown

Pull request status dashboard

@naja628 naja628 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread estate/__init__.py Outdated
@@ -0,0 +1,3 @@
from . import models

from odoo import api, SUPERUSER_ID No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are you trying to do with this line?

@frva-odoo frva-odoo Apr 23, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread estate/models/estate_property.py Outdated
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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

descriptive names please

Comment thread estate/models/estate_property.py Outdated
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: usually we like the technical names of a selection to feel more code-like, so lower_snake_case

Comment thread estate/models/estate_property.py Outdated
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())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@naja628

naja628 commented Apr 24, 2025

Copy link
Copy Markdown

@vava-odoo I'll let you take over here :)

@vava-odoo vava-odoo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job! Just a few ( 👀 ) comments. Don't hesitate to ask if you have questions!

Comment thread estate/models/__init__.py Outdated
@@ -0,0 +1,3 @@
from . import estate_property
from . import estate_property_type, estate_property_tag, estate_property_offer

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convention is one file imported at a time

Comment thread estate/models/estate_property.py Outdated
Comment on lines +1 to +3
from odoo import fields, models
from odoo import api
from odoo import exceptions

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But nor here 😇
Also, we usually import UserError and ValidationError direclty

Suggested change
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

Comment thread estate/models/estate_property.py Outdated
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')])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit long... Suggestion:

Suggested change
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'),
],
)

Comment thread estate/models/estate_property.py Outdated
'The selling price must be positive.')
]

@api.onchange("selling_price", "expected_price")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this onchange? You only want to constrain, don't you?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it was necessary to be executed at each time the selling or expected price changes but I guess it's not then

Comment thread estate/models/estate_property.py Outdated

@api.depends("living_area", "garden_area")
def _compute_total_area(self):
self.total_area = self.living_area + self.garden_area

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Suggested change
super().action_sell_property()
res = super().action_sell_property()
for record in self:
...
return res

})
]
}
self.env["account.move"].create(invoice_dictionary)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be best to batch create with a list of invoice values, outside of the loop on self.

Comment thread estate_account/__manifest__.py Outdated
'estate',
'account'
],
'data': [],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty, can be dropped

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty, not in manifest... Can be removed 🙂

@frva-odoo
frva-odoo force-pushed the 18.0-server-tutorial-frva branch from f9b9613 to 0475bd5 Compare April 28, 2025 11:47
@frva-odoo
frva-odoo force-pushed the 18.0-server-tutorial-frva branch from 265d728 to dda9737 Compare April 29, 2025 06:49
Comment on lines +28 to +29
for val in invoice_values:
self.env["account.move"].create(val)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of my previous comment was to be able to create multiple invoices at once

Suggested change
for val in invoice_values:
self.env["account.move"].create(val)
self.env["account.move"].create(invoice_values)

@naja628 naja628 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello :) -- Good work !
I left some comments on the js tutorial

Comment thread awesome_owl/static/src/card/card.js Outdated
static template = "Card";
static props = {
title: String,
slots: Object

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
slots: Object
slots: Object,

Also: could you make the slots optional so that something like below doesn't give an error?

<Card title='...'/> 

Comment thread awesome_owl/static/src/card/card.js Outdated
};

setup(){
this.state = useState( {open: true});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: inconsistent spacing

Suggested change
this.state = useState( {open: true});
this.state = useState({ open: true });

Comment on lines +15 to +17
if (this.props.onChange){
this.props.onChange()
}

@naja628 naja628 May 2, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tip: js syntactic sugar 🌈 (subjective -- it's ok to not like/use it)

Suggested change
if (this.props.onChange){
this.props.onChange()
}
this.props.onChange?.();

increment(){
this.state.value++;
if (this.props.onChange){
this.props.onChange()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: inconsistent ; usage (do either always or never)

Suggested change
this.props.onChange()
this.props.onChange();

<Counter onChange.bind="incrementSum"/>
</Card>
<Card title="'boring title'">
Some text egjpojges

@naja628 naja628 May 2, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

segjopjge :)

Comment on lines +30 to +32
if (todoIdInArray >= 0) {
this.todos.splice(todoIdInArray, 1);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation

Suggested change
if (todoIdInArray >= 0) {
this.todos.splice(todoIdInArray, 1);
}
if (todoIdInArray >= 0) {
this.todos.splice(todoIdInArray, 1);
}

Comment thread awesome_owl/static/src/utils.js Outdated
export function UseAutofocus(refString){
var myRef = useRef(refString);
onMounted(() => {
console.log(myRef.el)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left-over debug code

}

removeTodo(todoId){
var todoIdInArray = this.todos.findIndex((todo) => todo.id == todoId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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

Comment thread awesome_owl/static/src/utils.js Outdated


export function UseAutofocus(refString){
var myRef = useRef(refString);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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']],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
views: [[false, 'list'],[false, 'form']],
views: [[false, 'list'], [false, 'form']],

or evn

Suggested change
views: [[false, 'list'],[false, 'form']],
views: [
[false, 'list'],
[false, 'form'],
],

@vava-odoo vava-odoo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data module looks great until now 👍

Comment thread estate/security/estate_security.xml Outdated
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty?

Comment thread estate/__init__.py Outdated
@@ -0,0 +1 @@
from . import models

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing in the init

Suggested change
from . import models

<field name="sequence">1</field>
</record>

<record id="selection_real_estate_property_garden_orientation" model="ir.model.fields.selection">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same ID as previous one, will edit it instead of creating a new one...

Suggested change
<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">

@vava-odoo vava-odoo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good good 🙂

Comment thread estate/views/estate_property_views.xml Outdated
Comment on lines +58 to +60
<group>
<field name="x_offer_ids"/>
</group>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need a group here?

property['x_total_area'] = property.x_living_area + property.x_garden_area
]]>
</field>
<field name="readonly">True</field>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure we want to store that field

<field name="on_delete">restrict</field>
</record>

<record id="action_real_estate_property_offer_accept_offer" model="ir.actions.server">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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']:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Python ORM way of writing works for this reading case (I personally find it easier to read)

Suggested change
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']

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this way of writing

Comment thread estate_account/__manifest__.py Outdated
'name': 'frva-estate-account',
'license': 'LGPL-3',
'depends': [
'estate',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷

Suggested change
'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"/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to read it twice 😅

Suggested change
<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"/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not active?

self.property.state = 'sold'
with self.assertRaises(UserError):
self.offer = self.env['estate_classic.property.offer'].create({
"property_id": self.property,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"property_id": self.property,
"property_id": self.property.id,

Comment on lines +21 to +22
}),
}).id

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}),
}).id
}).id,
})

Comment on lines +27 to +28
}),
}).id

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}),
}).id
}).id,
})

@vava-odoo vava-odoo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only small comments about tests :-)

}),
}).id
self.property.offer_ids = [self.offer]
self.offer.action_accept_offer()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You accept an offer without price 😯

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add it !

from odoo.tests import tagged


@tagged('at_install')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need as default one

Suggested change
@tagged('at_install')

Comment on lines +7 to +16
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,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setup is the same between both test classes, you can inherit then

Suggested change
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):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no problem like duplicate function when we do that ? Because the parent's function will also be inherited no ?

Comment on lines +31 to +33
"partner_id": self.env['res.partner'].create({
'name': 'partner_a',
}).id,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless, since you already linked the property to the offer

Suggested change
self.property.offer_ids = [self.offer.id]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants