From 5e0797da71cfa86b87c491582438934efab0f65a Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Thu, 2 Apr 2026 10:07:29 +0530 Subject: [PATCH 01/21] [ADD] estate: initialize module structure (Chapter 2) - Create the initial __manifest__.py file to define the module's metadata and make it discoverable by Odoo. - Add the __init__.py file to mark the directory as a Python package and prepare for importing future models and other components. - Establish the basic structure required for the new 'estate' module to be loaded and function within the Odoo framework. --- estate/__init__.py | 1 + estate/__manifest__.py | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..3d23f15b753 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +__name__ \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..1f350b3bebd --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,7 @@ +{ + 'name': 'Estate', + 'depends': [ 'base' ], + 'application': True, + 'installable': True, + 'version': '1.0', +} From c222c922d220fd73bea17b1ca0cbaeafa8857579 Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Thu, 2 Apr 2026 10:09:37 +0530 Subject: [PATCH 02/21] [FIX] estate: Fixed the commit message for estate module (Chapter 2) - Updated the commit message to accurately reflect the changes made in the estate module for Chapter 2. --- estate/git | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 estate/git diff --git a/estate/git b/estate/git new file mode 100644 index 00000000000..e69de29bb2d From f2e79134b58e34e16bc33cc6c36349ba062940e4 Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Thu, 2 Apr 2026 10:16:08 +0530 Subject: [PATCH 03/21] [FIX] estate: Fix the space issue in the __init__.py file - fix the space issue in the __init__.py file of the estate module to ensure proper formatting and readability. --- estate/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/estate/__init__.py b/estate/__init__.py index 3d23f15b753..e69de29bb2d 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +0,0 @@ -__name__ \ No newline at end of file From b6455520e570e759e0600bd5617587d4e82b07f2 Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Mon, 6 Apr 2026 09:58:14 +0530 Subject: [PATCH 04/21] [ADD] estate: implement property module (Chapter 4) - Created estate.property model - Added module structure and manifest configuration - Implemented fields and basic validations - Fixed import and dependency issues - Resolved runtime errors during module loading [fix] estate: fix issue of external id and access rights [fix] estate: fix issue of access rights for estate.property model [fix] estate: fix issue of installation of estate module due to missing access rights --- estate/__init__.py | 1 + estate/__manifest__.py | 14 ++++++--- estate/models/__init__.py | 1 + estate/models/estate_property.py | 45 +++++++++++++++++++++++++++++ estate/security/ir.model.access.csv | 4 +++ estate/security/ir.txt | 16 ++++++++++ 6 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py create mode 100644 estate/security/ir.model.access.csv create mode 100644 estate/security/ir.txt diff --git a/estate/__init__.py b/estate/__init__.py index e69de29bb2d..75aa8a6ce33 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from .import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 1f350b3bebd..78df154f211 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,7 +1,13 @@ { - 'name': 'Estate', - 'depends': [ 'base' ], - 'application': True, - 'installable': True, + 'name': 'Estate', # this display the name of our menu.if we removed it ui will show empty name or some weird ui + 'depends': ['base'], # this define dependencies.odoo install this before our module.if we remove it odoo assume no dependency.the base dependency will always install even if we leave depends field empty. + 'category': 'Tutorials', # this define the category of our module.ex:sales,knowledge etc. if we remove this our module goes to uncategorized no function issue only ui impact + 'application': True, #marks module as main app because of this app get it's own icon and become visible in ui.if we make it false or remove it our app/module will not shown in ui.the default value of application is false + 'installable': True, #control wheather module can be installed or not.if True we can install it,if false then module can't be install and will be hidden.the default value is True 'version': '1.0', + 'author': 'vikvi', #shows who created the module.there will be no technical issue if we remove it + 'license': 'LGPL-3', #define legel licence of your moudule it's open source license + 'data' : [ + 'security/ir.model.access.csv' + ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..dfed455c5a4 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from .import estate_property \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..f553d5b6462 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,45 @@ +from odoo import fields, models + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property for purchasing and selling properties" + + name = fields.Char(required=True) + description = fields.Char() + postcode = fields.Char() + date_availibility = fields.Date() + expected_price = fields.Float(required=True) + selling_price = fields.Float() + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + selection=[ + ('north', 'North'), # value,label + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + + ], + ) + +# module: a module is a package of features/functionality in odoo.Ex : CRM,Sales,EstateProperty.it's a complete feature bundle +# model : model is a python class that define data structure and logic.it represent a real world entity +# table: a table is the actual data stored in database.our _name = "estate.property" will converted to estate_property table name +# relationship : module -> model -> table + +# Odoo's ORM calls `_auto_init()` on your model, which introspects all fields and runs `CREATE TABLE` / `ALTER TABLE`. Here's roughly what happens: +# ``` +# _name = "estate.property" => estate is a module name and property is a model name this tells that "property" belongs to estate module. giving name like using dot (estate.property) is a odoo convention +# ↓ +# table name = "estate_property" +# The _ prefix tells Odoo "this is configuration about the model itself", not "this is a field to store in the database". + + +#what is happening in backend? how odoo is connecting to psql even if we are not adding code to connect it? +#=>when odoo load's our model it convert out table name estate.property to estate_property +#=> Odoo's base Model class (which your class inherits from) has a method called _auto_init(). This runs automatically during module install/upgrade. +#=> inside auto_init method there is self._cr, it's a database cursor this is the actual live connection to postgresql.it send the connection request to psycopg2 py library and this library establish the connection to psql \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..cccbe012198 --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,4 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"access_estate_property","access_estate_property","model_estate_property","base.group_user",1,1,1,1 + + diff --git a/estate/security/ir.txt b/estate/security/ir.txt new file mode 100644 index 00000000000..3bc7d169bf7 --- /dev/null +++ b/estate/security/ir.txt @@ -0,0 +1,16 @@ +ir.model.access.csv file control the access of our model through odoo. + +User clicks "Create Property" in Odoo UI + ↓ +Odoo checks ir.model.access.csv + ↓ +is this user's group allowed to create? + ↓ +YES → ORM fires SQL to PostgreSQL +NO → "Access Denied" error shown to user + PostgreSQL never even gets the request + +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"access_estate_property","access_estate_property","model_estate_property","base.group_user",1,1,1,1 + + From 50d2e13574286667e5b63df0f21b8581c00ce57e Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Mon, 6 Apr 2026 18:03:35 +0530 Subject: [PATCH 05/21] [ADD] estate: add access rights for estate.property (Chapter 4) - Added ir.model.access.csv for estate.property - Configured basic CRUD permissions for internal users Chapter 4 - Security Intro --- estate/models/estate_property.py | 1 - 1 file changed, 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index f553d5b6462..517f41aa904 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -34,7 +34,6 @@ class EstateProperty(models.Model): # Odoo's ORM calls `_auto_init()` on your model, which introspects all fields and runs `CREATE TABLE` / `ALTER TABLE`. Here's roughly what happens: # ``` # _name = "estate.property" => estate is a module name and property is a model name this tells that "property" belongs to estate module. giving name like using dot (estate.property) is a odoo convention -# ↓ # table name = "estate_property" # The _ prefix tells Odoo "this is configuration about the model itself", not "this is a field to store in the database". From 33446e04531a249a18cffd222ce8cfce6bdc2144 Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Wed, 8 Apr 2026 11:08:12 +0530 Subject: [PATCH 06/21] [ADD] estate: add chapter 5 UI and property fields - Introduce the estate module into the user interface. - Add estate_menus.xml to define the module's menu structure and make it visible. - Include estate_property_views.xml to define the initial property views. - Implement the necessary extra fields and conditions as required by chapter 5. --- estate/__init__.py | 2 +- estate/__manifest__.py | 14 ++++---- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 49 ++++++++++++++++---------- estate/security/ir.model.access.csv | 6 ++-- estate/views/estate_menus.xml | 12 +++++++ estate/views/estate_property_views.xml | 9 +++++ 7 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__init__.py b/estate/__init__.py index 75aa8a6ce33..4920c6bc8a0 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1 @@ -from .import models \ No newline at end of file +from .import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 78df154f211..2d898612f35 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,12 +2,14 @@ 'name': 'Estate', # this display the name of our menu.if we removed it ui will show empty name or some weird ui 'depends': ['base'], # this define dependencies.odoo install this before our module.if we remove it odoo assume no dependency.the base dependency will always install even if we leave depends field empty. 'category': 'Tutorials', # this define the category of our module.ex:sales,knowledge etc. if we remove this our module goes to uncategorized no function issue only ui impact - 'application': True, #marks module as main app because of this app get it's own icon and become visible in ui.if we make it false or remove it our app/module will not shown in ui.the default value of application is false - 'installable': True, #control wheather module can be installed or not.if True we can install it,if false then module can't be install and will be hidden.the default value is True + 'application': True, # marks module as main app because of this app get it's own icon and become visible in ui.if we make it false or remove it our app/module will not shown in ui.the default value of application is false + 'installable': True, # control wheather module can be installed or not.if True we can install it,if false then module can't be install and will be hidden.the default value is True 'version': '1.0', - 'author': 'vikvi', #shows who created the module.there will be no technical issue if we remove it - 'license': 'LGPL-3', #define legel licence of your moudule it's open source license - 'data' : [ - 'security/ir.model.access.csv' + 'author': 'vikvi', # shows who created the module.there will be no technical issue if we remove it + 'license': 'LGPL-3', # define legel licence of your moudule it's open source license + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_menus.xml', + 'views/estate_property_views.xml', ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index dfed455c5a4..f1db166ebd9 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from .import estate_property \ No newline at end of file +from .import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 517f41aa904..bde705e0e50 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,16 +1,18 @@ from odoo import fields, models +from odoo.tools import date_utils class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property for purchasing and selling properties" - - name = fields.Char(required=True) + + # _log_access = False # this will disable the automatic creation of create_uid,create_date,write_uid,write_date fields in our table. these fields are used to track who created and modified the record and when it was done. + title = fields.Char(required=True) description = fields.Char() postcode = fields.Char() - date_availibility = fields.Date() + date_availibility = fields.Date(copy=False, default=lambda self: fields.Date.today() + date_utils.get_timedelta(3, "month")) expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() + selling_price = fields.Float(readonly=True,copy=False) + bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() @@ -25,20 +27,31 @@ class EstateProperty(models.Model): ], ) + status = fields.Selection( + selection = [ + ('new','New'), + ('offer received','Offer received'), + ('offer accepted','Offer accepted'), + ('sold','Sold'), + ('cancelled','Cancelled') + ], + ) + active = fields.Boolean() + + # module: a module is a package of features/functionality in odoo.Ex : CRM,Sales,EstateProperty.it's a complete feature bundle + # model : model is a python class that define data structure and logic.it represent a real world entity + # table: a table is the actual data stored in database.our _name = "estate.property" will converted to estate_property table name + # relationship : module -> model -> table -# module: a module is a package of features/functionality in odoo.Ex : CRM,Sales,EstateProperty.it's a complete feature bundle -# model : model is a python class that define data structure and logic.it represent a real world entity -# table: a table is the actual data stored in database.our _name = "estate.property" will converted to estate_property table name -# relationship : module -> model -> table + # Odoo's ORM calls `_auto_init()` on your model, which introspects all fields and runs `CREATE TABLE` / `ALTER TABLE`. Here's roughly what happens: + # ``` + # _name = "estate.property" => estate is a module name and property is a model name this tells that "property" belongs to estate module. giving name like using dot (estate.property) is a odoo convention + # table name = "estate_property" + # The _ prefix tells Odoo "this is configuration about the model itself", not "this is a field to store in the database". -# Odoo's ORM calls `_auto_init()` on your model, which introspects all fields and runs `CREATE TABLE` / `ALTER TABLE`. Here's roughly what happens: -# ``` -# _name = "estate.property" => estate is a module name and property is a model name this tells that "property" belongs to estate module. giving name like using dot (estate.property) is a odoo convention -# table name = "estate_property" -# The _ prefix tells Odoo "this is configuration about the model itself", not "this is a field to store in the database". + #what is happening in backend? how odoo is connecting to psql even if we are not adding code to connect it? + #=>when odoo load's our model it convert out table name estate.property to estate_property + #=> Odoo's base Model class (which your class inherits from) has a method called _auto_init(). This runs automatically during module install/upgrade. + #=> inside auto_init method there is self._cr, it's a database cursor this is the actual live connection to postgresql.it send the connection request to psycopg2 py library and this library establish the connection to psql -#what is happening in backend? how odoo is connecting to psql even if we are not adding code to connect it? -#=>when odoo load's our model it convert out table name estate.property to estate_property -#=> Odoo's base Model class (which your class inherits from) has a method called _auto_init(). This runs automatically during module install/upgrade. -#=> inside auto_init method there is self._cr, it's a database cursor this is the actual live connection to postgresql.it send the connection request to psycopg2 py library and this library establish the connection to psql \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index cccbe012198..32389642d4f 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,4 +1,2 @@ -"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -"access_estate_property","access_estate_property","model_estate_property","base.group_user",1,1,1,1 - - +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..6b7d5643153 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..4dd1bd56501 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,9 @@ + + + + Estate Property + estate.property + list,form + + + From 25fe23bae19ab0922a7c1a79a51d31b9e6aff445 Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Wed, 8 Apr 2026 14:04:11 +0530 Subject: [PATCH 07/21] [FIX] estate: improve code clarity and consistency - Removed superfluous comments that added noise without providing value. - Addressed inconsistent indentation to ensure adherence to code style guidelines. --- estate/models/estate_property.py | 42 ++++++++------------------ estate/views/estate_menus.xml | 12 +++----- estate/views/estate_property_views.xml | 8 ++--- 3 files changed, 20 insertions(+), 42 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index bde705e0e50..1fda3f64e91 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,17 +1,17 @@ from odoo import fields, models from odoo.tools import date_utils + class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property for purchasing and selling properties" - - # _log_access = False # this will disable the automatic creation of create_uid,create_date,write_uid,write_date fields in our table. these fields are used to track who created and modified the record and when it was done. - title = fields.Char(required=True) + + name = fields.Char(required=True, string="Property Name") description = fields.Char() postcode = fields.Char() - date_availibility = fields.Date(copy=False, default=lambda self: fields.Date.today() + date_utils.get_timedelta(3, "month")) + date_availibility = fields.Date(copy=False, default=lambda self: fields.Date.today() + date_utils.get_timedelta(3, "month")) expected_price = fields.Float(required=True) - selling_price = fields.Float(readonly=True,copy=False) + selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() @@ -28,30 +28,14 @@ class EstateProperty(models.Model): ], ) status = fields.Selection( - selection = [ - ('new','New'), - ('offer received','Offer received'), - ('offer accepted','Offer accepted'), - ('sold','Sold'), - ('cancelled','Cancelled') + selection=[ + ('new', 'New'), + ('offer received', 'Offer received'), + ('offer accepted', 'Offer accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled') ], - ) + ) active = fields.Boolean() - # module: a module is a package of features/functionality in odoo.Ex : CRM,Sales,EstateProperty.it's a complete feature bundle - # model : model is a python class that define data structure and logic.it represent a real world entity - # table: a table is the actual data stored in database.our _name = "estate.property" will converted to estate_property table name - # relationship : module -> model -> table - - # Odoo's ORM calls `_auto_init()` on your model, which introspects all fields and runs `CREATE TABLE` / `ALTER TABLE`. Here's roughly what happens: - # ``` - # _name = "estate.property" => estate is a module name and property is a model name this tells that "property" belongs to estate module. giving name like using dot (estate.property) is a odoo convention - # table name = "estate_property" - # The _ prefix tells Odoo "this is configuration about the model itself", not "this is a field to store in the database". - - - #what is happening in backend? how odoo is connecting to psql even if we are not adding code to connect it? - #=>when odoo load's our model it convert out table name estate.property to estate_property - #=> Odoo's base Model class (which your class inherits from) has a method called _auto_init(). This runs automatically during module install/upgrade. - #=> inside auto_init method there is self._cr, it's a database cursor this is the actual live connection to postgresql.it send the connection request to psycopg2 py library and this library establish the connection to psql - + \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 6b7d5643153..7da252e6846 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,12 +1,8 @@ - - - - + + + + - - - - diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 4dd1bd56501..0a6cec70d6a 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,9 +1,7 @@ - Estate Property - estate.property - list,form - - + Estate Property + estate.property + list,form From e80d11008b979164ee1ebece6064ddea8be8f650 Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Wed, 8 Apr 2026 14:13:36 +0530 Subject: [PATCH 08/21] [FIX] estate: improve code consistency - Addressed inconsistent indentation to ensure adherence to code style guidelines. --- estate/models/estate_property.py | 2 -- estate/views/estate_property_views.xml | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 1fda3f64e91..bad94ee9b27 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -37,5 +37,3 @@ class EstateProperty(models.Model): ], ) active = fields.Boolean() - - \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 0a6cec70d6a..84e852f6b2d 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,4 +4,5 @@ Estate Property estate.property list,form + From cf4d3fabb3af1be7915a8674669511633454bbd0 Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Fri, 10 Apr 2026 12:50:38 +0530 Subject: [PATCH 09/21] [ADD] estate: Add list, form, and search views for [chapter 6] - Implement the list view for the estate properties. - Implement the form view for detailed property management. - Implement the search view to allow filtering and grouping of properties. - These views are introduced as part of the Chapter 6 exercises in the Odoo development tutorial. --- estate/__init__.py | 2 +- estate/__manifest__.py | 16 ++--- estate/models/estate_property.py | 2 +- estate/security/ir.txt | 16 ----- estate/views/estate_property_views.xml | 81 ++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 26 deletions(-) delete mode 100644 estate/security/ir.txt diff --git a/estate/__init__.py b/estate/__init__.py index 4920c6bc8a0..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1 @@ -from .import models +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 2d898612f35..2a4fc4a145d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,15 +1,15 @@ { - 'name': 'Estate', # this display the name of our menu.if we removed it ui will show empty name or some weird ui - 'depends': ['base'], # this define dependencies.odoo install this before our module.if we remove it odoo assume no dependency.the base dependency will always install even if we leave depends field empty. - 'category': 'Tutorials', # this define the category of our module.ex:sales,knowledge etc. if we remove this our module goes to uncategorized no function issue only ui impact - 'application': True, # marks module as main app because of this app get it's own icon and become visible in ui.if we make it false or remove it our app/module will not shown in ui.the default value of application is false - 'installable': True, # control wheather module can be installed or not.if True we can install it,if false then module can't be install and will be hidden.the default value is True + 'name': 'Estate', + 'depends': ['base'], + 'category': 'Tutorials', + 'application': True, + 'installable': True, 'version': '1.0', - 'author': 'vikvi', # shows who created the module.there will be no technical issue if we remove it - 'license': 'LGPL-3', # define legel licence of your moudule it's open source license + 'author': 'vikvi', + 'license': 'LGPL-3', 'data': [ 'security/ir.model.access.csv', - 'views/estate_menus.xml', 'views/estate_property_views.xml', + 'views/estate_menus.xml', ], } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index bad94ee9b27..576c9829ee5 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -27,7 +27,7 @@ class EstateProperty(models.Model): ], ) - status = fields.Selection( + state = fields.Selection( selection=[ ('new', 'New'), ('offer received', 'Offer received'), diff --git a/estate/security/ir.txt b/estate/security/ir.txt deleted file mode 100644 index 3bc7d169bf7..00000000000 --- a/estate/security/ir.txt +++ /dev/null @@ -1,16 +0,0 @@ -ir.model.access.csv file control the access of our model through odoo. - -User clicks "Create Property" in Odoo UI - ↓ -Odoo checks ir.model.access.csv - ↓ -is this user's group allowed to create? - ↓ -YES → ORM fires SQL to PostgreSQL -NO → "Access Denied" error shown to user - PostgreSQL never even gets the request - -"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -"access_estate_property","access_estate_property","model_estate_property","base.group_user",1,1,1,1 - - diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 84e852f6b2d..741820d3c13 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,5 +1,86 @@ + + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + Estate Property estate.property From e81d39e3d24b32cb4b63ea23e26b7034e166e948 Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Tue, 14 Apr 2026 15:29:12 +0530 Subject: [PATCH 10/21] [ADD] estate: establish relational fields for estate models Implement Many2one, Many2many, and One2many relationships: - Add Many2one link from property to property type - Add Many2many link between property and property tags - Add One2many link from property to offers - Update views to display the new relational fields --- estate/__manifest__.py | 6 +-- estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 7 +++- estate/models/estate_property_offer.py | 13 +++++++ estate/models/estate_property_tag.py | 6 +++ estate/models/estate_property_type.py | 6 +++ estate/security/ir.model.access.csv | 3 ++ estate/views/estate_menus.xml | 10 +++-- estate/views/estate_property_tag_views.xml | 9 +++++ estate/views/estate_property_type_views.xml | 9 +++++ estate/views/estate_property_views.xml | 42 ++++++++++++++------- 11 files changed, 94 insertions(+), 20 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_tag_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 2a4fc4a145d..ec6fc5dfd7f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,12 +4,12 @@ 'category': 'Tutorials', 'application': True, 'installable': True, - 'version': '1.0', 'author': 'vikvi', - 'license': 'LGPL-3', 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', - 'views/estate_menus.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_menus.xml' ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f1db166ebd9..0be7bb647d3 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from .import estate_property +from .import estate_property_type +from .import estate_property_tag +from .import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 576c9829ee5..a86d7f8cfa0 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -20,7 +20,7 @@ class EstateProperty(models.Model): garden_area = fields.Integer() garden_orientation = fields.Selection( selection=[ - ('north', 'North'), # value,label + ('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West'), @@ -36,4 +36,9 @@ class EstateProperty(models.Model): ('cancelled', 'Cancelled') ], ) + property_type_id = fields.Many2one("estate.property.type", string="Property Type Id") + tag_ids = fields.Many2many("estate.property.tag", string="Property Tags") + salesperson = fields.Many2one('res.users', default=lambda self: self.env.user) + buyer = fields.Many2one('res.partner', copy=False) + offer_ids = fields.One2many('estate.property.offer', 'property_id') active = fields.Boolean() diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..2174421adf3 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,13 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + price = fields.Float() + status = fields.Selection([ + ('accepted', "Accepted"), + ('refused', "Refused") + ], copy=False) + + property_id = fields.Many2one('estate.property', required=True) + partner_id = fields.Many2one('res.partner', required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..4adfab2b529 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,6 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..614c11b7bae --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,6 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 32389642d4f..89f97c50842 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 7da252e6846..cf4247f213c 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,8 +1,12 @@ - - - + + + + + + + diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..3cd72cf7ee5 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,9 @@ + + + + + Property Tags + estate.property.tag + list,form + + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..1a74bbaad9f --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,9 @@ + + + + + Property Types + estate.property.type + list,form + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 741820d3c13..7d398b8f1a0 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,26 +1,26 @@ - estate.property.list estate.property - + + + - estate.property.form estate.property - +
@@ -30,7 +30,8 @@
- + + @@ -41,7 +42,7 @@ - + @@ -52,14 +53,28 @@ - + + + + + + + + + + + + + + + +
- estate.property.search estate.property @@ -71,19 +86,20 @@ - - - + + + + - Estate Property estate.property - list,form + {'search_default_active' : 1} + list,form
From 285c47fbc8507e579b4dac58dc473ec8a3b3ee55 Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Tue, 14 Apr 2026 15:53:49 +0530 Subject: [PATCH 11/21] [FIX] estate: add missing description and license - The estate module's manifest was updated to include a proper description. - A standard LICENSE has been added . - These additions ensure the module is complete and adheres to standard Odoo module structure. --- estate/__manifest__.py | 1 + estate/models/estate_property_offer.py | 2 ++ estate/models/estate_property_tag.py | 1 + estate/models/estate_property_type.py | 1 + 4 files changed, 5 insertions(+) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index ec6fc5dfd7f..e4f34f5c292 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,6 +5,7 @@ 'application': True, 'installable': True, 'author': 'vikvi', + 'license': 'LGPL-3', 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 2174421adf3..b76121b7a14 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -3,6 +3,8 @@ class EstatePropertyOffer(models.Model): _name = "estate.property.offer" + _description = "Estate Property Offer" + price = fields.Float() status = fields.Selection([ ('accepted', "Accepted"), diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 4adfab2b529..59f0f57ea4d 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -3,4 +3,5 @@ class EstatePropertyTag(models.Model): _name = "estate.property.tag" + _description = "Estate Property Tag" name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 614c11b7bae..5ece848f499 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -3,4 +3,5 @@ class EstatePropertyType(models.Model): _name = "estate.property.type" + _description = "Estate Property Type" name = fields.Char(required=True) From 2797b5aff8f7f0dbf0d451093fea3c0324ec63e1 Mon Sep 17 00:00:00 2001 From: vikvi-odoo Date: Fri, 17 Apr 2026 12:22:59 +0530 Subject: [PATCH 12/21] [ADD] estate: add property actions and offer management logic - Implement action_sold_property and action_cancel_property buttons. - Add validation to prevent cancelling a sold property and vice-versa. - Add action_accept_offer and action_reject_offer buttons in offer list. - Implement logic to automatically set selling_price and buyer upon offer acceptance. - Add UserError constraints to ensure data integrity during state transitions. --- estate/models/estate_property.py | 46 ++++++++++++++++++++++++-- estate/models/estate_property_offer.py | 42 ++++++++++++++++++++++- estate/views/estate_property_views.xml | 29 +++++++++++----- 3 files changed, 106 insertions(+), 11 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index a86d7f8cfa0..fb2a7cea1da 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ -from odoo import fields, models +from odoo import api, fields, models from odoo.tools import date_utils +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -9,7 +10,7 @@ class EstateProperty(models.Model): name = fields.Char(required=True, string="Property Name") description = fields.Char() postcode = fields.Char() - date_availibility = fields.Date(copy=False, default=lambda self: fields.Date.today() + date_utils.get_timedelta(3, "month")) + date_availibility = fields.Date(copy=False, default=lambda x: fields.Date.today() + date_utils.get_timedelta(3, "month")) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) @@ -42,3 +43,44 @@ class EstateProperty(models.Model): buyer = fields.Many2one('res.partner', copy=False) offer_ids = fields.One2many('estate.property.offer', 'property_id') active = fields.Boolean() + + total = fields.Integer(compute='_compute_totalArea') + best_price = fields.Float(compute='_compute_best_price') + + @api.depends('living_area', 'garden_area') + def _compute_totalArea(self): + for record in self: + record.total = record.garden_area + record.living_area + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + prices = record.offer_ids.mapped('price') + if prices: + record.best_price = max(prices) + else: + record.best_price = 0 + + @api.onchange('garden') + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = 0 + self.garden_orientation = "" + + def action_sold_property(self): + if self.state == 'cancelled': + raise UserError("A cancelled property cannot be set as sold.") + return False + + self.state = 'sold' + return True + + def action_cancel_property(self): + if self.state == 'sold': + raise UserError("A sold property cannot be set as cancelled.") + + self.state = 'cancelled' + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index b76121b7a14..b7c48122838 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,6 @@ -from odoo import fields, models +from odoo import fields, models, api +from datetime import date, timedelta +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): @@ -13,3 +15,41 @@ class EstatePropertyOffer(models.Model): property_id = fields.Many2one('estate.property', required=True) partner_id = fields.Many2one('res.partner', required=True) + + validity = fields.Integer(default=7) + date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_validity') + property_type = fields.Many2one(related="property_id.property_type_id", string="Property Type") + + @api.depends('validity') + def _compute_date_deadline(self): + for record in self: + + create_date = record.create_date.date() if record.create_date else date.today() + record.date_deadline = create_date + timedelta(days=record.validity) + + def _inverse_validity(self): + for record in self: + + create_date = record.create_date.date() if record.create_date else date.today() + record.validity = (record.date_deadline - create_date).days + + def action_accept_offer(self): + for record in self.property_id.offer_ids: + record.status = 'refused' + + for record in self.property_id.offer_ids: + if record.status == 'accepted': + raise UserError('Offer is already accepted') + return False + + self.property_id.selling_price = self.price + self.property_id.buyer = self.partner_id + self.status = 'accepted' + return True + + def action_reject_offer(self): + + self.status = 'refused' + self.property_id.selling_price = 0 + self.property_id.buyer = "" + return True diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 7d398b8f1a0..65564781f31 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -3,8 +3,8 @@ estate.property.list estate.property - - + + @@ -12,7 +12,7 @@ - + @@ -22,6 +22,11 @@ estate.property
+
+
+

@@ -31,13 +36,15 @@ - + + + @@ -51,16 +58,22 @@ - + + - - - + + + + + +

- diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 8a1ad605390..388cefa3580 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,7 +4,7 @@ estate.property.list estate.property - + @@ -24,10 +24,9 @@
-

@@ -36,8 +35,8 @@

- - + + @@ -45,7 +44,6 @@ - @@ -57,22 +55,21 @@ - - + +
- - + + - - diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 388cefa3580..493d2ef56d4 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,14 +4,14 @@ estate.property.list estate.property - + - + @@ -35,7 +35,7 @@ - + @@ -69,7 +69,7 @@