Skip to content

tutorial server framework joemo#1241

Open
djoewie wants to merge 2 commits intoodoo:19.0from
odoo-dev:19.0-tutorial-joemo
Open

tutorial server framework joemo#1241
djoewie wants to merge 2 commits intoodoo:19.0from
odoo-dev:19.0-tutorial-joemo

Conversation

@djoewie
Copy link
Copy Markdown

@djoewie djoewie commented Apr 21, 2026

No description provided.

@robodoo
Copy link
Copy Markdown

robodoo commented Apr 21, 2026

Pull request status dashboard

@djoewie djoewie requested a review from cgun-odoo April 21, 2026 14:22
Comment thread estate/models/__init__.py Outdated
@@ -0,0 +1 @@
from . import estate_property 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.

Don't forget to have an empty line at the end of every file. This will be automatic if you use a formatter like Ruff

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 can check which files are missing the line on the PR

Comment thread estate/security/ir.model.access.csv Outdated
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 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.

Suggested change
estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1
access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1

You don't need to start with the module name here as that will be done automatically.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Important thing to notice: You need to keep 'base' in base.group_user because it's a different module. But if you just write model_estate_property odoo knows to look for that in the estate module.

Comment thread estate/models/estate_property.py Outdated


class EstateProperty(models.Model):
_name = 'estate_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.

For technical names we separate words with .

Suggested change
_name = 'estate_property'
_name = 'estate.property'

https://www.odoo.com/documentation/18.0/contributing/development/coding_guidelines.html#symbols-and-conventions

Comment thread estate/views/estate_menus.xml Outdated
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
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 to have this line for xml files

Suggested change
<?xml version="1.0" encoding="utf-8" ?>

Comment thread estate/models/estate_property.py Outdated
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection(
selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]
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
selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]
[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]

The first positional argument is already 'selection' so it's redundant to have it.

@djoewie djoewie force-pushed the 19.0-tutorial-joemo branch from e291eec to a2c494c Compare April 22, 2026 13:35
Copy link
Copy Markdown

@cgun-odoo cgun-odoo left a comment

Choose a reason for hiding this comment

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

Good job 👍 You can check the runbot, you're getting a warning

Image

Comment thread estate/models/estate_property.py Outdated
_description = 'Estate Property'

name = fields.Char(required=True)
name = fields.Char('Title', required=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.

For strings, as far as I know in R&D they put user facing strings in double quotes and technical strings in single quotes. In PS-Tech we put everything in double quotes.
Again something that will easily be fixed with a formatter configuration

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.

Do you have a formatter configuration I can start from, because now I use the formatter of pycharm by default.
I forgot to enable it for the first couple of commits.

Comment thread estate/models/estate_property.py Outdated
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date()
date_availability = fields.Date('Available From', default=fields.Date.today() + relativedelta(months=3), copy=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.

Here you're passing a value as default. This value will be calculated once when you run the server.
However, what you want to do is to pass a function, so that the value is calculated every time the default value is needed.

Comment thread estate/models/estate_property.py Outdated
[('new', 'New'), ('offer', 'Offer Received'), ('accepted', 'Offer Accepted'), ('sold', 'Sold'), ('canceled', 'Cancelled')],
default='new'
)
property_type_id = fields.Many2one('estate.property.type', 'Property Type')
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 to add the string here, the default string for property_type_id is already Property Type

Comment thread estate/models/estate_property.py Outdated
default='new'
)
property_type_id = fields.Many2one('estate.property.type', 'Property Type')
sales_person_id = fields.Many2one('res.users', "Salesman", copy=False, default=lambda self: self.env.user)
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
sales_person_id = fields.Many2one('res.users', "Salesman", copy=False, default=lambda self: self.env.user)
salesperson_id = fields.Many2one('res.users', copy=False, default=lambda self: self.env.user)

salesperson is one word i guess, not super sure

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.

indeed this is one word, is there a way to not loose data when renaming a field?

Copy link
Copy Markdown

@cgun-odoo cgun-odoo Apr 23, 2026

Choose a reason for hiding this comment

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

