Python is de taal van de Odoo backend. Voor we in de complexiteit van het framework duiken, moeten we de fundamenten van de taal beheersen. Odoo maakt intensief gebruik van object-georiënteerd programmeren (OOP), decorators en specifieke data-structuren zoals dictionaries. Vandaag bouwen we de basislogica voor onze eerste TechniCool componenten en begrijpen we waarom 'self' de spil is van elk Odoo record.
1.1
Python Syntax & Variabelen
▼
Bij TechniCool NV is het cruciaal dat we data van keukentoestellen gestructureerd opslaan in variabelen. Python gebruikt hiervoor een dynamische typering, wat betekent dat we het type niet vooraf hoeven te definiëren. Voor een Rational combi-steamer gebruiken we strings voor het merk en model, en integers of floats voor technische waarden zoals de temperatuur of het aantal bedrijfsuren. We leren hoe we deze variabelen correct benoemen volgens de Pythonic 'snake_case' conventie die ook binnen Odoo de standaard is.
# Definiëren van toestel data voor TechniCool
merk = "Rational"
model = "iCombi Pro 6-1/1"
serienummer = "E61S J2401001"
bedrijfsuren = 450
temperatuur_max = 300.5
is_actief = True
print(f"Toestel: {merk} {model} (SN: {serienummer})")</pre>
✏ Mini-oefening
Maak variabelen aan voor een Electrolux blast chiller met model 'SkyLine' en een serienummer naar keuze. Voeg een float toe voor de koeltemperatuur (-20.5 graden).
👁 Toon oplossing
merk = "Electrolux"
model = "SkyLine"
serienummer = "ELX-9988-X"
koeltemperatuur = -20.5
1.2
Lijsten & Dictionaries Geavanceerd
▼
In de wereld van Odoo ontvangen we vaak data als een lijst van dictionaries, bijvoorbeeld bij het ophalen van een onderdelenlijst voor een herstelling bij TechniCool. Dictionaries laten ons toe om data te labelen met 'keys' zoals 'onderdeel_naam' en 'prijs', terwijl lijsten deze groeperen. We leren hoe we diep in deze structuren duiken om specifieke informatie te extraheren, zoals de totale waarde van de stock in een technieker-wagen. List comprehensions zijn hierbij een onmisbare tool om snel data te transformeren zonder lange for-loops.
# Onderdelenlijst voor een TechniCool herstelling
onderdelen = [
{"id": 1, "naam": "Deurrubber", "prijs": 45.50},
{"id": 2, "naam": "Ventilator motor", "prijs": 120.00},
{"id": 3, "naam": "Temperatuursonde", "prijs": 12.75}
]
# Haal enkel de namen op via list comprehension
namen = [o["naam"] for o in onderdelen]
print(f"Benodigde onderdelen: {namen}")</pre>
✏ Mini-oefening
Schrijf een list comprehension die enkel de prijzen uit de lijst `onderdelen` haalt en deze opslaat in een nieuwe lijst `prijzen`.
👁 Toon oplossing
prijzen = [o["prijs"] for o in onderdelen]
1.3
Control Flow & Logic
▼
TechniCool dispatchers moeten vaak beslissingen automatiseren op basis van logica, zoals het checken van garantie. We gebruiken if, elif en else om de stroom van ons programma te sturen op basis van de ouderdom van een keukentoestel. Door datums te vergelijken kunnen we automatisch bepalen of een klant recht heeft op gratis support of dat de wisselstukken gefactureerd moeten worden. Logische operatoren zoals and, or en not helpen ons om complexe condities te bouwen voor TechniCool.
from datetime import date
installatie_datum = date(2023, 5, 10)
vandaag = date.today()
garantie_termijn = 730 # 2 jaar in dagen
dagen_oud = (vandaag - installatie_datum).days
if dagen_oud < 0:
print("Fout: Toestel moet nog geïnstalleerd worden.")
elif dagen_oud < garantie_termijn:
print("Status: TechniCool Premium Garantie (Actief)")
else:
print("Status: Garantie verlopen. Factureer interventie.")</pre>
✏ Mini-oefening
Schrijf een if-statement dat controleert of de variabele `temperatuur` (stel in op 210) hoger is dan 200. Zo ja, print "Gevaar: Oven te warm!".
👁 Toon oplossing
temperatuur = 210
if temperatuur > 200:
print("Gevaar: Oven te warm!")
1.4
Functies & *args/**kwargs
▼
Herbruikbaarheid is de sleutel tot schone code bij TechniCool NV, en functies stellen ons in staat om logica één keer te schrijven en overal te gebruiken. We leren hoe we argumenten doorgeven aan functies om berekeningen uit te voeren, zoals het bepalen van de voorrijkosten. Geavanceerde technieken zoals *args en **kwargs laten ons toe om een variabel aantal argumenten te accepteren, wat Odoo intern constant gebruikt voor de creatie van records via de API.
def bereken_factuur(uren, uurtarief=65, **onderdelen):
totaal_uren = uren * uurtarief
totaal_onderdelen = sum(onderdelen.values())
return totaal_uren + totaal_onderdelen
# Aanroep met kwargs voor onderdelen
totaal = bereken_factuur(uren=2, filter=25, pakking=12)
print(f"Totaal te factureren aan TechniCool klant: €{totaal}")</pre>
✏ Mini-oefening
Maak een functie `check_serienummer(sn)` die True teruggeeft als het serienummer start met "TC-" en anders False. Tip: gebruik `sn.startswith("TC-")`.
👁 Toon oplossing
def check_serienummer(sn):
return sn.startswith("TC-")
1.5
Classes & Inheritance
▼
Odoo is volledig gebouwd op Object-Georiënteerd Programmeren (OOP). Bij TechniCool definiëren we een basis Toestel klasse die algemene eigenschappen heeft, en gebruiken we overerving om specifieke sub-klasses zoals Oven te maken. Dit laat ons toe om algemene methoden één keer te definiëren, terwijl we specifieke logica enkel toevoegen aan de relevante sub-klasse. Dit concept is direct overdraagbaar naar hoe we Odoo modellen uitbreiden via _inherit.
class Toestel:
def __init__(self, naam):
self.naam = naam
def rapport(self):
print(f"Basis rapport voor {self.naam}")
class Oven(Toestel):
def rapport(self):
super().rapport()
print("Inclusief temperatuur-log voor TechniCool keuring.")
mijn_oven = Oven("Rational iCombi")
mijn_oven.rapport()</pre>
✏ Mini-oefening
Maak een subklasse `Koelkast` die overerft van `Toestel`. Voeg een methode `vries()` toe die "Koelen gestart..." print.
👁 Toon oplossing
class Koelkast(Toestel):
def vries(self):
print("Koelen gestart...")
1.6
Het 'self' concept en Scope
▼
Het keyword self verwijst naar de specifieke instantie (het record) waar we op dat moment mee werken. Bij TechniCool, als we een methode aanroepen op een specifieke Rational oven, zorgt self ervoor dat we de data van die oven ophalen en niet van een ander toestel. We leren ook het verschil tussen instance variabelen (uniek per toestel) en class variabelen (gedeeld door alle techniekers). Begrip van scope voorkomt veelvoorkomende errors in Odoo logica.
class Technieker:
bedrijf = "TechniCool NV" # Class variabele
def __init__(self, naam):
self.naam = naam # Instance variabele
def voorstellen(self):
print(f"Ik ben {self.naam} van {self.bedrijf}")
t1 = Technieker("Marc")
t1.voorstellen()</pre>
✏ Mini-oefening
Voeg een variabele `self.specialisatie` toe aan de `__init__` van de klasse `Technieker`. Print deze specialisatie in de methode `voorstellen`.
👁 Toon oplossing
class Technieker:
bedrijf = "TechniCool NV"
def __init__(self, naam, specialisatie):
self.naam = naam
self.specialisatie = specialisatie
def voorstellen(self):
print(f"Ik ben {self.naam} van {self.bedrijf}, expert in {self.specialisatie}")
1.7
Decorators (@) in Detail
▼
Decorators zien eruit als magie in Odoo (@api.depends), maar technisch zijn het gewoon Python functies die een andere functie 'wrappen' om extra gedrag toe te voegen. Bij TechniCool gebruiken we ze om bijvoorbeeld automatisch logging toe te voegen aan herstel-methoden. We leren hoe we zelf een eenvoudige decorator schrijven die de uitvoeringstijd meet. Dit inzicht zorgt ervoor dat je echt begrijpt hoe de Odoo server de dependency graph opbouwt.
def log_actie(func):
def wrapper(*args, **kwargs):
print(f"LOG: Start uitvoering van {func.__name__}")
result = func(*args, **kwargs)
print("LOG: Actie voltooid.")
return result
return wrapper
@log_actie
def herstel_oven():
print("Oven wordt hersteld door TechniCool...")
herstel_oven()</pre>
✏ Mini-oefening
Maak een decorator `check_admin` die enkel een print "Toegang verleend" doet voor hij de functie uitvoert.
👁 Toon oplossing
def check_admin(func):
def wrapper(*args, **kwargs):
print("Toegang verleend")
return func(*args, **kwargs)
return wrapper
1.8
Error Handling (Try/Except)
▼
Niets is vervelender dan een TechniCool server die crasht omdat er een veld leeg is in de database. Met try/except blokken maken we onze code robuust tegen onverwachte situaties, zoals een ontbrekende prijs bij een onderdeel. We leren hoe we specifieke errors vangen en hoe we een zinvolle foutmelding teruggeven aan de dispatcher. Dit is cruciaal voor Odoo 'ValidationErrors' waarbij we de gebruiker professionele feedback geven.
# Error handling in de TechniCool database
onderdeel_data = {"naam": "Ventilator"} # Oeps, prijs ontbreekt!
try:
prijs = onderdeel_data["prijs"]
totaal = prijs * 1.21 # BTW berekening
print(f"Totaal: {totaal}")
except KeyError:
print("Fout: Prijs ontbreekt in TechniCool onderhouds-database.")
except Exception as e:
print(f"Onverwachte fout: {e}")
finally:
print("Check voltooid.")</pre>
✏ Mini-oefening
Schrijf een try/except blok dat een `ZeroDivisionError` opvangt wanneer je `100 / 0` probeert te doen. Print "Kan niet delen door nul!".
👁 Toon oplossing
try:
result = 100 / 0
except ZeroDivisionError:
print("Kan niet delen door nul!")
1.9
Virtual Environments & Pip
▼
Op de Hetzner-server van TechniCool NV werken we met virtuele omgevingen zodat elk project zijn eigen afhankelijkheden heeft zonder conflicten. Een venv isoleert de Python-installatie volledig. We installeren requests voor REST API-aanroepen naar Odoo en externe diensten, en pytz voor tijdzone-conversies bij het plannen van TechniCool-interventies. Het requirements.txt-bestand documenteert alle dependencies zodat elk teamlid exact dezelfde omgeving reproduceert met één commando.
# 1. Virtualenv aanmaken op de Hetzner dev-server
python3 -m venv techniecool-env
# 2. Omgeving activeren (Linux / Mac)
source techniecool-env/bin/activate
# 3. Libraries installeren die we later voor Odoo nodig hebben
pip install requests pytz python-dotenv
# 4. Bewaar exacte versies voor het team
pip freeze > requirements.txt
# requirements.txt (voorbeeld output):
# requests==2.31.0
# pytz==2024.1
# python-dotenv==1.0.0
# 5. Team-lid reproduceert de omgeving met:
pip install -r requirements.txt</pre>
✏ Mini-oefening
Maak een requirements.txt voor een project dat de anthropic-library (Claude AI, gebruikt in Vak 4) en openpyxl (voor Excel-exports uit Odoo) nodig heeft. Waarom hoeft xmlrpc — die we gebruiken voor Odoo XML-RPC communicatie — niet in requirements.txt?
👁 Toon oplossing
anthropic==0.26.0
openpyxl==3.1.2
# xmlrpc is een INGEBOUWDE Python-module (standard library).
# Je installeert hem niet via pip, hij is altijd beschikbaar.
# Enkel externe third-party packages horen in requirements.txt.
1.10
Python Modules & Imports
▼
Code verspreid over meerdere bestanden organiseren is exact wat Odoo ook doet. Een Python package is een map met een __init__.py-bestand dat de submodules laadt — precies de structuur die we terugzien in elke Odoo-module. Relatieve imports (from . import equipment) zijn identiek aan hoe Odoo zijn models-map organiseert. Als je straks een Odoo-module opent en from . import equipment ziet in models/__init__.py, herken je dit direct als het patroon dat je hier leert.
# Mapstructuur die EXACT lijkt op een Odoo-module:
# techniecool/
# ├── __init__.py ← Maakt het een Python package
# ├── equipment.py ← KeukenToestel klasse
# └── technician.py ← Technieker klasse
# __init__.py — laadt sub-modules (zelfde als Odoo models/__init__.py)
from . import equipment
from . import technician
# equipment.py
class KeukenToestel:
def __init__(self, brand, serial):
self.brand = brand
self.serial = serial
# main.py — gebruik het package vanuit buiten
from techniecool.equipment import KeukenToestel
steamer = KeukenToestel("Rational", "RA-2024-001")
print(steamer.brand) # Output: Rational</pre>
✏ Mini-oefening
Voeg een bestand utils.py toe aan het techniecool package met een functie formatteer_serienummer(sn) die het serienummer naar hoofdletters omzet. Importeer de functie in main.py en print formatteer_serienummer("ra-2024-001").
👁 Toon oplossing
# utils.py
def formatteer_serienummer(sn):
return sn.upper()
# __init__.py — voeg toe:
from . import utils
# main.py
from techniecool.utils import formatteer_serienummer
print(formatteer_serienummer("ra-2024-001")) # RA-2024-001
🛠 Capstone — Alles Samen: KeukenToestel Module
Dit is de volledige equipment.py die alle H1-concepten combineert: class variabelen, instance variabelen, methods, error handling en JSON-export. Sla dit bestand op als techniecool/equipment.py in jouw virtualenv-project.
# TechniCool Equipment Logic - Uitgebreid Fundament
from datetime import date, timedelta
import json
class KeukenToestel:
"""Basis klasse voor alle TechniCool apparatuur."""
GARANTIE_DAGEN = 730 # 2 Jaar
def __init__(self, brand, model, serial_number, install_date):
self.brand = brand
self.model = model
self.serial_number = serial_number
self.install_date = install_date
self.history = []
def is_under_warranty(self):
# Garantie berekening op basis van constante
warranty_end = self.install_date + timedelta(days=self.GARANTIE_DAGEN)
return date.today() < warranty_end
def add_log(self, message):
# Historiek bijhouden van het toestel
timestamp = date.today().isoformat()
self.history.append({"date": timestamp, "msg": message})
def to_json(self):
# Converteren naar JSON voor TechniCool API
data = {
'brand': self.brand,
'model': self.model,
'sn': self.serial_number,
'warranty': self.is_under_warranty(),
'logs': self.history
}
return json.dumps(data, indent=2)
# Instantie van een Rational Combi-Steamer
steamer = KeukenToestel(
brand='Rational',
model='iCombi Pro 6-1/1',
serial_number='E61S J2401001',
install_date=date(2024, 3, 9)
)
steamer.add_log("Installatie voltooid door technieker Marc.")
steamer.add_log("Eerste check-up uitgevoerd.")
print(f"--- Status Rapport: {steamer.serial_number} ---")
if steamer.is_under_warranty():
print("Status: Onder garantie (TechniCool Premium Support)")
else:
print("Status: Garantie verlopen. Factureer wisselstukken.")
print(\nJSON Export voor Vanventory App:")
print(steamer.to_json())</pre>
⚙ Praktijkopdracht
Schrijf een Python script dat een lijst van 5 KeukenToestel objecten aanmaakt voor TechniCool NV. Gebruik een loop om van elk toestel de garantie-status te printen. Voeg een methode needs_service() toe die True teruggeeft als de installatiedatum meer dan 1 jaar geleden is.
Theorie: Recordsets
In Odoo werk je zelden met één object. Je werkt met "recordsets". Een recordset is een collectie van records van hetzelfde model. In Python gedraagt dit zich als een lijst, maar met extra Odoo-magie.