Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading