From c8993771ca166f6fa3069c2ae821e7e8b8dcdaf8 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Fri, 8 May 2026 17:52:56 +0530 Subject: [PATCH 01/25] [ADD] estate: Create estate property model - Added estate.property model - Defined basic property fields - Added understanding of Odoo three-tier architecture --- myenv/bin/Activate.ps1 | 247 +++++++++++++++++++++++++++++++++ myenv/bin/activate | 70 ++++++++++ myenv/bin/activate.csh | 27 ++++ myenv/bin/activate.fish | 69 +++++++++ myenv/bin/ipdb3 | 8 ++ myenv/bin/ipython | 8 ++ myenv/bin/ipython3 | 8 ++ myenv/bin/pip | 8 ++ myenv/bin/pip3 | 8 ++ myenv/bin/pip3.12 | 8 ++ myenv/bin/pygmentize | 8 ++ myenv/bin/python | 1 + myenv/bin/python3 | 1 + myenv/bin/python3.12 | 1 + myenv/lib64 | 1 + myenv/pyvenv.cfg | 5 + myenv/share/man/man1/ipython.1 | 60 ++++++++ 17 files changed, 538 insertions(+) create mode 100644 myenv/bin/Activate.ps1 create mode 100644 myenv/bin/activate create mode 100644 myenv/bin/activate.csh create mode 100644 myenv/bin/activate.fish create mode 100755 myenv/bin/ipdb3 create mode 100755 myenv/bin/ipython create mode 100755 myenv/bin/ipython3 create mode 100755 myenv/bin/pip create mode 100755 myenv/bin/pip3 create mode 100755 myenv/bin/pip3.12 create mode 100755 myenv/bin/pygmentize create mode 120000 myenv/bin/python create mode 120000 myenv/bin/python3 create mode 120000 myenv/bin/python3.12 create mode 120000 myenv/lib64 create mode 100644 myenv/pyvenv.cfg create mode 100644 myenv/share/man/man1/ipython.1 diff --git a/myenv/bin/Activate.ps1 b/myenv/bin/Activate.ps1 new file mode 100644 index 00000000000..b49d77ba44b --- /dev/null +++ b/myenv/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/myenv/bin/activate b/myenv/bin/activate new file mode 100644 index 00000000000..153e032b118 --- /dev/null +++ b/myenv/bin/activate @@ -0,0 +1,70 @@ +# This file must be used with "source bin/activate" *from bash* +# You cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # Call hash to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + hash -r 2> /dev/null + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +# on Windows, a path can contain colons and backslashes and has to be converted: +if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then + # transform D:\path\to\venv to /d/path/to/venv on MSYS + # and to /cygdrive/d/path/to/venv on Cygwin + export VIRTUAL_ENV=$(cygpath /home/odoo/odoo19/tutorials/myenv) +else + # use the path as-is + export VIRTUAL_ENV=/home/odoo/odoo19/tutorials/myenv +fi + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/"bin":$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1='(myenv) '"${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT='(myenv) ' + export VIRTUAL_ENV_PROMPT +fi + +# Call hash to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +hash -r 2> /dev/null diff --git a/myenv/bin/activate.csh b/myenv/bin/activate.csh new file mode 100644 index 00000000000..5ec021a2c10 --- /dev/null +++ b/myenv/bin/activate.csh @@ -0,0 +1,27 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. + +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV /home/odoo/odoo19/tutorials/myenv + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/"bin":$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = '(myenv) '"$prompt" + setenv VIRTUAL_ENV_PROMPT '(myenv) ' +endif + +alias pydoc python -m pydoc + +rehash diff --git a/myenv/bin/activate.fish b/myenv/bin/activate.fish new file mode 100644 index 00000000000..37726716887 --- /dev/null +++ b/myenv/bin/activate.fish @@ -0,0 +1,69 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/). You cannot run it directly. + +function deactivate -d "Exit virtual environment and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + set -e _OLD_FISH_PROMPT_OVERRIDE + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV /home/odoo/odoo19/tutorials/myenv + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/"bin $PATH + +# Unset PYTHONHOME if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # Save the current fish_prompt function as the function _old_fish_prompt. + functions -c fish_prompt _old_fish_prompt + + # With the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command. + set -l old_status $status + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) '(myenv) ' (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT '(myenv) ' +end diff --git a/myenv/bin/ipdb3 b/myenv/bin/ipdb3 new file mode 100755 index 00000000000..6988c24582b --- /dev/null +++ b/myenv/bin/ipdb3 @@ -0,0 +1,8 @@ +#!/home/odoo/odoo19/tutorials/myenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from ipdb.__main__ import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/myenv/bin/ipython b/myenv/bin/ipython new file mode 100755 index 00000000000..d874db4ef6a --- /dev/null +++ b/myenv/bin/ipython @@ -0,0 +1,8 @@ +#!/home/odoo/odoo19/tutorials/myenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from IPython import start_ipython +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(start_ipython()) diff --git a/myenv/bin/ipython3 b/myenv/bin/ipython3 new file mode 100755 index 00000000000..d874db4ef6a --- /dev/null +++ b/myenv/bin/ipython3 @@ -0,0 +1,8 @@ +#!/home/odoo/odoo19/tutorials/myenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from IPython import start_ipython +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(start_ipython()) diff --git a/myenv/bin/pip b/myenv/bin/pip new file mode 100755 index 00000000000..9ff3b056db9 --- /dev/null +++ b/myenv/bin/pip @@ -0,0 +1,8 @@ +#!/home/odoo/odoo19/tutorials/myenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/myenv/bin/pip3 b/myenv/bin/pip3 new file mode 100755 index 00000000000..9ff3b056db9 --- /dev/null +++ b/myenv/bin/pip3 @@ -0,0 +1,8 @@ +#!/home/odoo/odoo19/tutorials/myenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/myenv/bin/pip3.12 b/myenv/bin/pip3.12 new file mode 100755 index 00000000000..9ff3b056db9 --- /dev/null +++ b/myenv/bin/pip3.12 @@ -0,0 +1,8 @@ +#!/home/odoo/odoo19/tutorials/myenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/myenv/bin/pygmentize b/myenv/bin/pygmentize new file mode 100755 index 00000000000..0b3394b5d0c --- /dev/null +++ b/myenv/bin/pygmentize @@ -0,0 +1,8 @@ +#!/home/odoo/odoo19/tutorials/myenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pygments.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/myenv/bin/python b/myenv/bin/python new file mode 120000 index 00000000000..b8a0adbbb97 --- /dev/null +++ b/myenv/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/myenv/bin/python3 b/myenv/bin/python3 new file mode 120000 index 00000000000..ae65fdaa129 --- /dev/null +++ b/myenv/bin/python3 @@ -0,0 +1 @@ +/usr/bin/python3 \ No newline at end of file diff --git a/myenv/bin/python3.12 b/myenv/bin/python3.12 new file mode 120000 index 00000000000..b8a0adbbb97 --- /dev/null +++ b/myenv/bin/python3.12 @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/myenv/lib64 b/myenv/lib64 new file mode 120000 index 00000000000..7951405f85a --- /dev/null +++ b/myenv/lib64 @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/myenv/pyvenv.cfg b/myenv/pyvenv.cfg new file mode 100644 index 00000000000..c3fe9dc187d --- /dev/null +++ b/myenv/pyvenv.cfg @@ -0,0 +1,5 @@ +home = /usr/bin +include-system-site-packages = false +version = 3.12.3 +executable = /usr/bin/python3.12 +command = /usr/bin/python3 -m venv /home/odoo/odoo19/tutorials/myenv diff --git a/myenv/share/man/man1/ipython.1 b/myenv/share/man/man1/ipython.1 new file mode 100644 index 00000000000..0f4a191f3fa --- /dev/null +++ b/myenv/share/man/man1/ipython.1 @@ -0,0 +1,60 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH IPYTHON 1 "July 15, 2011" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) and groff_man(7) +.\" .SH section heading +.\" .SS secondary section heading +.\" +.\" +.\" To preview this page as plain text: nroff -man ipython.1 +.\" +.SH NAME +ipython \- Tools for Interactive Computing in Python. +.SH SYNOPSIS +.B ipython +.RI [ options ] " files" ... + +.B ipython subcommand +.RI [ options ] ... + +.SH DESCRIPTION +An interactive Python shell with automatic history (input and output), dynamic +object introspection, easier configuration, command completion, access to the +system shell, integration with numerical and scientific computing tools, +web notebook, Qt console, and more. + +For more information on how to use IPython, see 'ipython \-\-help', +or 'ipython \-\-help\-all' for all available command\(hyline options. + +.SH "ENVIRONMENT VARIABLES" +.sp +.PP +\fIIPYTHONDIR\fR +.RS 4 +This is the location where IPython stores all its configuration files. The default +is $HOME/.ipython if IPYTHONDIR is not defined. + +You can see the computed value of IPYTHONDIR with `ipython locate`. + +.SH FILES + +IPython uses various configuration files stored in profiles within IPYTHONDIR. +To generate the default configuration files and start configuring IPython, +do 'ipython profile create', and edit '*_config.py' files located in +IPYTHONDIR/profile_default. + +.SH AUTHORS +IPython is written by the IPython Development Team . From 50755fd19f888504e3f1b670d849a89fa0eadfc7 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Mon, 11 May 2026 18:44:39 +0530 Subject: [PATCH 02/25] [IMP] estate: Initialize Estate application module - Created estate module structure - Added __init__.py and __manifest__.py - Configured base module dependency - Enabled module as an installable app --- estate/__init__.py | 1 + estate/__manifest__.py | 13 +++++++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 13 +++++++++++++ 4 files changed, 28 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ 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 new file mode 100644 index 00000000000..4f2077479d5 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,13 @@ +{ + + "name": "real estate", + "version": "1.0", + "summary": "Estate management", + "description": "Estate management", + "author": "Odoo S.A.", + "website": "https://www.odoo.com/", + "category": "Uncategorized", + "depends": ["base"], + "application": True, + "installable":True +} \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..f4c8fd6db6d --- /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..868ab64b1a6 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,13 @@ +from odoo import fields, models + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real Estate Property" + + name = fields.Char(string="Title", required=True) + description = fields.Text(string="Description") + postcode = fields.Char(string="Postcode") + expected_price = fields.Float() + bedrooms = fields.Integer() + active = fields.Boolean(default=True) \ No newline at end of file From aab3a9c4e288d011c1a9800828acd5e687cfa9a1 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Tue, 12 May 2026 18:53:32 +0530 Subject: [PATCH 03/25] [IMP] estate: Add estate property model with essential fields - Created estate.property ORM model - Added core property fields (name, description, postcode, prices, features) - Defined garden orientation selection (North, South, East, West) - Set required constraints on important fields (name, expected_price) - Initialized estate_property database table via ORM --- estate/models/estate_property.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 868ab64b1a6..de97a603e96 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,13 +1,29 @@ -from odoo import fields, models - +from odoo import models, fields class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" - name = fields.Char(string="Title", required=True) - description = fields.Text(string="Description") - postcode = fields.Char(string="Postcode") + name = fields.Char() + description = fields.Text() + + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float() + selling_price = fields.Float() + bedrooms = fields.Integer() - active = fields.Boolean(default=True) \ No newline at end of file + living_area = fields.Integer() + facades = fields.Integer() + + + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection([ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ]) \ No newline at end of file From cf4f1096b4e7de5f375f01eb396efb51eab0b7f9 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Thu, 14 May 2026 18:27:29 +0530 Subject: [PATCH 04/25] [IMP] estate: add security access rights and model permissions - Created ir.model.access.csv for estate.property model - Added read, write, create and delete permissions - Learned how Odoo security works using access rights - Added security file in __manifest__.py - Fixed access warning for estate.property model --- estate/__manifest__.py | 4 ++++ estate/models/estate_property.py | 6 +++--- estate/security/ir.model.access.csv | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 4f2077479d5..4984311c7a0 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,6 +8,10 @@ "website": "https://www.odoo.com/", "category": "Uncategorized", "depends": ["base"], + "data":[ + "security/ir.model.access.csv" + ], + "LICENSE":"LGPL", "application": True, "installable":True } \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index de97a603e96..324ec7e5b59 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -4,20 +4,20 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" - name = fields.Char() + name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() date_availability = fields.Date() expected_price = fields.Float() - selling_price = fields.Float() + selling_price = fields.Float(required=True) bedrooms = fields.Integer() living_area = fields.Integer() facades = fields.Integer() - + garage = fields.Boolean() garden = fields.Boolean() garden_area = fields.Integer() diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..0e11f47e58d --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +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 \ No newline at end of file From 2f69c9bc035d7c6147eec5561432f2363614072e Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Fri, 15 May 2026 18:34:40 +0530 Subject: [PATCH 05/25] [IMP] estate: add UI action and views structure - Added estate_property_views.xml with basic act_window action - Connected estate.property model with UI through action - Understood how menu > action >view >model flow works in Odoo - Created views folder and add the estate_property_views.xml file - After adding this file Odoo loads it successfully during module update - Action gets created in the database but nothing shows in UI bcz menu is not added --- estate/__init__.py | 3 +- estate/__manifest__.py | 8 +- estate/models/__init__.py | 3 +- estate/models/estate_property.py | 10 +- estate/security/ir.model.access.csv | 3 +- estate/views/estate_property_views.xml | 11 ++ myenv/bin/Activate.ps1 | 247 ------------------------- myenv/bin/activate | 70 ------- myenv/bin/activate.csh | 27 --- myenv/bin/activate.fish | 69 ------- myenv/bin/ipdb3 | 8 - myenv/bin/ipython | 8 - myenv/bin/ipython3 | 8 - myenv/bin/pip | 8 - myenv/bin/pip3 | 8 - myenv/bin/pip3.12 | 8 - myenv/bin/pygmentize | 8 - myenv/bin/python | 1 - myenv/bin/python3 | 1 - myenv/bin/python3.12 | 1 - myenv/lib64 | 1 - myenv/pyvenv.cfg | 5 - myenv/share/man/man1/ipython.1 | 60 ------ 23 files changed, 25 insertions(+), 551 deletions(-) create mode 100644 estate/views/estate_property_views.xml delete mode 100644 myenv/bin/Activate.ps1 delete mode 100644 myenv/bin/activate delete mode 100644 myenv/bin/activate.csh delete mode 100644 myenv/bin/activate.fish delete mode 100755 myenv/bin/ipdb3 delete mode 100755 myenv/bin/ipython delete mode 100755 myenv/bin/ipython3 delete mode 100755 myenv/bin/pip delete mode 100755 myenv/bin/pip3 delete mode 100755 myenv/bin/pip3.12 delete mode 100755 myenv/bin/pygmentize delete mode 120000 myenv/bin/python delete mode 120000 myenv/bin/python3 delete mode 120000 myenv/bin/python3.12 delete mode 120000 myenv/lib64 delete mode 100644 myenv/pyvenv.cfg delete mode 100644 myenv/share/man/man1/ipython.1 diff --git a/estate/__init__.py b/estate/__init__.py index 9a7e03eded3..899bcc97f0f 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,2 @@ -from . import models \ No newline at end of file +from . import models + diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 4984311c7a0..ca43f1f5527 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,9 +9,11 @@ "category": "Uncategorized", "depends": ["base"], "data":[ - "security/ir.model.access.csv" + "security/ir.model.access.csv", + "views/estate_property_views.xml" ], - "LICENSE":"LGPL", + "license":"LGPL-3", "application": True, "installable":True -} \ No newline at end of file +} + diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f4c8fd6db6d..c5006b18cf8 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,2 @@ -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 324ec7e5b59..71563ff554f 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -3,21 +3,15 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" - name = fields.Char(required=True) description = fields.Text() - postcode = fields.Char() date_availability = fields.Date() - expected_price = fields.Float() selling_price = fields.Float(required=True) - bedrooms = fields.Integer() living_area = fields.Integer() facades = fields.Integer() - - garage = fields.Boolean() garden = fields.Boolean() garden_area = fields.Integer() @@ -26,4 +20,6 @@ class EstateProperty(models.Model): ('south', 'South'), ('east', 'East'), ('west', 'West'), - ]) \ No newline at end of file + ]) + + diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 0e11f47e58d..d48cf7145c0 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,3 @@ 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 \ No newline at end of file +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 new file mode 100644 index 00000000000..f3928274bc5 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,11 @@ + + + + + Properties + estate.property + list,form + + + + diff --git a/myenv/bin/Activate.ps1 b/myenv/bin/Activate.ps1 deleted file mode 100644 index b49d77ba44b..00000000000 --- a/myenv/bin/Activate.ps1 +++ /dev/null @@ -1,247 +0,0 @@ -<# -.Synopsis -Activate a Python virtual environment for the current PowerShell session. - -.Description -Pushes the python executable for a virtual environment to the front of the -$Env:PATH environment variable and sets the prompt to signify that you are -in a Python virtual environment. Makes use of the command line switches as -well as the `pyvenv.cfg` file values present in the virtual environment. - -.Parameter VenvDir -Path to the directory that contains the virtual environment to activate. The -default value for this is the parent of the directory that the Activate.ps1 -script is located within. - -.Parameter Prompt -The prompt prefix to display when this virtual environment is activated. By -default, this prompt is the name of the virtual environment folder (VenvDir) -surrounded by parentheses and followed by a single space (ie. '(.venv) '). - -.Example -Activate.ps1 -Activates the Python virtual environment that contains the Activate.ps1 script. - -.Example -Activate.ps1 -Verbose -Activates the Python virtual environment that contains the Activate.ps1 script, -and shows extra information about the activation as it executes. - -.Example -Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv -Activates the Python virtual environment located in the specified location. - -.Example -Activate.ps1 -Prompt "MyPython" -Activates the Python virtual environment that contains the Activate.ps1 script, -and prefixes the current prompt with the specified string (surrounded in -parentheses) while the virtual environment is active. - -.Notes -On Windows, it may be required to enable this Activate.ps1 script by setting the -execution policy for the user. You can do this by issuing the following PowerShell -command: - -PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - -For more information on Execution Policies: -https://go.microsoft.com/fwlink/?LinkID=135170 - -#> -Param( - [Parameter(Mandatory = $false)] - [String] - $VenvDir, - [Parameter(Mandatory = $false)] - [String] - $Prompt -) - -<# Function declarations --------------------------------------------------- #> - -<# -.Synopsis -Remove all shell session elements added by the Activate script, including the -addition of the virtual environment's Python executable from the beginning of -the PATH variable. - -.Parameter NonDestructive -If present, do not remove this function from the global namespace for the -session. - -#> -function global:deactivate ([switch]$NonDestructive) { - # Revert to original values - - # The prior prompt: - if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { - Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt - Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT - } - - # The prior PYTHONHOME: - if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { - Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME - Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME - } - - # The prior PATH: - if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { - Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH - Remove-Item -Path Env:_OLD_VIRTUAL_PATH - } - - # Just remove the VIRTUAL_ENV altogether: - if (Test-Path -Path Env:VIRTUAL_ENV) { - Remove-Item -Path env:VIRTUAL_ENV - } - - # Just remove VIRTUAL_ENV_PROMPT altogether. - if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { - Remove-Item -Path env:VIRTUAL_ENV_PROMPT - } - - # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: - if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { - Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force - } - - # Leave deactivate function in the global namespace if requested: - if (-not $NonDestructive) { - Remove-Item -Path function:deactivate - } -} - -<# -.Description -Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the -given folder, and returns them in a map. - -For each line in the pyvenv.cfg file, if that line can be parsed into exactly -two strings separated by `=` (with any amount of whitespace surrounding the =) -then it is considered a `key = value` line. The left hand string is the key, -the right hand is the value. - -If the value starts with a `'` or a `"` then the first and last character is -stripped from the value before being captured. - -.Parameter ConfigDir -Path to the directory that contains the `pyvenv.cfg` file. -#> -function Get-PyVenvConfig( - [String] - $ConfigDir -) { - Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" - - # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). - $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue - - # An empty map will be returned if no config file is found. - $pyvenvConfig = @{ } - - if ($pyvenvConfigPath) { - - Write-Verbose "File exists, parse `key = value` lines" - $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath - - $pyvenvConfigContent | ForEach-Object { - $keyval = $PSItem -split "\s*=\s*", 2 - if ($keyval[0] -and $keyval[1]) { - $val = $keyval[1] - - # Remove extraneous quotations around a string value. - if ("'""".Contains($val.Substring(0, 1))) { - $val = $val.Substring(1, $val.Length - 2) - } - - $pyvenvConfig[$keyval[0]] = $val - Write-Verbose "Adding Key: '$($keyval[0])'='$val'" - } - } - } - return $pyvenvConfig -} - - -<# Begin Activate script --------------------------------------------------- #> - -# Determine the containing directory of this script -$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition -$VenvExecDir = Get-Item -Path $VenvExecPath - -Write-Verbose "Activation script is located in path: '$VenvExecPath'" -Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" -Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" - -# Set values required in priority: CmdLine, ConfigFile, Default -# First, get the location of the virtual environment, it might not be -# VenvExecDir if specified on the command line. -if ($VenvDir) { - Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" -} -else { - Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." - $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") - Write-Verbose "VenvDir=$VenvDir" -} - -# Next, read the `pyvenv.cfg` file to determine any required value such -# as `prompt`. -$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir - -# Next, set the prompt from the command line, or the config file, or -# just use the name of the virtual environment folder. -if ($Prompt) { - Write-Verbose "Prompt specified as argument, using '$Prompt'" -} -else { - Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" - if ($pyvenvCfg -and $pyvenvCfg['prompt']) { - Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" - $Prompt = $pyvenvCfg['prompt']; - } - else { - Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" - Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" - $Prompt = Split-Path -Path $venvDir -Leaf - } -} - -Write-Verbose "Prompt = '$Prompt'" -Write-Verbose "VenvDir='$VenvDir'" - -# Deactivate any currently active virtual environment, but leave the -# deactivate function in place. -deactivate -nondestructive - -# Now set the environment variable VIRTUAL_ENV, used by many tools to determine -# that there is an activated venv. -$env:VIRTUAL_ENV = $VenvDir - -if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { - - Write-Verbose "Setting prompt to '$Prompt'" - - # Set the prompt to include the env name - # Make sure _OLD_VIRTUAL_PROMPT is global - function global:_OLD_VIRTUAL_PROMPT { "" } - Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT - New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt - - function global:prompt { - Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " - _OLD_VIRTUAL_PROMPT - } - $env:VIRTUAL_ENV_PROMPT = $Prompt -} - -# Clear PYTHONHOME -if (Test-Path -Path Env:PYTHONHOME) { - Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME - Remove-Item -Path Env:PYTHONHOME -} - -# Add the venv to the PATH -Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH -$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/myenv/bin/activate b/myenv/bin/activate deleted file mode 100644 index 153e032b118..00000000000 --- a/myenv/bin/activate +++ /dev/null @@ -1,70 +0,0 @@ -# This file must be used with "source bin/activate" *from bash* -# You cannot run it directly - -deactivate () { - # reset old environment variables - if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then - PATH="${_OLD_VIRTUAL_PATH:-}" - export PATH - unset _OLD_VIRTUAL_PATH - fi - if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then - PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" - export PYTHONHOME - unset _OLD_VIRTUAL_PYTHONHOME - fi - - # Call hash to forget past commands. Without forgetting - # past commands the $PATH changes we made may not be respected - hash -r 2> /dev/null - - if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then - PS1="${_OLD_VIRTUAL_PS1:-}" - export PS1 - unset _OLD_VIRTUAL_PS1 - fi - - unset VIRTUAL_ENV - unset VIRTUAL_ENV_PROMPT - if [ ! "${1:-}" = "nondestructive" ] ; then - # Self destruct! - unset -f deactivate - fi -} - -# unset irrelevant variables -deactivate nondestructive - -# on Windows, a path can contain colons and backslashes and has to be converted: -if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then - # transform D:\path\to\venv to /d/path/to/venv on MSYS - # and to /cygdrive/d/path/to/venv on Cygwin - export VIRTUAL_ENV=$(cygpath /home/odoo/odoo19/tutorials/myenv) -else - # use the path as-is - export VIRTUAL_ENV=/home/odoo/odoo19/tutorials/myenv -fi - -_OLD_VIRTUAL_PATH="$PATH" -PATH="$VIRTUAL_ENV/"bin":$PATH" -export PATH - -# unset PYTHONHOME if set -# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) -# could use `if (set -u; : $PYTHONHOME) ;` in bash -if [ -n "${PYTHONHOME:-}" ] ; then - _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" - unset PYTHONHOME -fi - -if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then - _OLD_VIRTUAL_PS1="${PS1:-}" - PS1='(myenv) '"${PS1:-}" - export PS1 - VIRTUAL_ENV_PROMPT='(myenv) ' - export VIRTUAL_ENV_PROMPT -fi - -# Call hash to forget past commands. Without forgetting -# past commands the $PATH changes we made may not be respected -hash -r 2> /dev/null diff --git a/myenv/bin/activate.csh b/myenv/bin/activate.csh deleted file mode 100644 index 5ec021a2c10..00000000000 --- a/myenv/bin/activate.csh +++ /dev/null @@ -1,27 +0,0 @@ -# This file must be used with "source bin/activate.csh" *from csh*. -# You cannot run it directly. - -# Created by Davide Di Blasi . -# Ported to Python 3.3 venv by Andrew Svetlov - -alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' - -# Unset irrelevant variables. -deactivate nondestructive - -setenv VIRTUAL_ENV /home/odoo/odoo19/tutorials/myenv - -set _OLD_VIRTUAL_PATH="$PATH" -setenv PATH "$VIRTUAL_ENV/"bin":$PATH" - - -set _OLD_VIRTUAL_PROMPT="$prompt" - -if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then - set prompt = '(myenv) '"$prompt" - setenv VIRTUAL_ENV_PROMPT '(myenv) ' -endif - -alias pydoc python -m pydoc - -rehash diff --git a/myenv/bin/activate.fish b/myenv/bin/activate.fish deleted file mode 100644 index 37726716887..00000000000 --- a/myenv/bin/activate.fish +++ /dev/null @@ -1,69 +0,0 @@ -# This file must be used with "source /bin/activate.fish" *from fish* -# (https://fishshell.com/). You cannot run it directly. - -function deactivate -d "Exit virtual environment and return to normal shell environment" - # reset old environment variables - if test -n "$_OLD_VIRTUAL_PATH" - set -gx PATH $_OLD_VIRTUAL_PATH - set -e _OLD_VIRTUAL_PATH - end - if test -n "$_OLD_VIRTUAL_PYTHONHOME" - set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME - set -e _OLD_VIRTUAL_PYTHONHOME - end - - if test -n "$_OLD_FISH_PROMPT_OVERRIDE" - set -e _OLD_FISH_PROMPT_OVERRIDE - # prevents error when using nested fish instances (Issue #93858) - if functions -q _old_fish_prompt - functions -e fish_prompt - functions -c _old_fish_prompt fish_prompt - functions -e _old_fish_prompt - end - end - - set -e VIRTUAL_ENV - set -e VIRTUAL_ENV_PROMPT - if test "$argv[1]" != "nondestructive" - # Self-destruct! - functions -e deactivate - end -end - -# Unset irrelevant variables. -deactivate nondestructive - -set -gx VIRTUAL_ENV /home/odoo/odoo19/tutorials/myenv - -set -gx _OLD_VIRTUAL_PATH $PATH -set -gx PATH "$VIRTUAL_ENV/"bin $PATH - -# Unset PYTHONHOME if set. -if set -q PYTHONHOME - set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME - set -e PYTHONHOME -end - -if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" - # fish uses a function instead of an env var to generate the prompt. - - # Save the current fish_prompt function as the function _old_fish_prompt. - functions -c fish_prompt _old_fish_prompt - - # With the original prompt function renamed, we can override with our own. - function fish_prompt - # Save the return status of the last command. - set -l old_status $status - - # Output the venv prompt; color taken from the blue of the Python logo. - printf "%s%s%s" (set_color 4B8BBE) '(myenv) ' (set_color normal) - - # Restore the return status of the previous command. - echo "exit $old_status" | . - # Output the original/"old" prompt. - _old_fish_prompt - end - - set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" - set -gx VIRTUAL_ENV_PROMPT '(myenv) ' -end diff --git a/myenv/bin/ipdb3 b/myenv/bin/ipdb3 deleted file mode 100755 index 6988c24582b..00000000000 --- a/myenv/bin/ipdb3 +++ /dev/null @@ -1,8 +0,0 @@ -#!/home/odoo/odoo19/tutorials/myenv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from ipdb.__main__ import main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(main()) diff --git a/myenv/bin/ipython b/myenv/bin/ipython deleted file mode 100755 index d874db4ef6a..00000000000 --- a/myenv/bin/ipython +++ /dev/null @@ -1,8 +0,0 @@ -#!/home/odoo/odoo19/tutorials/myenv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from IPython import start_ipython -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(start_ipython()) diff --git a/myenv/bin/ipython3 b/myenv/bin/ipython3 deleted file mode 100755 index d874db4ef6a..00000000000 --- a/myenv/bin/ipython3 +++ /dev/null @@ -1,8 +0,0 @@ -#!/home/odoo/odoo19/tutorials/myenv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from IPython import start_ipython -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(start_ipython()) diff --git a/myenv/bin/pip b/myenv/bin/pip deleted file mode 100755 index 9ff3b056db9..00000000000 --- a/myenv/bin/pip +++ /dev/null @@ -1,8 +0,0 @@ -#!/home/odoo/odoo19/tutorials/myenv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from pip._internal.cli.main import main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(main()) diff --git a/myenv/bin/pip3 b/myenv/bin/pip3 deleted file mode 100755 index 9ff3b056db9..00000000000 --- a/myenv/bin/pip3 +++ /dev/null @@ -1,8 +0,0 @@ -#!/home/odoo/odoo19/tutorials/myenv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from pip._internal.cli.main import main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(main()) diff --git a/myenv/bin/pip3.12 b/myenv/bin/pip3.12 deleted file mode 100755 index 9ff3b056db9..00000000000 --- a/myenv/bin/pip3.12 +++ /dev/null @@ -1,8 +0,0 @@ -#!/home/odoo/odoo19/tutorials/myenv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from pip._internal.cli.main import main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(main()) diff --git a/myenv/bin/pygmentize b/myenv/bin/pygmentize deleted file mode 100755 index 0b3394b5d0c..00000000000 --- a/myenv/bin/pygmentize +++ /dev/null @@ -1,8 +0,0 @@ -#!/home/odoo/odoo19/tutorials/myenv/bin/python3 -# -*- coding: utf-8 -*- -import re -import sys -from pygments.cmdline import main -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - sys.exit(main()) diff --git a/myenv/bin/python b/myenv/bin/python deleted file mode 120000 index b8a0adbbb97..00000000000 --- a/myenv/bin/python +++ /dev/null @@ -1 +0,0 @@ -python3 \ No newline at end of file diff --git a/myenv/bin/python3 b/myenv/bin/python3 deleted file mode 120000 index ae65fdaa129..00000000000 --- a/myenv/bin/python3 +++ /dev/null @@ -1 +0,0 @@ -/usr/bin/python3 \ No newline at end of file diff --git a/myenv/bin/python3.12 b/myenv/bin/python3.12 deleted file mode 120000 index b8a0adbbb97..00000000000 --- a/myenv/bin/python3.12 +++ /dev/null @@ -1 +0,0 @@ -python3 \ No newline at end of file diff --git a/myenv/lib64 b/myenv/lib64 deleted file mode 120000 index 7951405f85a..00000000000 --- a/myenv/lib64 +++ /dev/null @@ -1 +0,0 @@ -lib \ No newline at end of file diff --git a/myenv/pyvenv.cfg b/myenv/pyvenv.cfg deleted file mode 100644 index c3fe9dc187d..00000000000 --- a/myenv/pyvenv.cfg +++ /dev/null @@ -1,5 +0,0 @@ -home = /usr/bin -include-system-site-packages = false -version = 3.12.3 -executable = /usr/bin/python3.12 -command = /usr/bin/python3 -m venv /home/odoo/odoo19/tutorials/myenv diff --git a/myenv/share/man/man1/ipython.1 b/myenv/share/man/man1/ipython.1 deleted file mode 100644 index 0f4a191f3fa..00000000000 --- a/myenv/share/man/man1/ipython.1 +++ /dev/null @@ -1,60 +0,0 @@ -.\" Hey, EMACS: -*- nroff -*- -.\" First parameter, NAME, should be all caps -.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection -.\" other parameters are allowed: see man(7), man(1) -.TH IPYTHON 1 "July 15, 2011" -.\" Please adjust this date whenever revising the manpage. -.\" -.\" Some roff macros, for reference: -.\" .nh disable hyphenation -.\" .hy enable hyphenation -.\" .ad l left justify -.\" .ad b justify to both left and right margins -.\" .nf disable filling -.\" .fi enable filling -.\" .br insert line break -.\" .sp insert n+1 empty lines -.\" for manpage-specific macros, see man(7) and groff_man(7) -.\" .SH section heading -.\" .SS secondary section heading -.\" -.\" -.\" To preview this page as plain text: nroff -man ipython.1 -.\" -.SH NAME -ipython \- Tools for Interactive Computing in Python. -.SH SYNOPSIS -.B ipython -.RI [ options ] " files" ... - -.B ipython subcommand -.RI [ options ] ... - -.SH DESCRIPTION -An interactive Python shell with automatic history (input and output), dynamic -object introspection, easier configuration, command completion, access to the -system shell, integration with numerical and scientific computing tools, -web notebook, Qt console, and more. - -For more information on how to use IPython, see 'ipython \-\-help', -or 'ipython \-\-help\-all' for all available command\(hyline options. - -.SH "ENVIRONMENT VARIABLES" -.sp -.PP -\fIIPYTHONDIR\fR -.RS 4 -This is the location where IPython stores all its configuration files. The default -is $HOME/.ipython if IPYTHONDIR is not defined. - -You can see the computed value of IPYTHONDIR with `ipython locate`. - -.SH FILES - -IPython uses various configuration files stored in profiles within IPYTHONDIR. -To generate the default configuration files and start configuring IPython, -do 'ipython profile create', and edit '*_config.py' files located in -IPYTHONDIR/profile_default. - -.SH AUTHORS -IPython is written by the IPython Development Team . From f174915d47aedddf0317849422c01bab37c91980 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Tue, 19 May 2026 18:42:54 +0530 Subject: [PATCH 06/25] [IMP] estate: add menu, action, list and form views - Added estate_menu.xml and connected menu with action - Added estate_property_views.xml with list and form views - Learned basic structure of Odoo views and actions - Completed Chapter 5 and understood form/list view concepts - Practiced menu >action >view connection flow --- estate/__manifest__.py | 3 +- estate/models/estate_property.py | 21 +++++++-- estate/views/estate_menus.xml | 23 ++++++++++ estate/views/estate_property_views.xml | 59 ++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 estate/views/estate_menus.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index ca43f1f5527..6fc4a136508 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,7 +10,8 @@ "depends": ["base"], "data":[ "security/ir.model.access.csv", - "views/estate_property_views.xml" + "views/estate_property_views.xml", + "views/estate_menus.xml" ], "license":"LGPL-3", "application": True, diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 71563ff554f..a5137f4382f 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,3 +1,4 @@ +from dateutil.relativedelta import relativedelta from odoo import models, fields class EstateProperty(models.Model): @@ -6,10 +7,12 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date( + default=lambda self: #without lambda date would be fixed permanently + fields.Date.today() + relativedelta(months=3)) expected_price = fields.Float() - selling_price = fields.Float(required=True) - 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() @@ -21,5 +24,17 @@ class EstateProperty(models.Model): ('east', 'East'), ('west', 'West'), ]) + active = fields.Boolean(default=True) + state = fields.Selection([ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('canceled', 'Canceled'), + ], + required=True, + copy=False, + default='new' + ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..1401281708a --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index f3928274bc5..779af2c0be6 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,11 +1,70 @@ + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +
+

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ Properties estate.property list,form + +
From b5f0beda76d6f96f58ddca87d7c3eddf069f5f3d Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Thu, 21 May 2026 18:32:45 +0530 Subject: [PATCH 07/25] [IMP] estate: add form and search views for properties - Added custom search view for estate properties - Implemented search fields for property filtering - Added predefined filters and group by functionality - Practiced and understood search views, filters, domains, and group by concepts from Chapter 6 exercise --- estate/views/estate_property_views.xml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 779af2c0be6..9622aeca21e 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,6 +1,5 @@ - estate.property.list @@ -49,6 +48,8 @@ + + @@ -57,6 +58,29 @@ + + + estate.property.search + estate.property + + + + + + + + + + + + + + + Properties From 4ef1bc9dc38fbae92e9f86857380b562a12ea4fa Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Fri, 22 May 2026 18:24:29 +0530 Subject: [PATCH 08/25] [IMP] estate: revise previous chapters and begin relational fields - Started Chapter 7 on relational fields - Revised earlier Odoo tutorial chapters - Reviewed models, views, ORM, and field concepts --- estate/views/estate_property_views.xml | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 9622aeca21e..ff8701c55a6 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -27,17 +27,18 @@