Comment thread estate/models/estate_property.py Outdated
)
property_type_id = fields.Many2one('estate.property.type', 'Property Type')
sales_person_id = fields.Many2one('res.users', "Salesman", copy=False, default=lambda self: self.env.user)
buyer_id = fields.Many2one('res.partner', 'Buyer', copy=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.

Suggested change
buyer_id = fields.Many2one('res.partner', 'Buyer', copy=False)
buyer_id = fields.Many2one('res.partner, copy=False)

Comment thread estate/views/estate_property_views.xml Outdated
<odoo>

<record id="estate_property_view_search_name" model="ir.ui.view">
<field name="name">estate.property.list</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.

Suggested change
<field name="name">estate.property.list</field>
<field name="name">estate.property.view.search</field>

Comment thread estate/views/estate_property_views.xml Outdated
<field name="living_area"/>
<field name="facades"/>
<field name="property_type_id"/>
<filter name="available" domain="['|',('state','=','new'),('state','=','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.

Suggested change
<filter name="available" domain="['|',('state','=','new'),('state','=','offer')]"/>
<filter name="available" domain="[('state', 'in', ['new', 'offer'])]"/>

Comment thread estate/views/estate_property_views.xml Outdated
</record>

<record id="estate_property_view_form" model="ir.ui.view">
<field name="name">estate.property.list</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.

Suggested change
<field name="name">estate.property.list</field>
<field name="name">estate.property.view.form</field>

Comment thread estate/__manifest__.py Outdated
"views/estate_property_views.xml",
"views/estate_property_offer_views.xml",
"views/estate_menus.xml",
'security/ir.model.access.csv',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We usually put the security files on the top, since what they define can be used in the views but not the opposite

Comment thread estate/models/estate_property_tag.py Outdated

class EstatePropertyTag(models.Model):
_name = 'estate.property.tag'
_description = 'Tags for properties'
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
_description = 'Tags for properties'
_description = 'Estate Property Tag'

Copy link
Copy Markdown

@cgun-odoo cgun-odoo left a comment

Choose a reason for hiding this comment

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

Added some comments to align more with the conventions

Comment on lines +49 to +50
_check_expected_price = models.Constraint("CHECK(expected_price>0)", "The expected price needs to be bigger then 0")
_check_selling_price = models.Constraint("CHECK(selling_price>=0)", "The selling price needs to be positive")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

sql Constraints should be above fields with other private properties.
Refer to this to check the order in a model:
https://www.odoo.com/documentation/18.0/contributing/development/coding_guidelines.html#symbols-and-conventions

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 was following the 19 guidelines where it was indicating that this is below the field declaration
https://www.odoo.com/documentation/19.0/contributing/development/coding_guidelines.html#symbols-and-conventions

Comment thread estate/models/estate_property.py Outdated
def action_state_to_canceled(self):
self.ensure_one()
if self.state == "sold":
raise UserError("Canceled properties can not be sold")
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 Error messages for action_state_to_sold and action_state_to_canceled felt like the opposite of what it should be

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.

you are absolutely right

Comment thread estate/models/estate_property.py Outdated
not float_is_zero(estate_property.selling_price, 2)
and float_compare(estate_property.selling_price, estate_property.expected_price * 0.9, 2) < 0
):
raise UserError("The property can not be sold for a price lower than 90% of the 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.

This one should be a ValidationError as it's thrown from an api.constrains
Check this for exceptions in Odoo
https://www.odoo.com/documentation/18.0/developer/reference/backend/orm.html?highlight=usererror#odoo.exceptions.UserError

Comment thread estate/models/estate_property.py Outdated
def accept_offer(self, price, buyer):
self.ensure_one()
if self.state not in ["new", "offer"]:
raise UserError("The offer can not be accepted")
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 can explain more like with the message like you did for the other ones.
Why can't they accept it?

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.

would you go for 1 generic message, or for individual messages based on the different states?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I would go for multiple messages to be more explanatory. If there are too many different states that require too many messages, I would think about creating a helper function that generates an error message.

Comment thread estate/models/estate_property_offer.py
Comment on lines -12 to -24
<record id="estate_property_tag_view_form" model="ir.ui.view">
<field name="name">estate.property.tag.list</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<form string="Tag">
<sheet>
<group>
<field name="name"/>
</group>
</sheet>
</form>
</field>
</record>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If you don't want them to access the form view you should remove it from the action as well

</page>
<page string="Offers">
<field name="offer_ids"/>
<field name="offer_ids" readonly="state in ['accepted','sold','canceled']"/>
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 can define the list view of offers here so that you don't need to have a file for it (i guess you don't access that view anywhere else)

Kind of like

<field name="one2many_field">
      <list>
           <field/>
           <field/>
      </list>
 </field>

self.ensure_one()
if self.state not in ["new", "offer"]:
raise UserError(
"An offer can can only be accepted when the building is still for sale and no other offer is already accepted"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"An offer can only be made" probably

"An offer can can only be accepted when the building is still for sale and no other offer is already accepted"
)
if float_compare(price, self.best_offer, 2) <= 0:
raise UserError("Only offers that are over the current best offer can be accepted")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shouldn't this check be in accept_offer?

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.

question was to only allow offers that are higher than previous made offers

Comment thread estate/models/estate_property_offer.py Outdated

@api.model
def create(self, vals_list):
for offer in vals_list:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Here we usually do
for vals in vals_list:
As they aren't offers yet

Comment thread estate/models/estate_property_type.py Outdated
@api.depends("offer_ids")
def _compute_offer_count(self):
for offer in self:
offer.offer_count = len(offer.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.

This should match the model
for property_type in self:

Comment thread estate/views/res_user_views.xml Outdated
@@ -0,0 +1,15 @@
<odoo>
<!-- Update User form !-->
<record id="res_users_form_view_properties" model="ir.ui.view">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

https://www.odoo.com/documentation/19.0/contributing/development/coding_guidelines.html#inheriting-xml

ID of the inherited record should stay the same:

<record id="view_users_form" model="ir.ui.view">

I know that some people in the department actually don't like this and they try to give more explanatory id and names to view records.
IMO, naming them the same helps check where the record has been updated easily so I follow the convention.

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.

makes sense, did not get that from the tutorial

Comment thread estate/views/res_user_views.xml Outdated
<odoo>
<!-- Update User form !-->
<record id="res_users_form_view_properties" model="ir.ui.view">
<field name="name">res.users.form.properties</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.

According to the same guideline this should be named:
res.users.view.form.inherit.estate

Comment thread estate/models/estate_property_offer.py Outdated
property_id = fields.Many2one("estate.property", required=True)
validity = fields.Integer("Validity (days)", default=7)
date_deadline = fields.Date("Deadline", compute="_calculate_date_deadline", inverse="_inverse_date_deadline")
property_type_id = fields.Many2one(related="property_id.property_type_id", stored=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.

Why did you need to make this field stored?

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.

done to make it possible to have the inverse field in property_type as one2many, if not stored then these items could not be found from a property_type trough SQL queries

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There is one problem though: It should be store=True without the 'd'. Right now, the field is probably not stored.
It works because related fields are special, you can search on them even if they aren't stored.

@djoewie djoewie changed the title tutorial joemo tutorial server framework joemo Apr 24, 2026

def action_state_to_sold(self):
if not self.buyer_id:
raise ValidationError("Properties can only be sold if the buyer is filled in")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment thread estate/models/estate_property_offer.py Outdated
property_id = fields.Many2one("estate.property", required=True)
validity = fields.Integer("Validity (days)", default=7)
date_deadline = fields.Date("Deadline", compute="_calculate_date_deadline", inverse="_inverse_date_deadline")
property_type_id = fields.Many2one(related="property_id.property_type_id", stored=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.

There is one problem though: It should be store=True without the 'd'. Right now, the field is probably not stored.
It works because related fields are special, you can search on them even if they aren't stored.

Copy link
Copy Markdown

@cgun-odoo cgun-odoo left a comment

Choose a reason for hiding this comment

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

You can try to make the runbot green now

djoewie added 2 commits April 24, 2026 17:43
make it possible to handle all requests for this

[ADD] estate: create placeholder for Real Estate Advertisement

make basic setup

[IMP] estate: convert module in application

convert module in application

[IMP] estate: add property in database

make it possible to store properties in the database

[IMP] estate: add description to property database model

make it easier to understand what the estate property means

[IMP] estate: add fields in property model

make sure the basic information can be stored in the estate property

[FIX] estate: in property some fields are not optional

some fields should not be required

[IMP] estate: add access rights for properties

make sure that data is only accessible for the predefined usergroup

[IMP] estate: create view for properties

making it possible to have a view the model of property

[IMP] estate: make app visible in UI

make it possible to see the properties in the userinterface

[LINT] estate: fix end of file

keep code inline with the python standard

[FIX] estate: make UI labels in line with wireframe

change the labels so the labes in the UI stay inline with the wireframe

[CLN] estate: resolve problems with coding guidelines

improve consistency with rest of the codebase

[IMP] estate: make property more resilient during copy

make sure property specific information is not copied

[IMP] estate: default values for property

reducing work by filling in fields op properties with sensible default values

[IMP] estate: add option to archive a property

reducing work by filling in fields op properties with sensible default values

[FIX] estate: stop archiving new properties

when creating new properties they should not be archived immediately

[FIX] estate: add different states for property

making sure the current state of a property can be properly represented

[IMP] estate: add listview for property

making it possible to view more information in the list view

[IMP] estate: add formview for property

make the detail view of a property more appealing

[IMP] estate: add search options

improve the possible options for searching

[IMP] estate: default filter and grouping for property

improve the options for searching properties by having defaults

[IMP] estate: add type of properties

make it possible to distinguish between multiple types of properties

[IMP] estate: add buyer and salesperson on property

keep track of who bought the property and the salesperson

[IMP] estate: add tags for properties

adding an option to some custom tag to buildings so you don't need to search for important information about the building

[FIX] estate: wrong naming in records of tag and type

make naming consistent

[IMP] estate: add option to put offers in

allow the system to store offers on a property, in preparation to be able to really sell

[IMP] estate: add total area of property

help the realtor, precalculate the total area

[IMP] estate: show best offer

help the realtor with showing the price of the best offer

[IMP] estate: add validity and deadline to offer

make sure an offer is not valid for an infinite amount of time

[IMP] estate: defaults for garden attributes based on availability

help users when garden is selected to fill in some details and remove them if no garden is there

[CLN] estate: use blank end of line

follow coding guidelines

[CLN] estate: double quotes for language specific texts

follow coding guidelines

[FIX] estate: make default value based on moment of invocation

default availability needs to be calculated when creating the property, not at startup

[IMP] estate: rename field

make field in line with naming standaard

[IMP] estate: state changes

make sure transitions are set to reduce users making impossible actions

[CLN] estate: add missing fields in module manifest

make sure the module is compliant with the testing tools

[LINT] estate: blank line at end of file

make sure the module is compliant with the coding style

[CLN] estate: make _description technical term

the _description is used in technical locations so keep it useful for that environment over an explanation

[CLN] estate: code guidelines fix naming compute and inverse

make sure to follow coding guidelines

[FIX] estate: make it possible to delete offers for admins

make sure offers can be deleted, but only if you know what you do

[CLN] estate: change naming to match coding style for views

follow coding rules

[REF] estate: improve readability of search filter

improve readability

[REF] estate: move the security file to load before the views

the content of the security can be used in the views but not the opposite

[LINT] estate: use linter to fix codestyle

follow style defined by team

[FIX] estate: security duplicate entry

make sure the id's are unique for security rules

[IMP] estate: add database constraints on prices, tags and types

make sure no invalid data or duplicate can be entered in the database

[IMP] estate: add code constraint on selling price

make sure the property can not be sold if it is not at least 90% of the expected price

[IMP] estate: view property on type page

make it possible to view a minimum of property information that is linked to one type of property

[IMP] estate: add status bar on property details

make the status visually appealing

[IMP] estate: add default ordering

making sure there is a logical default ordering for each model

[IMP] estate: add manual sorting for properties

make sure that it is possible to manually sort the properties

[IMP] estate: add widget options

making sure property tags can be easily distinguished from each-other based on color
avoid accidentally creating new property types when creating or editing properties

[IMP] estate: hide action buttons if not possible to use

help user by not showing buttons they can not use op properties

[IMP] estate: improve user workflow

help user by disabling or hiding buttons or items they do not need

[IMP] estate: list views editable

there is no need for detailed form_view if the data is limited and can easily be changed in the listview

[IMP] estate: hide field

improve usability by making fields optional if they are not adding value all the time

[IMP] estate: use decoration to indicate state

make the state visually appealing so less space is wasted on the screen and have a quick overview

[IMP] estate: improve property search and listview

make sure search has some sensible defaults and make sure searching is not exact but at least
add some missing information to the listview

[IMP] estate: add offers to be shown by type

when looking at a type of building see the amount of offers and also see the detailed list of the offers

[IMP] estate: improve error handling and fix error message

making sure to use the correct type of errors
make output to the user more consistent

[IMP] estate: add validation on CRUD operations

limit creation offer to useful situations
do not allow delete of properties that are currently active

[IMP] estate: add properties to user page

help the user to access properties by adding them to there userpage

[IMP] estate: add kanban view for properties

have a secondary way to show the different properties, imporve readablility and make sure values are stored correctly

[IMP] estate: add kanban view for properties

have a secondary way to show the different properties

[IMP] estate: follow up on coding advice

improve readability of code

[IMP] estate: runbot message fix

make sure value is stored
make life easy for the accounting by creating an invoice directly from the property when sold

[IMP] estate_account: use the correct error

the error that is needed is a usererror not validation error

[IMP] estate_account: change type of error

errors should be of correct type
@djoewie djoewie force-pushed the 19.0-tutorial-joemo branch from 4cc9d4c to ac225f6 Compare April 24, 2026 15:45
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.

3 participants