[ADD] estate: basic module creation#1245
Conversation
Created init and manifest files
Added fields in __manifest__.py Created models folder, EstateProperty model with the required fields and imported it in __manifest__.py files
Created data folder and file ir.model.access.csv inside it giving read, write, create and unlink permissions to the group base.group_user
Added views and actions for estate.property. Added a 3 level menu with the basic action Added and improved attributes
Mathilde411
left a comment
There was a problem hiding this comment.
Good job !
I was kinda nitpicky but this is a good job overall :)
- Added newlines to the relevant files - Changed license and version - Moved menus form data to views folder - Now follows formatting rules
Added list views to main menu Added form view when creating property Added search views, filters and group
0b858e1 to
70fa394
Compare
Added property types and its menu and views Added property tags and its menu and views Added property offers and its views Added necessary fields and links
Added total area (garden area + living area) Added best price Added validity date Added onchanges on garden fields
| name = fields.Char('Property Name', required=True) | ||
| description = fields.Text('Description') | ||
| postcode = fields.Char('Postcode') | ||
| date_availability = fields.Date('Availability Date', copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3)) | ||
| expected_price = fields.Float('Expected Price', required=True) | ||
| selling_price = fields.Float('Selling Price', readonly=True, copy=False) | ||
| bedrooms = fields.Integer('Bedrooms', default=2) | ||
| living_area = fields.Integer('Living Area') | ||
| facades = fields.Integer('Facades') | ||
| garage = fields.Boolean('Garage') | ||
| garden = fields.Boolean('Garden') |
There was a problem hiding this comment.
Strings are not needed in most of those. The default would work.
| @api.onchange('garden') | ||
| def _onchange_garden(self): | ||
| if not self.garden: | ||
| self.garden_area = 0 | ||
| self.garden_orientation = False | ||
| else: | ||
| self.garden_area = 10 | ||
| self.garden_orientation = 'north' | ||
|
|
||
| garden_area = fields.Integer('Garden Area') | ||
| garden_orientation = fields.Selection( | ||
| selection=[ | ||
| ('north', 'North'), | ||
| ('south', 'South'), | ||
| ('east', 'East'), | ||
| ('west', 'West'), | ||
| ], | ||
| ) | ||
| state = fields.Selection( | ||
| selection=[ | ||
| ('new', 'New'), | ||
| ('offer received', 'Offer Received'), | ||
| ('offer accepted', 'Offer Accepted'), | ||
| ('sold', 'Sold'), | ||
| ('cancelled', 'Cancelled'), | ||
| ], | ||
| default='new', | ||
| required=True, | ||
| ) | ||
| property_type_id = fields.Many2one('estate.property.type', string="Property Type") | ||
| buyer_id = fields.Many2one('res.partner', string="Buyer", copy=False) | ||
| salesperson_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.uid) | ||
| tag_ids = fields.Many2many('estate.property.tag', string="Property Tag") | ||
| offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offer") | ||
|
|
||
| @api.depends('living_area', 'garden_area') | ||
| def _compute_total_area(self): | ||
| for line in self: | ||
| line.total_area = line.living_area + line.garden_area | ||
| total_area = fields.Integer('Total Area', compute="_compute_total_area") | ||
|
|
||
| @api.depends('offer_ids.price') | ||
| def _compute_best_price(self): | ||
| for line in self: | ||
| line.best_price = max(line.mapped('offer_ids.price') or [0]) | ||
| best_price = fields.Float('Best Offer', compute="_compute_best_price") |
There was a problem hiding this comment.
Be careful about attribute ordering in a model. fields should be before the rest and every type of method should be appearing in a set order
| @api.depends('offer_ids.price') | ||
| def _compute_best_price(self): | ||
| for line in self: | ||
| line.best_price = max(line.mapped('offer_ids.price') or [0]) |
There was a problem hiding this comment.
| line.best_price = max(line.mapped('offer_ids.price') or [0]) | |
| line.best_price = max(line.mapped('offer_ids.price'), default=0) |
a little bit better :)
| partner_id = fields.Many2one("res.partner", string="Partner", required=True) | ||
| property_id = fields.Many2one("estate.property", string="Property", required=True) |
There was a problem hiding this comment.
On a many2one default string attribute is what comes before _id capitalized, so here it'd be the same, so you don't need to add it :)
Also works with many2many and one2many but with _ids instead of _id :D
| @api.depends('create_date', 'validity') | ||
| def _compute_date_deadline(self): | ||
| for offer in self: | ||
| if offer.validity: | ||
| if offer.create_date: | ||
| offer.date_deadline = offer.create_date + timedelta(days=offer.validity) | ||
| else: | ||
| offer.date_deadline = fields.Date.today() + timedelta(days=offer.validity) | ||
| else: | ||
| offer.date_deadline = False |
There was a problem hiding this comment.
Validity is an integer field, it always has a value
| @api.depends('create_date', 'validity') | |
| def _compute_date_deadline(self): | |
| for offer in self: | |
| if offer.validity: | |
| if offer.create_date: | |
| offer.date_deadline = offer.create_date + timedelta(days=offer.validity) | |
| else: | |
| offer.date_deadline = fields.Date.today() + timedelta(days=offer.validity) | |
| else: | |
| offer.date_deadline = False | |
| @api.depends('create_date', 'validity') | |
| def _compute_date_deadline(self): | |
| for offer in self: | |
| offer.date_deadline = (offer.create_date or fields.Date.today()) + timedelta(days=offer.validity) |
| def _inverse_date_deadline(self): | ||
| for offer in self: | ||
| if offer.date_deadline: | ||
| if offer.create_date: | ||
| offer.validity = (offer.date_deadline - offer.create_date).days | ||
| else: | ||
| offer.validity = (offer.date_deadline - fields.Date.today()).days | ||
| else: | ||
| offer.validity = 0 |
| else: | ||
| offer.validity = 0 | ||
|
|
||
| date_deadline = fields.Date("Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline", store=True) |
| <field name="price" width="200px"/> | ||
| <field name="partner_id" width="240px" /> | ||
| <field name="validity" width="240px" /> |
There was a problem hiding this comment.
Why the widths here ? Would this work correctly on all screen sizes or just yours ?
Added action to cancel or set a property as sold Added action to accept or refuse offer and set selling price and buyer
- Implemented required changes from PR - Fixed bug with deadline and validity of offers
- Added SQL constraints on expected price, offer price, tags and types - Added python constraint on selling price
This commit is here to introduce the testing framework of Odoo. Try running the tests using `--test-tags :TestEstateProperty`. Doc: https://www.odoo.com/documentation/18.0/developer/reference/backend/testing.html?highlight=tests#invocation The tests were made such that the first one should work but the second one should fail. Your job is to ensure both tests pass in the end. You should update the behaviour of the appropriate models. If you want, you can also add a small test of your own to get a feel for it.

Created init and manifest files