+ - - - - - - - - + + - + + + + + + + @@ -52,7 +53,9 @@ - + + + @@ -75,8 +78,7 @@ domain="['|',('state','=','new'),('state','=','offer_received')]"/> - + context="{'group_by':'postcode'}"/>
@@ -87,8 +89,6 @@ estate.property list,form - -
From d4785843ca12f4f00c894b73db226299c8200b7d Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Mon, 25 May 2026 18:34:36 +0530 Subject: [PATCH 09/25] [IMP] estate: improve code formatting and begin Many2one relations - Fixed code formatting issues and removed unnecessary whitespace - Cleaned up XML and Python structure for better readability - Started learning relational fields (Many2one) - Began exercises from the chapter on Many2one fields --- estate/__init__.py | 1 - estate/__manifest__.py | 14 ++++----- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 35 +++++++++++---------- estate/models/estate_property_type.py | 7 +++++ estate/security/ir.model.access.csv | 1 + estate/views/estate_menus.xml | 28 ++++++++++++----- estate/views/estate_property_type_views.xml | 9 ++++++ estate/views/estate_property_views.xml | 1 - 9 files changed, 64 insertions(+), 34 deletions(-) create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__init__.py b/estate/__init__.py index 899bcc97f0f..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,2 +1 @@ from . import models - diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 6fc4a136508..e142a7da2ea 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,20 +1,18 @@ { - "name": "real estate", "version": "1.0", "summary": "Estate management", "description": "Estate management", "author": "Odoo S.A.", - "website": "https://www.odoo.com/", - "category": "Uncategorized", + "category": "tutorials", "depends": ["base"], "data":[ "security/ir.model.access.csv", - "views/estate_property_views.xml", - "views/estate_menus.xml" + "views/estate_property_views.xml", + "views/estate_property_type_views.xml", + "views/estate_menus.xml" ], - "license":"LGPL-3", + "license": "LGPL-3", "application": True, - "installable":True + "installable": True } - diff --git a/estate/models/__init__.py b/estate/models/__init__.py index c5006b18cf8..40092a2d810 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,2 +1,2 @@ from . import estate_property - +from . import estate_property_type diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index a5137f4382f..8492865ab25 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ from dateutil.relativedelta import relativedelta -from odoo import models, fields +from odoo import fields, models + class EstateProperty(models.Model): _name = "estate.property" @@ -8,10 +9,13 @@ class EstateProperty(models.Model): description = fields.Text() postcode = fields.Char() date_availability = fields.Date( - default=lambda self: #without lambda date would be fixed permanently - fields.Date.today() + relativedelta(months=3)) + default=lambda self: ( + # Without lambda, date would be fixed permanently + fields.Date.today() + relativedelta(months=3) + ) + ) expected_price = fields.Float() - 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() @@ -25,16 +29,15 @@ class EstateProperty(models.Model): ('west', 'West'), ]) active = fields.Boolean(default=True) - state = fields.Selection([ - ('new', 'New'), - ('offer_received', 'Offer Received'), - ('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'), - ('canceled', 'Canceled'), - ], - required=True, - copy=False, - default='new' + state = fields.Selection( + [ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('canceled', 'Canceled'), + ], + required=True, + copy=False, + default='new' ) - - diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..823abc8981d --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Property Types" + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index d48cf7145c0..eff4c41cdda 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,3 +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 +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 1401281708a..f2c9d96faa0 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -4,20 +4,34 @@ id="estate_menu_root" name="Real Estate" /> - + - + - - - - + /> + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..18d992f8b4a --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,9 @@ + + + + + Property Types + estate.property.type + list,form + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index ff8701c55a6..37cf7bd1176 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -91,4 +91,3 @@ - From 95dccd68f2daa2d67b9c4527501cdc429feccbef Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Wed, 27 May 2026 18:46:06 +0530 Subject: [PATCH 10/25] [IMP] estate: learn and implement Many2one relations - Studied Many2one field behavior and relationships - Explored res.partner and res.users models - Started implementing Many2one field exercises in estate module - Improved understanding of relational fields in Odoo --- estate/__init__.py | 1 + estate/__manifest__.py | 3 +- estate/models/__init__.py | 1 + estate/models/estate_property.py | 10 ++- estate/models/estate_property_type.py | 2 + estate/views/estate_property_views.xml | 113 +++++++++++++------------ 6 files changed, 74 insertions(+), 56 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 0650744f6bc..899bcc97f0f 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,2 @@ from . import models + diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e142a7da2ea..602e6b2832f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,7 +6,7 @@ "author": "Odoo S.A.", "category": "tutorials", "depends": ["base"], - "data":[ + "data": [ "security/ir.model.access.csv", "views/estate_property_views.xml", "views/estate_property_type_views.xml", @@ -16,3 +16,4 @@ "application": True, "installable": True } + diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 40092a2d810..ced0e03ef07 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,2 +1,3 @@ from . import estate_property from . import estate_property_type + diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8492865ab25..8679aca7643 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,10 +1,12 @@ from dateutil.relativedelta import relativedelta + from odoo import fields, models class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" + name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() @@ -15,7 +17,7 @@ class EstateProperty(models.Model): ) ) expected_price = fields.Float() - 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() @@ -35,9 +37,13 @@ class EstateProperty(models.Model): ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), - ('canceled', 'Canceled'), + ('canceled', 'Canceled') ], required=True, copy=False, default='new' ) + buyer_id = fields.Many2one("res.partner",string="buyer",copy=False) + salesperson_id = fields.Many2one("res.users",string="salesperson",default=lambda self:self.env.user) + property_id= fields.Many2one("estate.property.type",string="Property Type") + diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 823abc8981d..cb68e52b893 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -4,4 +4,6 @@ class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "Property Types" + name = fields.Char(required=True) + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 37cf7bd1176..3e4571d2d08 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,23 +1,23 @@ - - + estate.property.list estate.property - - - - - - - - + + + + + + + + + - - - + + estate.property.form estate.property @@ -25,69 +25,76 @@
-

+

+ +

+ - - + + + - - + + - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + -
+
- + estate.property.search estate.property - - - - - - - - + + + + + + + + - + Properties estate.property list,form - - -
+ + \ No newline at end of file From 5b00f3d587eeb3afc17bfeda73b4b6a0d2112fed Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Thu, 28 May 2026 18:34:45 +0530 Subject: [PATCH 11/25] [IMP] estate: Understand Many2many field behavior and usage - Learned Many2many stores recordsets (multiple linked records) - Understood comodel_name defines target model relationship - Learned optional relation table and column mapping behavior - Understood ORM auto-generates relation table if not specified --- estate/models/estate_property.py | 33 ++++++++++++++++++-------- estate/views/estate_property_views.xml | 4 ++-- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8679aca7643..670ef5b206d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -24,12 +24,14 @@ class EstateProperty(models.Model): garage = fields.Boolean() garden = fields.Boolean() garden_area = fields.Integer() - garden_orientation = fields.Selection([ - ('north', 'North'), - ('south', 'South'), - ('east', 'East'), - ('west', 'West'), - ]) + garden_orientation = fields.Selection( + [ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ] + ) active = fields.Boolean(default=True) state = fields.Selection( [ @@ -37,13 +39,24 @@ class EstateProperty(models.Model): ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), - ('canceled', 'Canceled') + ('canceled', 'Canceled'), ], required=True, copy=False, default='new' ) - buyer_id = fields.Many2one("res.partner",string="buyer",copy=False) - salesperson_id = fields.Many2one("res.users",string="salesperson",default=lambda self:self.env.user) - property_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.user, + ) + property_type_id= fields.Many2one( + "estate.property.type", + string="Property Type", + ) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 3e4571d2d08..f5d2a355cee 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -12,7 +12,7 @@ - + @@ -32,7 +32,7 @@ - + From 529878661400ef36f059219715f0cb575d884543 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Fri, 29 May 2026 18:12:29 +0530 Subject: [PATCH 12/25] [IMP] estate: implement Many2many tags for properties - Added estate.property.tag model estate_property_tag.py - Created views for property tags list and form views - Added menu and action for Property Tags in Estate module --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 11 ++++++- estate/models/estate_property_tag.py | 9 ++++++ estate/security/ir.model.access.csv | 1 + estate/views/estate_menus.xml | 6 ++++ estate/views/estate_property_tag_views.xml | 34 ++++++++++++++++++++++ estate/views/estate_property_views.xml | 4 +++ 8 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/views/estate_property_tag_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 602e6b2832f..7af076b7a3a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,7 @@ "security/ir.model.access.csv", "views/estate_property_views.xml", "views/estate_property_type_views.xml", + "views/estate_property_tag_views.xml", "views/estate_menus.xml" ], "license": "LGPL-3", diff --git a/estate/models/__init__.py b/estate/models/__init__.py index ced0e03ef07..088326293bd 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,3 +1,4 @@ from . import estate_property from . import estate_property_type +from . import estate_property_tag diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 670ef5b206d..33f248c3621 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -55,8 +55,17 @@ class EstateProperty(models.Model): string="salesperson", default=lambda self: self.env.user, ) - property_type_id= fields.Many2one( + property_type_id = fields.Many2one( "estate.property.type", string="Property Type", ) + tag_ids = fields.Many2many( + "estate.property.tag", + string="Tags", + ) + # def test_tags(self): + # breakpoint() + + # for tag in self.tag_ids: + # print(tag.name) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..5ed1ffcad99 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,9 @@ +from odoo import fields, models + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Property Tags" + + name = fields.Char(required=True) + + diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index eff4c41cdda..8be0b030a67 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,4 +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 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index f2c9d96faa0..b57f10101ed 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -34,4 +34,10 @@ parent="estate_property_type_menu" action="estate_property_type_action" /> + \ No newline at end of file diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..22a0f78c3f6 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,34 @@ + + + + + estate.property.tag.view.list + estate.property.tag + + + + + + + + + estate.property.tag.view.form + estate.property.tag + +
+ + + + + +
+
+
+ + + Property Tags + estate.property.tag + list,form + + +
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index f5d2a355cee..5e784bce25a 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -13,6 +13,7 @@ + @@ -28,6 +29,9 @@

+ + + From 5c6534e5138d141076fc815ad6bf37b006eb4783 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Tue, 2 Jun 2026 18:17:49 +0530 Subject: [PATCH 13/25] [IMP] estate: add page for property offer Allows users to view and manage all buyer offers in one place, making it easier to compare prices and track offer status. Improves decision-making and overall property management experience. Chapter 7: Relations between model : One2many --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 10 +++---- estate/models/estate_property_offer.py | 24 +++++++++++++++ estate/models/estate_property_tag.py | 2 +- estate/security/ir.model.access.csv | 1 + estate/views/estate_property_offer_views.xml | 31 ++++++++++++++++++++ estate/views/estate_property_views.xml | 6 +++- 8 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/views/estate_property_offer_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 7af076b7a3a..acde2a61ad4 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -11,6 +11,7 @@ "views/estate_property_views.xml", "views/estate_property_type_views.xml", "views/estate_property_tag_views.xml", + "views/estate_property_offer_views.xml", "views/estate_menus.xml" ], "license": "LGPL-3", diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 088326293bd..9752b464357 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,5 @@ 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 33f248c3621..ecb3abfe1ce 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -63,9 +63,9 @@ class EstateProperty(models.Model): "estate.property.tag", string="Tags", ) - # def test_tags(self): - # breakpoint() - - # for tag in self.tag_ids: - # print(tag.name) + offer_ids = fields.One2many( + "estate.property.offer", + "property_id", + string="Offer", + ) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..cb1262d40a5 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,24 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Property Offers" + + price = fields.Float() + status = fields.Selection( + [ + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ], + copy=False, + ) + partner_id = fields.Many2one( + 'res.partner', + required=True, + ) + property_id = fields.Many2one( + 'estate.property', + required=True, + ) + diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 5ed1ffcad99..7857688c5f2 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,9 +1,9 @@ from odoo import fields, models + class EstatePropertyTag(models.Model): _name = "estate.property.tag" _description = "Property Tags" name = fields.Char(required=True) - diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 8be0b030a67..c097c4b42cb 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -2,4 +2,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_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..ea0fe4c6b90 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,31 @@ + + + + estate.property.offer.list.view + estate.property.offer + + + + + + + + + + + estate.property.offer.form.view + estate.property.offer + +
+ + + + + + + +
+
+
+ +
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5e784bce25a..07b6eb1739d 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -63,6 +63,10 @@
+ + + + @@ -82,7 +86,7 @@ estate.property - + From 3b4ac756844badbb26f0ad56d7c85685e6cfba30 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Thu, 4 Jun 2026 17:58:37 +0530 Subject: [PATCH 14/25] [IMP] estate: implement computed total_area field - Started learning computed fields and completed the first exercise - Added total_area field in estate_property.py - Computed total_area from living_area and garden_area using a compute method Chapter 8: Computed fields and onChanges --- estate/models/estate_property.py | 9 ++++++++- estate/views/estate_property_views.xml | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ecb3abfe1ce..90f49a29206 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,6 @@ from dateutil.relativedelta import relativedelta -from odoo import fields, models +from odoo import api, fields, models class EstateProperty(models.Model): @@ -24,6 +24,9 @@ class EstateProperty(models.Model): garage = fields.Boolean() garden = fields.Boolean() garden_area = fields.Integer() + total_area = fields.Char( + compute='_compute_total' + ) garden_orientation = fields.Selection( [ ('north', 'North'), @@ -68,4 +71,8 @@ class EstateProperty(models.Model): "property_id", string="Offer", ) + @api.depends('living_area','garden_area') + def _compute_total(self): + for record in self: + record.total_area = record.living_area + record.garden_area diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 07b6eb1739d..c8f4731b665 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -60,6 +60,7 @@ + From 6304e1355af093589a389dc6b4cb113a6bdcb348 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Fri, 5 Jun 2026 18:20:37 +0530 Subject: [PATCH 15/25] [IMP] estate: compute best offer using mapped() - Added best_offer field on estate.property - Implemented compute method using mapped() to determine highest offer price - Updated dependencies on offer_ids.price for automatic recomputation --- estate/__init__.py | 1 - estate/__manifest__.py | 1 - estate/models/__init__.py | 1 - estate/models/estate_property.py | 18 +++++++++++++++--- estate/models/estate_property_offer.py | 1 - estate/models/estate_property_tag.py | 1 - estate/models/estate_property_type.py | 1 - estate/security/ir.model.access.csv | 1 - estate/views/estate_property_views.xml | 4 ++-- 9 files changed, 17 insertions(+), 12 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 899bcc97f0f..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,2 +1 @@ from . import models - diff --git a/estate/__manifest__.py b/estate/__manifest__.py index acde2a61ad4..3a5ff7b0dab 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -18,4 +18,3 @@ "application": True, "installable": True } - diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 9752b464357..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,4 +2,3 @@ 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 90f49a29206..8d0bc2adc22 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -24,8 +24,12 @@ class EstateProperty(models.Model): garage = fields.Boolean() garden = fields.Boolean() garden_area = fields.Integer() - total_area = fields.Char( - compute='_compute_total' + total_area = fields.Integer( + compute = '_compute_total' + ) + best_offer = fields.Integer( + string = 'Best Offer', + compute = '_compute_best_offer' ) garden_orientation = fields.Selection( [ @@ -71,8 +75,16 @@ class EstateProperty(models.Model): "property_id", string="Offer", ) - @api.depends('living_area','garden_area') + + @api.depends('living_area', 'garden_area') def _compute_total(self): for record in self: record.total_area = record.living_area + record.garden_area + @api.depends('offer_ids.price') + def _compute_best_offer(self): + for record in self: + if record.offer_ids: + record.best_offer = max(record.offer_ids.mapped('price')) + else: + record.best_offer = 0 diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index cb1262d40a5..6fdd4e8f830 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -21,4 +21,3 @@ class EstatePropertyOffer(models.Model): 'estate.property', required=True, ) - diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 7857688c5f2..f32702160d7 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -6,4 +6,3 @@ class EstatePropertyTag(models.Model): _description = "Property Tags" name = fields.Char(required=True) - diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index cb68e52b893..b4273cf35df 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -6,4 +6,3 @@ class EstatePropertyType(models.Model): _description = "Property Types" name = fields.Char(required=True) - diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index c097c4b42cb..89f97c50842 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -3,4 +3,3 @@ access_estate_property,access_estate_property,model_estate_property,base.group_u 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_property_views.xml b/estate/views/estate_property_views.xml index c8f4731b665..f5509ad2ba7 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -44,6 +44,7 @@ +
@@ -93,8 +94,7 @@ - + From 97c0965c592941ac2c0b73a4d968340d63e5bb1e Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Mon, 8 Jun 2026 18:32:21 +0530 Subject: [PATCH 16/25] [IMP] estate: implement offer validity and property onchange logic - Added validity and date_deadline fields to estate.property.offer - Implemented computed date_deadline field with @api.depends - Added inverse method to recalculate validity from deadline date - Added garden onchange in estate.property to auto-set garden area and orientation - Configured default values and field synchronization for offer deadlines --- estate/models/estate_property.py | 15 ++++++-- estate/models/estate_property_offer.py | 40 +++++++++++++++++++- estate/views/estate_property_offer_views.xml | 4 ++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8d0bc2adc22..4532e961cfd 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -25,11 +25,11 @@ class EstateProperty(models.Model): garden = fields.Boolean() garden_area = fields.Integer() total_area = fields.Integer( - compute = '_compute_total' + compute='_compute_total' ) best_offer = fields.Integer( - string = 'Best Offer', - compute = '_compute_best_offer' + string='Best Offer', + compute='_compute_best_offer' ) garden_orientation = fields.Selection( [ @@ -88,3 +88,12 @@ def _compute_best_offer(self): record.best_offer = max(record.offer_ids.mapped('price')) else: record.best_offer = 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 = False diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 6fdd4e8f830..05ced6858d3 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 dateutil.relativedelta import relativedelta + +from odoo import api, fields, models class EstatePropertyOffer(models.Model): @@ -21,3 +23,39 @@ class EstatePropertyOffer(models.Model): 'estate.property', required=True, ) + validity = fields.Integer(default=7) + date_deadline = fields.Date( + compute='_compute_date_deadline', + inverse='_inverse_date_deadline', + store=True, + ) + + @api.depends('create_date', 'validity') + def _compute_date_deadline(self): + for record in self: + if record.create_date: + record.date_deadline = ( + record.create_date.date() + + relativedelta(days=record.validity) + ) + else: + record.date_deadline = ( + fields.Date.today() + + relativedelta(days=record.validity) + ) + + def _inverse_date_deadline(self): + for record in self: + if record.date_deadline: + if record.create_date: + delta = ( + record.date_deadline + - record.create_date.date() + ) + record.validity = delta.days + else: + delta = ( + record.date_deadline + - fields.Date.today() + ) + record.validity = delta.days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index ea0fe4c6b90..b1a95e8c29f 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -8,6 +8,8 @@ + + @@ -22,6 +24,8 @@ + + From 6fa7566a0aad6ae6deb87e70d3fa053ff4cf885f Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Tue, 9 Jun 2026 18:45:09 +0530 Subject: [PATCH 17/25] [IMP] estate: implement property and offer state actions - Added Cancel and Sold actions on estate.property with state transition validation - Prevented cancelled properties from being sold and sold properties from being cancelled using UserError - Added Accept and Refuse actions on estate.property.offer - Implemented offer status updates and corresponding form view buttons with icons --- estate/__init__.py | 1 + estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 18 +++++++++++++++++- estate/models/estate_property_offer.py | 1 + estate/models/estate_property_tag.py | 1 + estate/models/estate_property_type.py | 1 + estate/security/ir.model.access.csv | 1 + estate/views/estate_property_views.xml | 7 ++++++- 9 files changed, 30 insertions(+), 2 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 0650744f6bc..899bcc97f0f 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,2 @@ from . import models + diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 3a5ff7b0dab..acde2a61ad4 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -18,3 +18,4 @@ "application": True, "installable": True } + diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 2f1821a39c1..9752b464357 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,3 +2,4 @@ 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 4532e961cfd..ef120804e6d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,7 @@ from dateutil.relativedelta import relativedelta from odoo import api, fields, models +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -93,7 +94,22 @@ def _compute_best_offer(self): def _onchange_garden(self): if self.garden: self.garden_area = 10 - self.garden_orientation =' north' + self.garden_orientation = 'north' else: self.garden_area = 0 self.garden_orientation = False + + def action_sold(self): + for record in self: + if record.state == 'canceled': + raise UserError("Canceled Property cannot be sold") + record.state = "sold" + return True + + def action_cancel(self): + for record in self: + if record.state == 'sold': + raise UserError("Sold Property cannot be canceled") + record.state = "canceled" + return True + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 05ced6858d3..cc5a22fd8ed 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -59,3 +59,4 @@ def _inverse_date_deadline(self): - fields.Date.today() ) record.validity = delta.days + diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index f32702160d7..7857688c5f2 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -6,3 +6,4 @@ class EstatePropertyTag(models.Model): _description = "Property Tags" name = fields.Char(required=True) + diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index b4273cf35df..cb68e52b893 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -6,3 +6,4 @@ class EstatePropertyType(models.Model): _description = "Property Types" name = fields.Char(required=True) + diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 89f97c50842..c097c4b42cb 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -3,3 +3,4 @@ access_estate_property,access_estate_property,model_estate_property,base.group_u 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_property_views.xml b/estate/views/estate_property_views.xml index f5509ad2ba7..0acbcce732d 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -24,6 +24,10 @@ estate.property
+
+

