diff --git a/awesome_estate/__init__.py b/awesome_estate/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/awesome_estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/awesome_estate/__manifest__.py b/awesome_estate/__manifest__.py new file mode 100644 index 00000000000..b148b3c9f73 --- /dev/null +++ b/awesome_estate/__manifest__.py @@ -0,0 +1,15 @@ +{ + 'name': 'Awesome Estate', + 'version': '0.1', + 'category': 'Tutorials', + 'summary': 'Real Estate Advertisement tutorial module (empty shell)', + 'author': 'Patja', + 'license': 'LGPL-3', + 'depends': ['base'], + 'data': [ + 'security/ir.model.access.csv', + 'views/awesome_estate_property_views.xml', + ], + 'application': True, + 'installable': True, +} diff --git a/awesome_estate/docs/chapter_2.md b/awesome_estate/docs/chapter_2.md new file mode 100644 index 00000000000..622ee340b0c --- /dev/null +++ b/awesome_estate/docs/chapter_2.md @@ -0,0 +1,25 @@ +# Chapter 2 + +## Module dependency + +`base` + +- My module needs Odoo core to be installed first. + +### Where `base` lives in this repo + +`community/odoo/addons/base/` + +### What `base` provides + +- Fundamental UI framework pieces and security bootstrap. +- Core records like languages, users, partners, currencies, companies, and countries. +- Base security, group, and access basics (Chapter 4). + +### Why my module depends on it + +Without `base`, Odoo is missing the required core models, configuration, and security layer, so my module cannot install safely. + +### Notes + +`application: true` suggests that this is an installable app, and `false` means it is a module. diff --git a/awesome_estate/docs/chapter_3.md b/awesome_estate/docs/chapter_3.md new file mode 100644 index 00000000000..60b4a7d9353 --- /dev/null +++ b/awesome_estate/docs/chapter_3.md @@ -0,0 +1,56 @@ +# How fields are converted to the database + +- `fields.Char` → `varchar` if a size is set, otherwise `text` +- `fields.Text` → `text` +- `fields.Integer` → `int4` (PostgreSQL integer) +- `fields.Float` → `numeric` with precision, or `float8` if no digits are set +- `fields.Boolean` → `bool` +- `fields.Date` → `date` +- `fields.Datetime` → `timestamp` without timezone (UTC) +- `fields.Selection` → `varchar` (stores the internal key string) +- `fields.Many2one` → `int4` (foreign key) +- `fields.Binary` → `bytea` if not attachment-backed, otherwise stored in `ir.attachment` +- `fields.Html` → `text` +- `fields.Monetary` → `numeric` linked to a currency + +--- + +## Blueprint, methods, and required fields + +- `class` = blueprint +- `methods` = functions +- `required=True` translates to `NOT NULL` in SQL + +--- + +## Module namespace vs business concept + +- `awesome_estate` is the module namespace prefix +- `property` is the business concept inside that module + +So the technical model name becomes `awesome_estate.property`. + +--- + +## Selection: key vs label + +- **Key** / internal value stored in the database + - `"north"`, `"south"`, `"east"`, `"west"` + +- **Label** / display value shown in the UI + - `"North"`, `"South"`, `"East"`, `"West"` + +--- + +## Chapter 3 verification + +### 1) Upgrade or install the module +`/home/odoo/odoo19/community/odoo-bin -d patja --addons-path=community/addons,enterprise,tutorials -u awesome_estate --stop-after-init` + +### 2) Check the table and columns +`psql -d patja -c "\pset pager off" -c "\d awesome_estate_property"` + +### 3) Check `required=True` becomes `NOT NULL` +`psql -d patja -c "\pset pager off" -c "SELECT column_name, is_nullable FROM information_schema.columns WHERE table_name='awesome_estate_property' AND column_name IN ('name', 'expected_price');"` + +You should see `is_nullable = NO` for `name` and `expected_price`. diff --git a/awesome_estate/docs/chapter_4.md b/awesome_estate/docs/chapter_4.md new file mode 100644 index 00000000000..b09a86836e9 --- /dev/null +++ b/awesome_estate/docs/chapter_4.md @@ -0,0 +1,93 @@ +# Security + +- module: `awesome_estate` +- model: `awesome_estate.property` +- ACL file: `tutorials/awesome_estate/security/ir.model.access.csv` +- manifest entry: `data: ['security/ir.model.access.csv']` + +If a model has no access rights, Odoo treats it as inaccessible and prints a warning in the logs. + +--- + +1. **Access rights (ACLs)** + Model-level permissions: + - read + - write + - create + - unlink + +2. **Groups** + ACLs are assigned to a group like `base.group_user`. + +3. **Record rules** + Used later to limit which records a group can see or edit. + +For Chapter 4, the important part is ACLs. + +--- + +## ACL file format + +File: `tutorials/awesome_estate/security/ir.model.access.csv` + +```csv +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_awesome_estate_property,access_awesome_estate_property,model_awesome_estate_property,base.group_user,1,1,1,1 +``` + +### What each part means + +- `id` + External ID of the access rule record. + +- `name` + Human-readable name. + +- `model_id:id` + Model the rule applies to. + For `awesome_estate.property`, the value is: + - `model_awesome_estate_property` + +- `group_id:id` + Group that gets the permissions. + Here: + - `base.group_user` + +- `perm_read` + Can read records. + +- `perm_write` + Can edit records. + +- `perm_create` + Can create records. + +- `perm_unlink` + Can delete records. + In Odoo, `unlink` means delete. + +### What this row gives + +This row gives internal users in `base.group_user` full access to the model: + +- read = 1 +- write = 1 +- create = 1 +- unlink = 1 + +--- + +## Manifest wiring + +File: `tutorials/awesome_estate/__manifest__.py` + +```python +'data': ['security/ir.model.access.csv'], +``` + +Why this matters: + +- Odoo only loads security data files if they are declared in the manifest. +- The file is loaded when the module is installed or upgraded. + +--- diff --git a/awesome_estate/docs/chapter_5.md b/awesome_estate/docs/chapter_5.md new file mode 100644 index 00000000000..64a9ef48156 --- /dev/null +++ b/awesome_estate/docs/chapter_5.md @@ -0,0 +1,48 @@ +# Chapter 5 - First UI + +## Action and Menus +- **Action (`ir.actions.act_window`)**: Connects a model to the UI, specifying view modes like `list,form`. +- **Menu Hierarchy**: 3 levels deep: Root Menu -> First Level Menu -> Action Menu. +- **Manifest Order**: XML files containing these UI definitions must be added to `__manifest__.py` under `data`. Data is loaded sequentially! + +## Field Attributes +- `required=True`: Field cannot be empty. Translates to `NOT NULL` in the DB. +- `copy=False`: Prevents the field value from duplicating when a user clicks the "Duplicate" action on a record. Used for unique or situational data like `date_availability` or `selling_price`. +- `readonly=True`: Makes the field uneditable from the UI. E.g., `selling_price` updates programmatically when an offer is accepted, not by manual entry. + +## Default Values +- Pre-populates a field logically when "New" is clicked. +- Can be a literal (`default=2`) or evaluated via an anonymous function. +- **Why use `lambda self:` for logic?**: If you say `default=date.today()`, Python computes it *once* when the Odoo server boots. Using `lambda self: date.today() + relativedelta(months=3)` evaluates dynamically at the *exact moment* the record is created. + +## Reserved Fields +- **`active`**: Special boolean field. If `False`, the record is "Archived" and automatically hidden from standard searches (without deleting DB row). +- **`state`**: Selection field commonly used to drive business flow (e.g., New -> Offer Received -> Sold). + +## Python / Odoo Conventions +- **String quotes (`''` vs `""`)**: Mechanically identical in Python. By Odoo / PEP 8 convention, use single quotes `''` for internal strings (keys, backend values) and double quotes `""` for UI text or docstrings. + +## Selection Fields +Are lists of tuples acting as Key/Value pairs: `('north', 'North')` +- **Key (`'north'`)**: Backend identifier. Lower-case, internal logic, stored in DB. +- **Label (`'North'`)**: UI string. Shown to the user, can be translated easily. + +## Date Imports +- `datetime.date`: Native module for server calendar dates (`date.today()`). +- `dateutil.relativedelta`: Robust utility that cleanly handles calendar leaps when calculating logic like `months=3`. Other periods supported: `years`, `months`, `weeks`, `days`, `hours`. + +## Implementation Proof +All rules required by the Chapter 5 tutorial (readonly/copy overrides, dynamic default date, correctly formatted status options, active field implementation) have been applied exactly to specification in `awesome_estate_property.py`. + +## Developer Setup Notes (`--dev`) +When executing and testing UI/view creations regularly, use the backend server command flag `--dev=all`. It auto-reloads your codebase so you bypass server restarts. +```bash +./odoo-bin -d patja -u awesome_estate --dev=all +``` +**Common `--dev=` parameters:** +- `all`: Enables all developer configurations below. +- `reload`: Automatically bounces the python worker when Python code changes are detected. +- `qweb`: Forces QWeb templates/XML to read directly from disks instead of reading from the database caching engine. Highly recommended when editing views! +- `werkzeug`: Routes exceptions natively to the debug interactive debugger. +- `xml`: Validates XML files are structurally whole before trying to push them to PostgreSQL. + diff --git a/awesome_estate/docs/initial.md b/awesome_estate/docs/initial.md new file mode 100644 index 00000000000..d20f68537eb --- /dev/null +++ b/awesome_estate/docs/initial.md @@ -0,0 +1,45 @@ +# Notes + +## Start Odoo command + +`odoo-bin -d --addons-path=` + +### Breakdown + +- `odoo-bin` starts the Odoo server. +- `-d ` selects which PostgreSQL database to use. +- `--addons-path=` is a comma-separated list of addon folders that Odoo scans. + +### It does + +- Loads already-installed modules. +- Starts the UI and backend services. + +## Upgrade a module + +### Command + +`odoo-bin -d -u --addons-path=` + +### Meaning + +`-u ` reloads the module and applies its model and data changes. + +### It does + +- After changing Python models (ORM), upgrade the module so database schema changes happen. +- After adding security or ACLs, upgrade the module so access rules apply. + +### For me + +`odoo-bin --addons-path=addons,../enterprise/,../tutorials/ -d patja -u awesome_estate` + +## Install a module for the first time + +### Command + +`odoo-bin -d -i --addons-path=` + +### Meaning + +`-i ` installs the module for the first time in that database. diff --git a/awesome_estate/models/__init__.py b/awesome_estate/models/__init__.py new file mode 100644 index 00000000000..2419350f1e9 --- /dev/null +++ b/awesome_estate/models/__init__.py @@ -0,0 +1 @@ +from . import awesome_estate_property diff --git a/awesome_estate/models/awesome_estate_property.py b/awesome_estate/models/awesome_estate_property.py new file mode 100644 index 00000000000..2149635810b --- /dev/null +++ b/awesome_estate/models/awesome_estate_property.py @@ -0,0 +1,47 @@ +from datetime import date + +from dateutil.relativedelta import relativedelta + +from odoo import fields, models + + +class AwesomeEstateProperty(models.Model): + _name = 'awesome.estate.property' + _description = "Real Estate Property" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date( + copy=False, + default=lambda self: date.today() + relativedelta(months=3), + ) + expected_price = fields.Float(required=True) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + active = fields.Boolean(default=True) + state = fields.Selection( + [ + ('new', "New"), + ('offer_received', "Offer Received"), + ('offer_accepted', "Offer Accepted"), + ('sold', "Sold"), + ('cancelled', "Cancelled"), + ], + required=True, + copy=False, + default='new', + ) + garden_orientation = fields.Selection( + [ + ('north', "North"), + ('south', "South"), + ('east', "East"), + ('west', "West"), + ] + ) diff --git a/awesome_estate/security/ir.model.access.csv b/awesome_estate/security/ir.model.access.csv new file mode 100644 index 00000000000..07b5b347867 --- /dev/null +++ b/awesome_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_awesome_estate_property,access_awesome_estate_property,model_awesome_estate_property,base.group_user,1,1,1,1 diff --git a/awesome_estate/views/awesome_estate_property_views.xml b/awesome_estate/views/awesome_estate_property_views.xml new file mode 100644 index 00000000000..0e32b14700c --- /dev/null +++ b/awesome_estate/views/awesome_estate_property_views.xml @@ -0,0 +1,96 @@ + + + + + + awesome.estate.property.list + awesome.estate.property + + + + + + + + + + + + + + + + + + awesome.estate.property.form + awesome.estate.property + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + awesome.estate.property.search + awesome.estate.property + + + + + + + + + + + + + + + + + + + Properties + awesome.estate.property + list,form + + + + + + + +