Skip to content
1 change: 1 addition & 0 deletions awesome_estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
15 changes: 15 additions & 0 deletions awesome_estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -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,
}
25 changes: 25 additions & 0 deletions awesome_estate/docs/chapter_2.md
Original file line number Diff line number Diff line change
@@ -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.
56 changes: 56 additions & 0 deletions awesome_estate/docs/chapter_3.md
Original file line number Diff line number Diff line change
@@ -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`.
93 changes: 93 additions & 0 deletions awesome_estate/docs/chapter_4.md
Original file line number Diff line number Diff line change
@@ -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.

---
48 changes: 48 additions & 0 deletions awesome_estate/docs/chapter_5.md
Original file line number Diff line number Diff line change
@@ -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.

45 changes: 45 additions & 0 deletions awesome_estate/docs/initial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Notes

## Start Odoo command

`odoo-bin -d <db_name> --addons-path=<paths...>`

### Breakdown

- `odoo-bin` starts the Odoo server.
- `-d <db_name>` selects which PostgreSQL database to use.
- `--addons-path=<paths...>` 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 <db_name> -u <module_name> --addons-path=<paths...>`

### Meaning

`-u <module_name>` 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 <db_name> -i <module_name> --addons-path=<paths...>`

### Meaning

`-i <module_name>` installs the module for the first time in that database.
1 change: 1 addition & 0 deletions awesome_estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import awesome_estate_property
47 changes: 47 additions & 0 deletions awesome_estate/models/awesome_estate_property.py
Original file line number Diff line number Diff line change
@@ -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"),
]
)
2 changes: 2 additions & 0 deletions awesome_estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions awesome_estate/views/awesome_estate_property_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<record id='awesome_estate_property_action' model='ir.actions.act_window'>
<field name='name'>Properties</field>
<field name='res_model'>awesome.estate.property</field>
<field name='view_mode'>list,form</field>
</record>

<menuitem id='awesome_estate_root_menu' name='Real Estate'/>
<menuitem id='awesome_estate_first_level_menu' name='Properties' parent='awesome_estate_root_menu'/>
<menuitem id='awesome_estate_property_menu' name='Properties' parent='awesome_estate_first_level_menu' action='awesome_estate_property_action'/>
</odoo>