@@ -94,7 +98,8 @@ - + From 947e31e665cea49072a1b94918f0dae4c38f0611 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Thu, 11 Jun 2026 18:44:53 +0530 Subject: [PATCH 18/25] [IMP] estate: implement SQL constraints for data integrity - Added SQL constraints in estate models: - property: expected_price > 0, selling_price >= 0 - offer: price > 0 - tag: UNIQUE(name) - property type: UNIQUE(name --- estate/models/estate_property.py | 20 +++++++++++++----- estate/models/estate_property_offer.py | 22 +++++++++++++++++++- estate/models/estate_property_tag.py | 6 +++++- estate/models/estate_property_type.py | 6 +++++- estate/views/estate_property_offer_views.xml | 4 ++++ 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ef120804e6d..6f012665a0d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,8 +1,8 @@ from dateutil.relativedelta import relativedelta -from odoo import api, fields, models -from odoo.exceptions import UserError - +from odoo import api, fields, models # pylint: disable=import-error +from odoo.exceptions import UserError # pylint: disable=import-error +from odoo.exceptions import ValidationError # pylint: disable=import-error class EstateProperty(models.Model): _name = "estate.property" @@ -28,7 +28,7 @@ class EstateProperty(models.Model): total_area = fields.Integer( compute='_compute_total' ) - best_offer = fields.Integer( + best_offer = fields.Float( string='Best Offer', compute='_compute_best_offer' ) @@ -99,13 +99,23 @@ def _onchange_garden(self): self.garden_area = 0 self.garden_orientation = False + _check_expected_price = models.Constraint( + 'CHECK(expected_price>0)', + 'Expected price must be strictly positive' + ) + + _check_selling_price = models.Constraint( + 'CHECK(selling_price>0)', + 'Selling price must be positive' + ) + def action_sold(self): for record in self: if record.state == 'canceled': raise UserError("Canceled Property cannot be sold") record.state = "sold" return True - + def action_cancel(self): for record in self: if record.state == 'sold': diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index cc5a22fd8ed..a9c8db3c430 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,8 @@ from dateutil.relativedelta import relativedelta -from odoo import api, fields, models +from odoo import api, fields, models # pylint: disable=import-error +from odoo.exceptions import UserError # pylint: disable=import-error +from odoo.exceptions import ValidationError # pylint: disable=import-error class EstatePropertyOffer(models.Model): @@ -60,3 +62,21 @@ def _inverse_date_deadline(self): ) record.validity = delta.days + _check_price = models.Constraint( + 'CHECK(price>0)', + 'Price must be positive' + ) + + def action_confirm(self): + for offer in self.property_id.offer_ids: + if self != offer and offer.status == 'accepted': + raise UserError("An offer is already accepted") + self.status = "accepted" + self.property_id.selling_price = self.price + self.property_id.buyer_id = self.partner_id + return True + + def action_refuse(self): + for record in self: + record.status = "refused" + return True \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 7857688c5f2..6e252b97669 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import fields, models # pylint: disable=import-error class EstatePropertyTag(models.Model): @@ -6,4 +6,8 @@ class EstatePropertyTag(models.Model): _description = "Property Tags" name = fields.Char(required=True) + _unique_price = models.Constraint( + 'UNIQUE(name)', + 'Property tag must be unique' + ) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index cb68e52b893..9f4b88c5d43 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import fields, models # pylint: disable=import-error class EstatePropertyType(models.Model): @@ -6,4 +6,8 @@ class EstatePropertyType(models.Model): _description = "Property Types" name = fields.Char(required=True) + _unique_name = models.Constraint( + 'UNIQUE(name)', + 'Property type name must be unique.' + ) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index b1a95e8c29f..80071b31ee7 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -7,6 +7,10 @@ +

- +
- + - + @@ -87,8 +86,6 @@
- estate.property.search estate.property @@ -107,10 +104,9 @@
- Properties estate.property - list,form + - \ No newline at end of file + From 453fb8c7a03a8de35fc54c84afc29585f82dfd96 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Mon, 22 Jun 2026 18:37:10 +0530 Subject: [PATCH 21/25] [IMP] estate: improve views, filters and business rules - Added invisible rules for garden fields and offer buttons - Restricted offer creation based on property state (readonly) - Made list views editable for property, offer and tags - Updated list view fields (optional date_availability, hidden by default) - Added decorations for property and offer states - Set default 'Available' filter and updated living area search domain --- estate/models/estate_property.py | 28 +++++++++----------- estate/models/estate_property_offer.py | 23 +++++++--------- estate/models/estate_property_tag.py | 2 +- estate/models/estate_property_type.py | 12 ++++++++- estate/views/estate_property_offer_views.xml | 8 +++--- estate/views/estate_property_tag_views.xml | 2 +- estate/views/estate_property_views.xml | 21 ++++++++------- 7 files changed, 52 insertions(+), 44 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 6c9ad94a8a1..f04801f4427 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,12 +1,12 @@ from dateutil.relativedelta import relativedelta from odoo import api, fields, models, _ -from odoo.exceptions import UserError -from odoo.exceptions import ValidationError +from odoo.exceptions import UserError, ValidationError from odoo.tools.float_utils import float_compare, float_is_zero class EstateProperty(models.Model): + _name = "estate.property" _description = "Real Estate Property" _order = "id desc" @@ -32,20 +32,20 @@ class EstateProperty(models.Model): string="Best Offer", compute="_compute_best_offer") garden_orientation = fields.Selection( [ - ("north", "North"), - ("south", "South"), - ("east", "East"), - ("west", "West"), + ('north', "North"), + ('south', "South"), + ('east', "East"), + ('west', "West"), ] ) active = fields.Boolean(default=True) state = fields.Selection( [ - ("new", "New"), - ("offer_received", "Offer Received"), - ("offer_accepted", "Offer Accepted"), - ("sold", "Sold"), - ("canceled", "Canceled"), + ('new', "New"), + ('offer_received', "Offer Received"), + ('offer_accepted', "Offer Accepted"), + ('sold', "Sold"), + ('canceled', "Canceled"), ], required=True, copy=False, @@ -58,7 +58,7 @@ class EstateProperty(models.Model): ) salesperson_id = fields.Many2one( "res.users", - string="salesperson", + string="Salesperson", default=lambda self: self.env.user, ) property_type_id = fields.Many2one( @@ -74,10 +74,6 @@ class EstateProperty(models.Model): "property_id", string="Offer", ) - property_type_id = fields.Many2one( - "estate.property.type", - string="Property Type", - ) @api.depends("living_area", "garden_area") def _compute_total(self): diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 938fc9eaee7..a5957a06012 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,8 +1,7 @@ from dateutil.relativedelta import relativedelta -from odoo import api, fields, models +from odoo import api, fields, models, _ from odoo.exceptions import UserError -from odoo.exceptions import ValidationError class EstatePropertyOffer(models.Model): @@ -13,8 +12,8 @@ class EstatePropertyOffer(models.Model): price = fields.Float() status = fields.Selection( [ - ('accepted', 'Accepted'), - ('refused', 'Refused'), + ('accepted', "Accepted"), + ('refused', "Refused"), ], copy=False, ) @@ -26,6 +25,8 @@ class EstatePropertyOffer(models.Model): 'estate.property', required=True, ) + property_type_id = fields.Many2one( + related="property_id.property_type_id", store=True) validity = fields.Integer(default=7) date_deadline = fields.Date( compute='_compute_date_deadline', @@ -38,13 +39,11 @@ def _compute_date_deadline(self): for record in self: if record.create_date: record.date_deadline = ( - record.create_date.date() - + relativedelta(days=record.validity) + record.create_date.date() + relativedelta(days=record.validity) ) else: record.date_deadline = ( - fields.Date.today() - + relativedelta(days=record.validity) + fields.Date.today() + relativedelta(days=record.validity) ) def _inverse_date_deadline(self): @@ -52,14 +51,12 @@ def _inverse_date_deadline(self): if record.date_deadline: if record.create_date: delta = ( - record.date_deadline - - record.create_date.date() + record.date_deadline - record.create_date.date() ) record.validity = delta.days else: delta = ( - record.date_deadline - - fields.Date.today() + record.date_deadline - fields.Date.today() ) record.validity = delta.days @@ -69,7 +66,7 @@ def _inverse_date_deadline(self): def action_confirm(self): for offer in self.property_id.offer_ids: if self != offer and offer.status == 'accepted': - raise UserError("An offer is already accepted") + raise UserError(_("An offer is already accepted.")) self.status = "accepted" self.property_id.selling_price = self.price self.property_id.buyer_id = self.partner_id diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 1cac4481b4a..db8580171ad 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -9,4 +9,4 @@ class EstatePropertyTag(models.Model): name = fields.Char(required=True) color = fields.Integer() _unique_price = models.Constraint( - "UNIQUE(name)", "Property tag must be unique") + "UNIQUE(name)", "Property tag must be unique.") diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 33e845c45cb..93ffd963d84 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class EstatePropertyType(models.Model): @@ -12,6 +12,16 @@ class EstatePropertyType(models.Model): "estate.property", "property_type_id", ) + offer_ids = fields.One2many( + "estate.property.offer", + "property_type_id", + ) + offer_count = fields.Integer(compute="_computed_offer_count") _unique_name = models.Constraint( "UNIQUE(name)", "Property type name must be unique." ) + + @api.depends('offer_ids') + def _compute_offer_count(self): + for record in self: + record.offer_count = len(record.offer_ids) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 69b09e97d52..273cbe5726d 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -3,13 +3,15 @@ estate.property.offer.list.view estate.property.offer - + + + +

