Kódolási irányelvek¶
Ez az oldal bemutatja az Odoo Kódolási Irányelveket. Ezek célja az Odoo Alkalmazások kódjának minőségének javítása. Valóban, a megfelelő kód javítja az olvashatóságot, megkönnyíti a karbantartást, segíti a hibakeresést, csökkenti a bonyolultságot és elősegíti a megbízhatóságot. Ezeket az irányelveket minden új modulra és minden új fejlesztésre alkalmazni kell.
Figyelem
Amikor meglévő fájlokat módosít a stabil verzióban, az eredeti fájlstílus szigorúan felülír minden más stílusirányelvet. Más szavakkal, kérjük, soha ne módosítson meglévő fájlokat ezen irányelvek alkalmazása érdekében. Ez elkerüli a kódsorok revíziós történetének megzavarását. A különbséget minimálisra kell csökkenteni. További részletekért lásd a pull request guide.
Figyelem
Amikor meglévő fájlokat módosít a master (fejlesztési) verzióban, alkalmazza ezeket az irányelveket a meglévő kódra csak a módosított kód esetében, vagy ha a fájl nagy része felülvizsgálat alatt áll. Más szavakkal, módosítsa a meglévő fájlok szerkezetét csak akkor, ha jelentős változásokon megy keresztül. Ebben az esetben először végezzen el egy move commitot, majd alkalmazza a funkcióval kapcsolatos változtatásokat.
Modul szerkezete¶
Figyelem
For modules developed by the community, it is strongly recommended to name your module with a prefix like your company name.
Könyvtárak¶
Egy modul fontos könyvtárakban van szervezve. Ezek tartalmazzák az üzleti logikát; ha megnézi őket, meg kell értenie a modul célját.
data/ : demó és adat xml
models/ : modellek definíciója
controllers/ : vezérlőket tartalmaz (HTTP útvonalak)
views/ : nézeteket és sablonokat tartalmaz
static/ : webes eszközöket tartalmaz, különválasztva css/, js/, img/, lib/, …
Egyéb opcionális könyvtárak alkotják a modult.
wizard/ : az átmeneti modelleket (
models.TransientModel) és azok nézeteit csoportosítjareport/ : a nyomtatható jelentéseket és az SQL nézeteken alapuló modelleket tartalmazza. Python objektumok és XML nézetek találhatók ebben a könyvtárban
tests/ : a Python teszteket tartalmazza
Fájl elnevezés¶
A fájlnevezés fontos az információk gyors megtalálásához az összes odoo bővítményen keresztül. Ez a szakasz bemutatja, hogyan nevezzük el a fájlokat egy szabványos odoo modulban. Példaként egy növénykertészet alkalmazást használunk. Két fő modellt tartalmaz: plant.nursery és plant.order.
A modellek tekintetében osszuk fel az üzleti logikát az azonos fő modellhez tartozó modellek halmazaira. Minden halmaz egy adott fájlban található, amely a fő modell alapján van elnevezve. Ha csak egy modell van, annak neve megegyezik a modul nevével. Minden örökölt modellnek saját fájlban kell lennie, hogy megkönnyítse az érintett modellek megértését.
addons/plant_nursery/
|-- models/
| |-- plant_nursery.py (first main model)
| |-- plant_order.py (another main model)
| |-- res_partner.py (inherited Odoo model)
A biztonság tekintetében három fő fájlt kell használni:
Az első az irányítási jogok meghatározása, amely egy
ir.model.access.csvfájlban történik.A felhasználói csoportok a
<module>_groups.xmlfájlban vannak meghatározva.A rekordszabályok a
<model>_security.xmlfájlban vannak meghatározva.
addons/plant_nursery/
|-- security/
| |-- ir.model.access.csv
| |-- plant_nursery_groups.xml
| |-- plant_nursery_security.xml
| |-- plant_order_security.xml
A nézetek tekintetében a háttérnézeteket a modellekhez hasonlóan kell felosztani, és _views.xml utótaggal kell ellátni. A háttérnézetek listanézetek, űrlapok, kanban, tevékenység, grafikon, pivot stb. nézetek. A nézetek modell szerinti felosztásának megkönnyítése érdekében a főmenük, amelyek nem kapcsolódnak konkrét műveletekhez, egy opcionális <module>_menus.xml fájlba vonhatók ki. A sablonok (QWeb oldalak, amelyeket különösen a portál / weboldal megjelenítésére használnak) külön fájlokban vannak elhelyezve, amelyek neve <model>_templates.xml.
addons/plant_nursery/
|-- views/
| | -- plant_nursery_menus.xml (optional definition of main menus)
| | -- plant_nursery_views.xml (backend views)
| | -- plant_nursery_templates.xml (portal templates)
| | -- plant_order_views.xml
| | -- plant_order_templates.xml
| | -- res_partner_views.xml
Az adatok tekintetében osszuk fel őket cél szerint (demo vagy adat) és fő modell szerint. A fájlnevek a fő_model neve, amelyet _demo.xml vagy _data.xml utótaggal látnak el. Például egy alkalmazás esetében, amelynek demó és adatai vannak a fő modelljéhez, valamint altípusok, tevékenységek és levélsablonok, amelyek mind a levél modulhoz kapcsolódnak:
addons/plant_nursery/
|-- data/
| |-- plant_nursery_data.xml
| |-- plant_nursery_demo.xml
| |-- mail_data.xml
A vezérlők tekintetében általában minden vezérlő egyetlen vezérlőhöz tartozik, amely egy <module_name>.py nevű fájlban található. Az Odoo régi konvenciója szerint ezt a fájlt main.py-nek nevezték, de ez elavultnak tekinthető. Ha egy meglévő vezérlőt kell örökölnie egy másik modulból, tegye ezt a <inherited_module_name>.py fájlban. Például egy portálvezérlő hozzáadása egy alkalmazásban a portal.py fájlban történik.
addons/plant_nursery/
|-- controllers/
| |-- plant_nursery.py
| |-- portal.py (inheriting portal/controllers/portal.py)
| |-- main.py (deprecated, replaced by plant_nursery.py)
A statikus fájlok tekintetében a Javascript fájlok globálisan ugyanazt a logikát követik, mint a python modellek. Minden komponenst saját fájlban kell elhelyezni, amelynek értelmes neve van. Például a tevékenységi widgetek a levél modul activity.js fájljában találhatók. Alkategóriák is létrehozhatók a «csomag» struktúrájának kialakításához (további részletekért lásd a web modult). Ugyanezt a logikát kell alkalmazni a JS widgetek sablonjaira (statikus XML fájlok) és stílusaikra (scss fájlok). Ne kapcsoljon adatokat (képek, könyvtárak) az Odoo-n kívülre: ne használjon URL-t egy képhez, hanem másolja azt a kódbázisba.
A varázslókkal kapcsolatban a névadási konvenció ugyanaz, mint a python modellek esetében: <transient>.py és <transient>_views.xml. Mindkettőt a wizard könyvtárba helyezzük. Ez a névadás a régi odoo alkalmazásokból származik, amelyek a wizard kulcsszót használták átmeneti modellekhez.
addons/plant_nursery/
|-- wizard/
| |-- make_plant_order.py
| |-- make_plant_order_views.xml
A statisztikai jelentések esetében, amelyeket python / SQL nézetekkel és klasszikus nézetekkel készítenek, a névadás a következő:
addons/plant_nursery/
|-- report/
| |-- plant_order_report.py
| |-- plant_order_report_views.xml
A nyomtatható jelentések esetében, amelyek főként adatelőkészítést és Qweb sablonokat tartalmaznak, a névadás a következő:
addons/plant_nursery/
|-- report/
| |-- plant_order_reports.xml (report actions, paperformat, ...)
| |-- plant_order_templates.xml (xml report templates)
Az Odoo modulunk teljes fástruktúrája tehát így néz ki
addons/plant_nursery/
|-- __init__.py
|-- __manifest__.py
|-- controllers/
| |-- __init__.py
| |-- plant_nursery.py
| |-- portal.py
|-- data/
| |-- plant_nursery_data.xml
| |-- plant_nursery_demo.xml
| |-- mail_data.xml
|-- models/
| |-- __init__.py
| |-- plant_nursery.py
| |-- plant_order.py
| |-- res_partner.py
|-- report/
| |-- __init__.py
| |-- plant_order_report.py
| |-- plant_order_report_views.xml
| |-- plant_order_reports.xml (report actions, paperformat, ...)
| |-- plant_order_templates.xml (xml report templates)
|-- security/
| |-- ir.model.access.csv
| |-- plant_nursery_groups.xml
| |-- plant_nursery_security.xml
| |-- plant_order_security.xml
|-- static/
| |-- img/
| | |-- my_little_kitten.png
| | |-- troll.jpg
| |-- lib/
| | |-- external_lib/
| |-- src/
| | |-- js/
| | | |-- widget_a.js
| | | |-- widget_b.js
| | |-- scss/
| | | |-- widget_a.scss
| | | |-- widget_b.scss
| | |-- xml/
| | | |-- widget_a.xml
| | | |-- widget_a.xml
|-- views/
| |-- plant_nursery_menus.xml
| |-- plant_nursery_views.xml
| |-- plant_nursery_templates.xml
| |-- plant_order_views.xml
| |-- plant_order_templates.xml
| |-- res_partner_views.xml
|-- wizard/
| |--make_plant_order.py
| |--make_plant_order_views.xml
Megjegyzés
A fájlnevek csak [a-z0-9_] (kisbetűs alfanumerikus karakterek és _) karaktereket tartalmazhatnak
Figyelem
Használjon helyes fájlengedélyeket: mappa 755 és fájl 644.
XML fájlok¶
Formátum¶
Egy rekord XML-ben való deklarálásához a record jelölés (a <record> használatával) ajánlott:
Helyezze az
idattribútumot amodeleléA mező deklarációjánál a
nameattribútum az első. Ezután helyezze el az értéket vagy afieldtagben, vagy azevalattribútumban, és végül a többi attribútumot (widget, options, …) fontossági sorrendben.Próbálja meg a rekordokat modell szerint csoportosítani. Ha az akciók/menük/nézetek között függőségek vannak, ez a konvenció nem alkalmazható.
Használja a következő pontban meghatározott elnevezési konvenciót
A <data> tag csak akkor használatos, ha nem frissíthető adatokat állítunk be
noupdate=1-el. Ha a fájlban csak nem frissíthető adatok vannak, anoupdate=1beállítható az<odoo>tagre, és nem szükséges<data>taget beállítani.
<record id="view_id" model="ir.ui.view">
<field name="name">view.name</field>
<field name="model">object_name</field>
<field name="priority" eval="16"/>
<field name="arch" type="xml">
<list>
<field name="my_field_1"/>
<field name="my_field_2" string="My Label" widget="statusbar" statusbar_visible="draft,sent,progress,done" />
</list>
</field>
</record>
Az Odoo támogatja az egyedi tageket, amelyek szintaktikai cukorként működnek:
menuitem: használja rövidítésként egy
ir.ui.menudeklarálásáhoztemplate: használja egy QWeb View deklarálásához, amely csak a nézet
archszekcióját igényli.
Ezek a tagek előnyben részesítendők a record jelöléssel szemben.
XML azonosítók és elnevezés¶
Biztonság, Nézet és Akció¶
Használja a következő mintát:
Egy menü esetén:
<model_name>_menu, vagy<model_name>_menu_do_stuffalmenük esetén.For a view:
<model_name>_view_<view_type>, where view_type iskanban,form,list,search, …Egy művelet esetén: a fő művelet megfelel
<model_name>_action. Mások_<detail>utótaggal rendelkeznek, ahol a detail egy kisbetűs szöveg, amely röviden magyarázza a műveletet. Ezt csak akkor használjuk, ha több művelet van deklarálva a modellhez.Ablak műveletek esetén: az akció nevét egészítse ki a konkrét nézet információjával, például
<model_name>_action_view_<view_type>.Egy csoport esetén:
<module_name>_group_<group_name>, ahol a group_name a csoport neve, általában «user», «manager», …Egy szabály esetén:
<model_name>_rule_<concerned_group>, ahol a concerned_group a vonatkozó csoport rövid neve («user» a «model_name_group_user» esetén, «public» a nyilvános felhasználó esetén, «company» a több vállalati szabályok esetén, …).
A névnek meg kell egyeznie az xml azonosítóval, ahol a pontok helyettesítik az aláhúzásokat. A műveleteknek valós nevet kell adni, mivel ez jelenik meg megjelenítési névként.
<!-- views -->
<record id="model_name_view_form" model="ir.ui.view">
<field name="name">model.name.view.form</field>
...
</record>
<record id="model_name_view_kanban" model="ir.ui.view">
<field name="name">model.name.view.kanban</field>
...
</record>
<!-- actions -->
<record id="model_name_action" model="ir.act.window">
<field name="name">Model Main Action</field>
...
</record>
<record id="model_name_action_child_list" model="ir.actions.act_window">
<field name="name">Model Access Children</field>
</record>
<!-- menus and sub-menus -->
<menuitem
id="model_name_menu_root"
name="Main Menu"
sequence="5"
/>
<menuitem
id="model_name_menu_action"
name="Sub Menu 1"
parent="module_name.module_name_menu_root"
action="model_name_action"
sequence="10"
/>
<!-- security -->
<record id="module_name_group_user" model="res.groups">
...
</record>
<record id="model_name_rule_public" model="ir.rule">
...
</record>
<record id="model_name_rule_company" model="ir.rule">
...
</record>
XML öröklés¶
Az öröklő nézetek Xml azonosítóinak az eredeti rekord azonosítóját kell használniuk. Ez segít az összes öröklés gyors megtalálásában. Mivel a végső Xml azonosítókat az őket létrehozó modul előtaggal látja el, nincs átfedés.
A névnek tartalmaznia kell egy .inherit.{details} utótagot, hogy megkönnyítse a felülírás céljának megértését a név megtekintésekor.
<record id="model_view_form" model="ir.ui.view">
<field name="name">model.view.form.inherit.module2</field>
<field name="inherit_id" ref="module1.model_view_form"/>
...
</record>
Az új elsődleges nézetek nem igénylik az öröklési utótagot, mivel ezek új rekordok az első alapján.
<record id="module2.model_view_form" model="ir.ui.view">
<field name="name">model.view.form.module2</field>
<field name="inherit_id" ref="module1.model_view_form"/>
<field name="mode">primary</field>
...
</record>
Python¶
Figyelem
Ne felejtsd el elolvasni a Security Pitfalls részt is, hogy biztonságos kódot írj.
PEP8 opciók¶
Egy linter használata segíthet a szintaktikai és szemantikai figyelmeztetések vagy hibák megjelenítésében. Az Odoo forráskód igyekszik tiszteletben tartani a Python szabványt, de néhányat figyelmen kívül lehet hagyni.
E501: túl hosszú sor
E301: 1 üres sor várt, 0 található
E302: 2 üres sor várt, 1 található
Importok¶
Az importok sorrendje a következő
Külső könyvtárak (egy sorban rendezve és python stdlib szerint szétválasztva)
odooimportokImportok Odoo modulokból (ritkán, és csak ha szükséges)
Ezen 3 csoporton belül az importált sorok ábécé sorrendben vannak rendezve.
# 1 : imports of python lib
import base64
import re
import time
from datetime import datetime
# 2 : imports of odoo
import odoo
from odoo import Command, _, api, fields, models # ASCIIbetically ordered
from odoo.tools.safe_eval import safe_eval as eval
# 3 : imports from odoo addons
from odoo.addons.web.controllers.main import login_redirect
from odoo.addons.website.models.website import slug
Programozási idiómák (Python)¶
Mindig a olvashatóságot részesítse előnyben a tömörséggel vagy a nyelvi funkciók vagy idiómák használatával szemben.
Ne használja a
.clone()metódust
# bad
new_dict = my_dict.clone()
new_list = old_list.clone()
# good
new_dict = dict(my_dict)
new_list = list(old_list)
Python szótár: létrehozás és frissítés
# -- creation empty dict
my_dict = {}
my_dict2 = dict()
# -- creation with values
# bad
my_dict = {}
my_dict['foo'] = 3
my_dict['bar'] = 4
# good
my_dict = {'foo': 3, 'bar': 4}
# -- update dict
# bad
my_dict['foo'] = 3
my_dict['bar'] = 4
my_dict['baz'] = 5
# good
my_dict.update(foo=3, bar=4, baz=5)
my_dict = dict(my_dict, **my_dict2)
Használjon értelmes változó/osztály/metódus neveket
Haszontalan változó: Az ideiglenes változók használata érthetőbbé teheti a kódot azáltal, hogy nevet ad az objektumoknak, de ez nem jelenti azt, hogy mindig létre kell hoznia ideiglenes változókat:
# pointless
schema = kw['schema']
params = {'schema': schema}
# simpler
params = {'schema': kw['schema']}
Többszörös visszatérési pontok rendben vannak, ha egyszerűbbek
# a bit complex and with a redundant temp variable
def axes(self, axis):
axes = []
if type(axis) == type([]):
axes.extend(axis)
else:
axes.append(axis)
return axes
# clearer
def axes(self, axis):
if type(axis) == type([]):
return list(axis) # clone the axis
else:
return [axis] # single-element list
Ismerje a beépített függvényeket: Legalább alapvető ismeretekkel kell rendelkeznie az összes Python beépített függvényről (http://docs.python.org/library/functions.html)
value = my_dict.get('key', None) # very very redundant
value = my_dict.get('key') # good
Továbbá, az if 'key' in my_dict és az if my_dict.get('key') nagyon eltérő jelentéssel bírnak, győződjön meg róla, hogy a megfelelő változatot használja.
Tanulja meg a listakomprehenziókat: Használjon listakomprehenziót, dict komprehenziót, és alapvető manipulációkat a
map,filter,sumstb. segítségével. Ezek megkönnyítik a kód olvasását.
# not very good
cube = []
for i in res:
cube.append((i['id'],i['name']))
# better
cube = [(i['id'], i['name']) for i in res]
Collections are booleans too : In python, many objects have „boolean-ish” value when evaluated in a boolean context (such as an if). Among these are collections (lists, dicts, sets, …) which are „falsy” when empty and „truthy” when containing items:
bool([]) is False
bool([1]) is True
bool([False]) is True
Tehát írhatja azt, hogy if some_collection: ahelyett, hogy if len(some_collection):.
Iteráljon iterálható objektumokon
# creates a temporary list and looks bar
for key in my_dict.keys():
"do something..."
# better
for key in my_dict:
"do something..."
# accessing the key,value pair
for key, value in my_dict.items():
"do something..."
Használja a dict.setdefault-et
# longer.. harder to read
values = {}
for element in iterable:
if element not in values:
values[element] = []
values[element].append(other_value)
# better.. use dict.setdefault method
values = {}
for element in iterable:
values.setdefault(element, []).append(other_value)
Jó fejlesztőként dokumentálja a kódját (docstring a metódusokon, egyszerű megjegyzések a kód trükkös részein)
In additions to these guidelines, you may also find the following link interesting: https://david.goodger.org/projects/pycon/2007/idiomatic/handout.html (a little bit outdated, but quite relevant)
Programozás az Odoo-ban¶
Kerülje a generátorok és dekorátorok létrehozását: csak az Odoo API által biztosítottakat használja.
Ahogy a pythonban, használja a
filtered,mapped,sorted, … metódusokat a kód olvashatóságának és teljesítményének javítása érdekében.
A kontextus továbbítása¶
A kontextus egy frozendict, amely nem módosítható. Ha egy metódust más kontextussal szeretne meghívni, a with_context metódust kell használni :
records.with_context(new_context).do_stuff() # all the context is replaced
records.with_context(**additionnal_context).do_other_stuff() # additionnal_context values override native context ones
Figyelem
A kontextusban történő paraméterátadás veszélyes mellékhatásokkal járhat.
Mivel az értékek automatikusan továbbítódnak, néhány váratlan viselkedés jelentkezhet. Ha egy modell create() metódusát hívjuk meg default_my_field kulccsal a kontextusban, az beállítja a my_field alapértelmezett értékét az érintett modell számára. De ha ezen létrehozás során más objektumok (mint például sale.order.line, a sale.order létrehozásakor) is létrejönnek, amelyeknek van my_field nevű mezőjük, azok alapértelmezett értéke is beállításra kerül.
Ha szükséges egy olyan kulcs kontextus létrehozása, amely befolyásolja egy objektum viselkedését, válasszon egy jó nevet, és esetleg előtagolja a modul nevével, hogy elszigetelje annak hatását. Jó példa erre a mail modul kulcsai: mail_create_nosubscribe, mail_notrack, mail_notify_user_signature, …
Gondolkodjon kiterjeszthetően¶
A függvényeknek és metódusoknak nem szabad túl sok logikát tartalmazniuk: több kis és egyszerű metódus használata tanácsosabb, mint kevés nagy és összetett metódus. Egy jó ökölszabály, hogy egy metódust akkor kell felosztani, amikor több mint egy felelőssége van (lásd http://en.wikipedia.org/wiki/Single_responsibility_principle).
Az üzleti logika keménykódolását egy metódusban kerülni kell, mivel ez megakadályozza, hogy könnyen kiterjeszthető legyen egy almodul által.
# do not do this
# modifying the domain or criteria implies overriding whole method
def action(self):
... # long method
partners = self.env['res.partner'].search(complex_domain)
emails = partners.filtered(lambda r: arbitrary_criteria).mapped('email')
# better but do not do this either
# modifying the logic forces to duplicate some parts of the code
def action(self):
...
partners = self._get_partners()
emails = partners._get_emails()
# better
# minimum override
def action(self):
...
partners = self.env['res.partner'].search(self._get_partner_domain())
emails = partners.filtered(lambda r: r._filter_partners()).mapped('email')
A fenti kód túlzottan kiterjeszthető a példa kedvéért, de a olvashatóságot figyelembe kell venni, és kompromisszumot kell kötni.
Továbbá, nevezze el a függvényeit ennek megfelelően: a kicsi és megfelelően elnevezett függvények az olvasható/karbantartható kód és a szorosabb dokumentáció kiindulópontjai.
Ez az ajánlás osztályokra, fájlokra, modulokra és csomagokra is vonatkozik. (Lásd még http://en.wikipedia.org/wiki/Cyclomatic_complexity)
Soha ne kötelezze el a tranzakciót¶
Az Odoo keretrendszer felelős a tranzakciós kontextus biztosításáért minden RPC hívás esetén. Az elv az, hogy minden RPC hívás elején egy új adatbázis kurzor nyílik meg, és elköteleződik, amikor a hívás visszatér, közvetlenül azelőtt, hogy az RPC kliensnek továbbítaná a választ, nagyjából így:
def execute(self, db_name, uid, obj, method, *args, **kw):
db, pool = pooler.get_db_and_pool(db_name)
# create transaction cursor
cr = db.cursor()
try:
res = pool.execute_cr(cr, uid, obj, method, *args, **kw)
cr.commit() # all good, we commit
except Exception:
cr.rollback() # error, rollback everything atomically
raise
finally:
cr.close() # always close cursor opened manually
return res
Ha bármilyen hiba történik az RPC hívás végrehajtása során, a tranzakció atomikusan visszagördül, megőrizve a rendszer állapotát.
Hasonlóképpen, a rendszer külön tranzakciót is biztosít a tesztcsomagok végrehajtása során, így az visszagördíthető vagy sem, a szerver indítási opcióitól függően.
Ennek következménye, hogy ha manuálisan hívja meg a cr.commit()-et bárhol, nagyon nagy az esélye, hogy különböző módokon tönkreteszi a rendszert, mert részleges elkötelezéseket okoz, és így részleges és tisztátalan visszagördüléseket, amelyek többek között okoznak:
következetlen üzleti adatokat, általában adatvesztést
munkafolyamat deszinkronizáció, dokumentumok véglegesen elakadnak
tesztek, amelyeket nem lehet tisztán visszagörgetni, és elkezdik szennyezni az adatbázist, valamint hibát váltanak ki (ez akkor is igaz, ha a tranzakció során nem történik hiba)
- Itt van a nagyon egyszerű szabály:
SOHA ne hívd meg saját magad a
cr.commit()-et, KIVÉVE, ha kifejezetten létrehoztad a saját adatbázis kurzorodat! És azok a helyzetek, amikor erre szükséged van, kivételesek!És mellesleg, ha létrehoztad a saját kurzorodat, akkor kezelni kell a hibás eseteket és a megfelelő visszagörgetést, valamint megfelelően le kell zárni a kurzort, amikor végeztél vele.
És a közhiedelemmel ellentétben, még a következő helyzetekben sem kell meghívnod a cr.commit()-et: - egy models.Model objektum _auto_init() metódusában: ezt az addonok inicializálási metódusa, vagy az ORM tranzakció kezeli egyedi modellek létrehozásakor - jelentésekben: a commit()-et a keretrendszer is kezeli, így még jelentésen belül is frissítheted az adatbázist - models.Transient metódusokon belül: ezek a metódusok pontosan úgy hívódnak meg, mint a szokásos models.Model metódusok, egy tranzakción belül, a megfelelő cr.commit()/rollback()-kel a végén - stb. (lásd a fenti általános szabályt, ha kétségeid vannak!)
Minden cr.commit() hívás a szerver keretrendszeren kívül mostantól kifejezett megjegyzéssel kell, hogy rendelkezzen, amely elmagyarázza, miért feltétlenül szükségesek, miért helyesek, és miért nem törik meg a tranzakciókat. Ellenkező esetben eltávolíthatók és el is lesznek távolítva!
Használd helyesen a fordítási módszert¶
Odoo uses a GetText-like method named „underscore” _() to indicate that
a static string used in the code needs to be translated at runtime.
That method is available at self.env._ using the language of the
environment.
Néhány nagyon fontos szabályt be kell tartani a használatakor, hogy működjön, és elkerüljük a fordítások haszontalan szeméttel való feltöltését.
Alapvetően ezt a módszert csak a kódban kézzel írt statikus szövegekhez szabad használni, nem fog működni mezőértékek, például terméknevek fordítására. Ehelyett ezt a megfelelő mezőn a fordítási zászló használatával kell elvégezni.
The method accepts optional positional or named parameter
The rule is very simple: calls to the underscore method should always be in
the form self.env._('literal string') and nothing else:
_ = self.env._
# good: plain strings
error = _('This record is locked!')
# good: strings with formatting patterns included
error = _('Record %s cannot be modified!', record)
# ok too: multi-line literal strings
error = _("""This is a bad multiline example
about record %s!""", record)
error = _('Record %s cannot be modified' \
'after being validated!', record)
# bad: tries to translate after string formatting
# (pay attention to brackets!)
# This does NOT work and messes up the translations!
error = _('Record %s cannot be modified!' % record)
# bad: formatting outside of translation
# This won't benefit from fallback mechanism in case of bad translation
error = _('Record %s cannot be modified!') % record
# bad: dynamic string, string concatenation, etc are forbidden!
# This does NOT work and messes up the translations!
error = _("'" + que_rec['question'] + "' \n")
# bad: field values are automatically translated by the framework
# This is useless and will not work the way you think:
error = _("Product %s is out of stock!") % _(product.name)
# and the following will of course not work as already explained:
error = _("Product %s is out of stock!" % product.name)
# Instead you can do the following and everything will be translated,
# including the product name if its field definition has the
# translate flag properly set:
error = _("Product %s is not available!", product.name)
Továbbá, ne feledje, hogy a fordítóknak a szó szerinti értékekkel kell dolgozniuk, amelyeket az aláhúzás függvényhez adnak át, ezért kérjük, próbálja meg ezeket könnyen érthetővé tenni, és a felesleges karaktereket és formázást minimálisra csökkenteni. A fordítóknak tisztában kell lenniük azzal, hogy a formázási mintákat, mint például %s vagy %d, új sorok stb. meg kell őrizni, de fontos, hogy ezeket ésszerű és nyilvánvaló módon használjuk:
# Bad: makes the translations hard to work with
error = "'" + question + _("' \nPlease enter an integer value ")
# Ok (pay attention to position of the brackets too!)
error = _("Answer to question %s is not valid.\n" \
"Please enter an integer value.", question)
# Better
error = _("Answer to question %(title)s is not valid.\n" \
"Please enter an integer value.", title=question)
Általánosságban az Odoo-ban, amikor szövegeket manipulálunk, előnyben részesítjük a % használatát a .format() helyett (amikor csak egy változót kell helyettesíteni egy szövegben), és előnyben részesítjük a %(varname) használatát a pozíció helyett (amikor több változót kell helyettesíteni). Ez megkönnyíti a fordítást a közösségi fordítók számára.
Szimbólumok és Konvenciók¶
- Modell neve (pont jelöléssel, a modul nevével előtagolva) :
Odoo Modell meghatározásakor: használja a név egyes számú alakját (res.partner és sale.order a res.partnerS és saleS.orderS helyett)
Odoo Átmeneti (varázsló) meghatározásakor: használja a
<related_base_model>.<action>formát, ahol a related_base_model az átmenetihez kapcsolódó alapmodell (a models/-ben meghatározva), és az action az átmeneti tevékenység rövid neve. Kerülje a wizard szót. Például:account.invoice.make,project.task.delegate.batch, …Jelentés modell (SQL nézetek, pl.) meghatározásakor: használja a
<related_base_model>.report.<action>formát, az Átmeneti konvenció alapján.
Odoo Python Class : use Pascal case (Object-oriented style).
class AccountInvoice(models.Model):
...
- Változó neve :
use Pascal case for model variable
használjon aláhúzásos kisbetűs jelölést a közönséges változóhoz.
toldalékolja a változó nevét _id vagy _ids végződéssel, ha rekord azonosítót vagy azonosítók listáját tartalmazza. Ne használja a
partner_id-t a res.partner rekord tárolására
Partner = self.env['res.partner']
partners = Partner.browse(ids)
partner_id = partners[0].id
A
One2ManyésMany2Manymezőknek mindig _ids végződéssel kell rendelkezniük (példa: sale_order_line_ids)A
Many2Onemezőknek _id végződéssel kell rendelkezniük (példa: partner_id, user_id, …)- Metódus konvenciók
Számított mező: a számítási metódus mintája _compute_<field_name>
Keresési metódus: a keresési metódus mintája _search_<field_name>
Alapértelmezett metódus: az alapértelmezett metódus mintája _default_<field_name>
Kiválasztási metódus: a kiválasztási metódus mintája _selection_<field_name>
Onchange metódus: az onchange metódus mintája _onchange_<field_name>
Korlátozás metódus: a korlátozás metódus mintája _check_<constraint_name>
Akció metódus: egy objektum akció metódusának előtagja action_. Mivel csak egy rekordot használ, adja hozzá a
self.ensure_one()-t a metódus elejére.
- Egy Modell attribútumban a sorrendnek a következőnek kell lennie
Privát attribútumok (
_name,_description,_inherit,_sql_constraints, …)Alapértelmezett metódus és
default_getMező deklarációk
Számított, inverz és keresési metódusok ugyanabban a sorrendben, mint a mező deklaráció
Kiválasztási metódus (metódusok, amelyeket a kiválasztási mezők számított értékeinek visszaadására használnak)
Korlátozás metódusok (
@api.constrains) és onchange metódusok (@api.onchange)CRUD metódusok (ORM felülírások)
Akció metódusok
És végül, egyéb üzleti metódusok.
class Event(models.Model):
# Private attributes
_name = 'event.event'
_description = 'Event'
# Default methods
def _default_name(self):
...
# Fields declaration
name = fields.Char(string='Name', default=_default_name)
seats_reserved = fields.Integer(string='Reserved Seats', store=True
readonly=True, compute='_compute_seats')
seats_available = fields.Integer(string='Available Seats', store=True
readonly=True, compute='_compute_seats')
price = fields.Integer(string='Price')
event_type = fields.Selection(string="Type", selection='_selection_type')
# compute and search fields, in the same order of fields declaration
@api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register')
def _compute_seats(self):
...
@api.model
def _selection_type(self):
return []
# Constraints and onchanges
@api.constrains('seats_max', 'seats_available')
def _check_seats_limit(self):
...
@api.onchange('date_begin')
def _onchange_date_begin(self):
...
# CRUD methods (and name_search, _search, ...) overrides
def create(self, values):
...
# Action methods
def action_validate(self):
self.ensure_one()
...
# Business methods
def mail_user_confirm(self):
...
Javascript¶
Statikus fájlok szervezése¶
Az Odoo bővítményeknek vannak bizonyos konvencióik arra vonatkozóan, hogyan kell különféle fájlokat struktúrálni. Itt részletesebben elmagyarázzuk, hogyan kell a webes eszközöket megszervezni.
Az első dolog, amit tudni kell, hogy az Odoo szerver (statikusan) kiszolgál minden fájlt, amely egy static/ mappában található, de az addon nevével előtagolva. Tehát például, ha egy fájl a addons/web/static/src/js/some_file.js helyen található, akkor statikusan elérhető lesz a következő URL-en: your-odoo-server.com/web/static/src/js/some_file.js
A konvenció az, hogy a kódot a következő struktúra szerint szervezzük:
static: általában minden statikus fájl
static/lib: ez az a hely, ahol a js könyvtáraknak egy almappában kell elhelyezkedniük. Tehát például, a jquery könyvtár összes fájlja a addons/web/static/lib/jquery helyen található
static/src: az általános statikus forráskód mappa
static/src/css: az összes css fájl
static/fonts
static/img
static/src/js
static/src/js/tours: végfelhasználói túra fájlok (oktatóanyagok, nem tesztek)
static/src/scss: scss fájlok
static/src/xml: az összes qweb sablon, amely JS-ben lesz renderelve
static/tests: ide helyezzük az összes teszttel kapcsolatos fájlt.
static/tests/tours: ide helyezzük az összes túra teszt fájlt (nem oktatóanyagok).
Javascript kódolási irányelvek¶
use strict;ajánlott minden javascript fájlhozHasználjon lintert (jshint, …)
Soha ne adjon hozzá minifikált Javascript könyvtárakat
Use Pascal case for class declaration
Részletesebb JS irányelvek a github wiki oldalon találhatók. Megtekintheti a meglévő API-kat Javascriptben a Javascript Referenciák megtekintésével.
CSS és SCSS¶
Szintaxis és formázás¶
.o_foo, .o_foo_bar, .o_baz {
height: $o-statusbar-height;
.o_qux {
height: $o-statusbar-height * 0.5;
}
}
.o_corge {
background: $o-list-footer-bg-color;
}
.o_foo, .o_foo_bar, .o_baz {
height: 32px;
}
.o_foo .o_quux, .o_foo_bar .o_quux, .o_baz .o_qux {
height: 16px;
}
.o_corge {
background: #EAEAEA;
}
négy (4) szóközös behúzás, tabulátorok nélkül;
oszlopok max. 80 karakter szélesek;
nyitó kapcsos zárójel (
{): üres hely az utolsó szelekció után;záró kapcsos zárójel (
}): saját új sorban;minden deklaráció külön sorban;
a szóközök értelmes használata.
"stylelint.config": {
"rules": {
// https://stylelint.io/user-guide/rules
// Avoid errors
"block-no-empty": true,
"shorthand-property-no-redundant-values": true,
"declaration-block-no-shorthand-property-overrides": true,
// Stylistic conventions
"indentation": 4,
"function-comma-space-after": "always",
"function-parentheses-space-inside": "never",
"function-whitespace-after": "always",
"unit-case": "lower",
"value-list-comma-space-after": "always-single-line",
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-colon-space-after": "always",
"declaration-colon-space-before": "never",
"block-closing-brace-empty-line-before": "never",
"block-opening-brace-space-before": "always",
"selector-attribute-brackets-space-inside": "never",
"selector-list-comma-space-after": "always-single-line",
"selector-list-comma-space-before": "never-single-line",
}
},
Tulajdonságok sorrendje¶
Order properties from the „outside” in, starting from position and ending with decorative rules
(font, filter, etc.).
Scoped SCSS változók és CSS változók a legfelső sorban kell, hogy legyenek, amelyeket egy üres sor választ el a többi deklarációtól.
.o_element {
$-inner-gap: $border-width + $legend-margin-bottom;
--element-margin: 1rem;
--element-size: 3rem;
@include o-position-absolute(1rem);
display: block;
margin: var(--element-margin);
width: calc(var(--element-size) + #{$-inner-gap});
border: 0;
padding: 1rem;
background: blue;
font-size: 1rem;
filter: blur(2px);
}
Elnevezési Konvenciók¶
Az elnevezési konvenciók a CSS-ben rendkívül hasznosak a kód szigorúbbá, átláthatóbbá és informatívabbá tételében.
id szelektorokat, és előtagolja az osztályait o_<module_name>-nel, ahol <module_name> a modul technikai neve (sale, im_chat, …) vagy a modul által fenntartott fő útvonal (főként weboldal modulok esetén, pl.: o_forum a website_forum modulhoz).o_ előtagot használja.Avoid creating hyper-specific classes and variable names. When naming nested elements, opt for the „Grandchild” approach.
Example
Ne
<div class=“o_element_wrapper”>
<div class=“o_element_wrapper_entries”>
<span class=“o_element_wrapper_entries_entry”>
<a class=“o_element_wrapper_entries_entry_link”>Entry</a>
</span>
</div>
</div>
Tedd
<div class=“o_element_wrapper”>
<div class=“o_element_entries”>
<span class=“o_element_entry”>
<a class=“o_element_link”>Entry</a>
</span>
</div>
</div>
Amellett, hogy kompaktabb, ez a megközelítés megkönnyíti a karbantartást, mivel korlátozza az átnevezési igényt, amikor változások történnek a DOM-ban.
SCSS változók¶
Szabványos konvenciónk a $o-[root]-[element]-[property]-[modifier], ahol:
$o-Az előtag.
[root]Vagy a komponens vagy a modul neve (a komponensek élveznek elsőbbséget).
[element]Egy opcionális azonosító a belső elemekhez.
[tulajdonság]A változó által meghatározott tulajdonság/viselkedés.
[módosító]Egy opcionális módosító.
Example
$o-block-color: value;
$o-block-title-color: value;
$o-block-title-color-hover: value;
SCSS változók (hatókörrel)¶
Ezek a változók blokkokon belül vannak deklarálva, és kívülről nem érhetők el. A mi szabványos konvenciónk: $-[változó név].
Example
.o_element {
$-inner-gap: compute-something;
margin-right: $-inner-gap;
.o_element_child {
margin-right: $-inner-gap * 0.5;
}
}
SCSS Mixinek és Funkciók¶
A mi szabványos konvenciónk: o-[név]. Használjon leíró neveket. Funkciók elnevezésekor használjon igéket felszólító módban (pl.: get, make, apply…).
Nevezze meg az opcionális argumentumokat a scoped variables form-ban, így $-[argument].
Example
@mixin o-avatar($-size: 1.5em, $-radius: 100%) {
width: $-size;
height: $-size;
border-radius: $-radius;
}
@function o-invert-color($-color, $-amount: 100%) {
$-inverse: change-color($-color, $-hue: hue($-color) + 180);
@return mix($-inverse, $-color, $-amount);
}
CSS változók¶
Az Odoo-ban a CSS változók használata szigorúan DOM-hoz kapcsolódik. Használja őket a tervezés és az elrendezés környezetfüggő adaptálására.
Szabványos konvenciónk a BEM, tehát --[root]__[element]-[property]--[modifier], a következőkkel:
[root]Vagy a komponens vagy a modul neve (a komponensek élveznek elsőbbséget).
[element]Egy opcionális azonosító a belső elemekhez.
[tulajdonság]A változó által meghatározott tulajdonság/viselkedés.
[módosító]Egy opcionális módosító.
Example
.o_kanban_record {
--KanbanRecord-width: value;
--KanbanRecord__picture-border: value;
--KanbanRecord__picture-border--active: value;
}
// Adapt the component when rendered in another context.
.o_form_view {
--KanbanRecord-width: another-value;
--KanbanRecord__picture-border: another-value;
--KanbanRecord__picture-border--active: another-value;
}
CSS változók használata¶
Az Odoo-ban a CSS változók használata szigorúan DOM-hoz kapcsolódik, ami azt jelenti, hogy a tervezés és az elrendezés környezetfüggő adaptálására használják, nem pedig a globális tervezési rendszer kezelésére. Ezeket általában akkor használják, amikor egy komponens tulajdonságai specifikus kontextusokban vagy más körülmények között változhatnak.
Ezeket a tulajdonságokat a komponens fő blokkjában határozzuk meg, alapértelmezett visszaeséseket biztosítva.
CSS és SCSS változók¶
Annak ellenére, hogy látszólag hasonlóak, a CSS és SCSS változók nagyon eltérően viselkednek. A fő különbség az, hogy míg a SCSS változók imperatívak és lefordítódnak, a CSS változók deklaratívak és bekerülnek a végső kimenetbe.
Az Odoo-ban a legjobbat vesszük mindkét világból: a SCSS változókat használjuk a dizájnrendszer meghatározására, míg a CSS változókat a kontextuális alkalmazkodásokhoz választjuk.
A korábbi példa megvalósítását javítani kell az SCSS változók hozzáadásával, hogy a legfelső szinten irányítást nyerjünk, és biztosítsuk az összhangot más komponensekkel.
A :root pszeudo-osztály¶
A CSS változók :root pszeudo-osztályon való definiálása egy olyan technika, amelyet általában nem használunk az Odoo felhasználói felületén. Ezt a gyakorlatot általában a CSS változók globális elérésére és módosítására használják. Ehelyett SCSS-t használunk.
Az e szabály alóli kivételeknek elég nyilvánvalónak kell lenniük, mint például az olyan sablonok, amelyeket több csomag között osztanak meg, és amelyek megfelelő megjelenítéséhez bizonyos szintű kontextuális tudatosság szükséges.