+ +

+
From 5fac9208ff99d6fe03330ec6fc09966a04008df0 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Thu, 25 Jun 2026 18:29:58 +0530 Subject: [PATCH 23/25] [IMP] estate: implement offer validation and property state logic -Added business logic to CRUD methods -Prevented deletion of properties unless state is 'New' or 'Cancelled' -Updated property state to 'Offer Received' when creating offers -Added validation to prevent lower offers than existing ones -Added property_ids field to users with available properties domain --- estate/models/__init__.py | 1 + estate/models/estate_property.py | 8 ++++++++ estate/models/estate_property_offer.py | 16 ++++++++++++++++ estate/models/res_users.py | 13 +++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 estate/models/res_users.py diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 2f1821a39c1..9a2189b6382 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,3 +2,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index f04801f4427..71321fe8807 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -113,6 +113,14 @@ def _onchange_garden(self): self.garden_area = 0 self.garden_orientation = False + @api.ondelete(at_uninstall=False) + def _unlink_check_state(self): + for record in self: + if record.state not in ('new', 'canceled'): + raise UserError( + _("Only New or Cancelled properties can be deleted")) + return True + _check_expected_price = models.Constraint( "CHECK(expected_price>0)", "Expected price must be strictly positive" ) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index a5957a06012..0a7291caab3 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -63,6 +63,22 @@ def _inverse_date_deadline(self): _check_price = models.Constraint( 'CHECK(price>0)', 'Price must be positive') + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + current_price = vals.get('price') + property_id = self.env['estate.property'].browse( + vals['property_id']) + for offer in property_id.offer_ids: + if current_price < offer.price: + raise UserError( + "The offer must be higher than the current highest offer.") + offers = super().create(vals_list) + for record in offers: + if record.property_id.state == 'new': + record.property_id.state = 'offer_received' + return offers + def action_confirm(self): for offer in self.property_id.offer_ids: if self != offer and offer.status == 'accepted': diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..a9282935325 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,13 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + + _inherit = "res.users" + + property_ids = fields.One2many( + "estate.property", + "salesperson_id", + string="Properties", + domain=[('state', '!=', 'sold')] + ) From b38d057347f3c9b866022ad2a52f03c9d5f8ce8c Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Wed, 1 Jul 2026 10:11:35 +0530 Subject: [PATCH 24/25] [ADD] estate_account: generate invoice on property sold Created the Estate Account module to automatically generate invoices for sold properties by integrating with the invoice functionality. When a property is marked as sold, an invoice is created automatically, reducing manual effort and streamlining the invoicing process for users and administrators. Chapter 13: Interaction with Other Modules --- estate/__manifest__.py | 3 ++- estate/models/estate_property.py | 17 ++++++++-------- estate/models/estate_property_offer.py | 20 +++++++++--------- estate/models/estate_property_tag.py | 1 + estate/models/estate_property_type.py | 1 + estate/models/res_users.py | 3 +-- estate/views/estate_property_views.xml | 7 +------ estate/views/res_users_views.xml | 15 ++++++++++++++ estate_account/__init__.py | 1 + estate_account/__manifest__.py | 6 ++++++ estate_account/models/__init__.py | 1 + estate_account/models/estate_account.py | 27 +++++++++++++++++++++++++ 12 files changed, 74 insertions(+), 28 deletions(-) create mode 100644 estate/views/res_users_views.xml create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_account.py diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 4774dc860a4..ef7d759276a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,7 +12,8 @@ "views/estate_property_offer_views.xml", "views/estate_property_type_views.xml", "views/estate_property_tag_views.xml", - "views/estate_menus.xml" + "views/estate_menus.xml", + "views/res_users_views.xml", ], "license": "LGPL-3", "application": True, diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 71321fe8807..4bcde10474e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -6,7 +6,6 @@ class EstateProperty(models.Model): - _name = "estate.property" _description = "Real Estate Property" _order = "id desc" @@ -75,6 +74,14 @@ class EstateProperty(models.Model): string="Offer", ) + _check_expected_price = models.Constraint( + "CHECK(expected_price>0)", "Expected price must be strictly positive" + ) + + _check_selling_price = models.Constraint( + "CHECK(selling_price>0)", "Selling price must be positive" + ) + @api.depends("living_area", "garden_area") def _compute_total(self): for record in self: @@ -121,14 +128,6 @@ def _unlink_check_state(self): _("Only New or Cancelled properties can be deleted")) return True - _check_expected_price = models.Constraint( - "CHECK(expected_price>0)", "Expected price must be strictly positive" - ) - - _check_selling_price = models.Constraint( - "CHECK(selling_price>0)", "Selling price must be positive" - ) - def action_sold(self): for record in self: if record.state == "canceled": diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 0a7291caab3..af0c8e8366e 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -34,6 +34,9 @@ class EstatePropertyOffer(models.Model): store=True, ) + _check_price = models.Constraint( + 'CHECK(price>0)', 'Price must be positive') + @api.depends('create_date', 'validity') def _compute_date_deadline(self): for record in self: @@ -60,9 +63,6 @@ def _inverse_date_deadline(self): ) record.validity = delta.days - _check_price = models.Constraint( - 'CHECK(price>0)', 'Price must be positive') - @api.model_create_multi def create(self, vals_list): for vals in vals_list: @@ -71,19 +71,19 @@ def create(self, vals_list): vals['property_id']) for offer in property_id.offer_ids: if current_price < offer.price: - raise UserError( - "The offer must be higher than the current highest offer.") - offers = super().create(vals_list) - for record in offers: - if record.property_id.state == 'new': - record.property_id.state = 'offer_received' - return offers + raise UserError(_( + "The offer must be higher than the current highest offer.")) + if property_id.state == 'new': + property_id.state = 'offer_received' + + return super().create(vals_list) def action_confirm(self): for offer in self.property_id.offer_ids: if self != offer and offer.status == 'accepted': raise UserError(_("An offer is already accepted.")) self.status = "accepted" + self.property_id.state = "offer_accepted" self.property_id.selling_price = self.price self.property_id.buyer_id = self.partner_id return True diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index db8580171ad..4dbfe646d9a 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -8,5 +8,6 @@ class EstatePropertyTag(models.Model): name = fields.Char(required=True) color = fields.Integer() + _unique_price = models.Constraint( "UNIQUE(name)", "Property tag must be unique.") diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 842450a3414..b3ec5f8852d 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -17,6 +17,7 @@ class EstatePropertyType(models.Model): "property_type_id", ) offer_count = fields.Integer(compute="_compute_offer_count") + _unique_name = models.Constraint( "UNIQUE(name)", "Property type name must be unique." ) diff --git a/estate/models/res_users.py b/estate/models/res_users.py index a9282935325..5192d3d18da 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -2,12 +2,11 @@ class ResUsers(models.Model): - _inherit = "res.users" property_ids = fields.One2many( "estate.property", "salesperson_id", - string="Properties", + string="My Properties", domain=[('state', '!=', 'sold')] ) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index c6c105fd393..12b00e248c1 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -26,7 +26,7 @@
-
@@ -40,7 +40,6 @@ options="{'color_field': 'color'}" /> - - @@ -68,15 +66,12 @@ -
- - diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..4923a47349e --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,15 @@ + + + + res.users.view.form.inherit.estate + res.users + + + + + + + + + + diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..3cc3d9671e1 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,6 @@ +{ + "name": "Estate Account", + "author": "abkus-odoo", + "license": "LGPL-3", + "depends": ["estate", "account"] +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..02b688798a3 --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_account diff --git a/estate_account/models/estate_account.py b/estate_account/models/estate_account.py new file mode 100644 index 00000000000..26951ddf8c4 --- /dev/null +++ b/estate_account/models/estate_account.py @@ -0,0 +1,27 @@ +from odoo import Command, models + + +class EstateAccount(models.Model): + _inherit = "estate.property" + + def action_sold(self): + + self.env["account.move"].create({ + "partner_id": self.buyer_id.id, + "move_type": "out_invoice", + + "invoice_line_ids": [ + Command.create({ + "name": "6% Commission", + "quantity": 1, + "price_unit": self.selling_price * 0.06, + }), + Command.create({ + "name": "Administrative fees", + "quantity": 1, + "price_unit": 100.00 + }), + ], + }) + + return super().action_sold() From cf6b343c561c7804cd87bfb4384a272840c05916 Mon Sep 17 00:00:00 2001 From: abkus-odoo Date: Sat, 4 Jul 2026 01:25:13 +0530 Subject: [PATCH 25/25] [IMP] estate: enhance property management with Kanban & custom fields Implemented Kanban view for a good visual user experience. Displays conditional fields like selling price upon offer acceptance and best price when offers are received. Adds grouping by property type for better organization and quick filtering. Helps users easily track property status, compare offers, and make faster decisions. Ch. 14: A Brief History of QWeb --- estate/views/estate_property_views.xml | 38 ++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 12b00e248c1..6033ab46ac3 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -20,6 +20,40 @@ + + estate.property.kanban + estate.property + + + + + +
+ + + +
+ Expected Price: + +
+
+ Best Offer: + +
+
+ Selling Price: + +
+
+ +
+
+
+
+
+
+
+ estate.property.form estate.property @@ -77,7 +111,7 @@
-
+
@@ -105,6 +139,6 @@ Properties estate.property - list,form + list,kanban,form