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ítja

  • report/ : 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.csv fájlban történik.

  • A felhasználói csoportok a <module>_groups.xml fájlban vannak meghatározva.

  • A rekordszabályok a <model>_security.xml fá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 id attribútumot a model elé

  • A mező deklarációjánál a name attribútum az első. Ezután helyezze el az értéket vagy a field tagben, vagy az eval attribú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, a noupdate=1 beá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.menu deklarálásához

  • template: használja egy QWeb View deklarálásához, amely csak a nézet arch szekció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_stuff almenük esetén.

  • For a view: <model_name>_view_<view_type>, where view_type is kanban, 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ő

  1. Külső könyvtárak (egy sorban rendezve és python stdlib szerint szétválasztva)

  2. odoo importok

  3. Importok 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
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, sum stb. 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)

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:

  1. következetlen üzleti adatokat, általában adatvesztést

  2. munkafolyamat deszinkronizáció, dokumentumok véglegesen elakadnak

  3. 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 és Many2Many mezőknek mindig _ids végződéssel kell rendelkezniük (példa: sale_order_line_ids)

  • A Many2One mező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
    1. Privát attribútumok (_name, _description, _inherit, _sql_constraints, …)

    2. Alapértelmezett metódus és default_get

    3. Mező deklarációk

    4. Számított, inverz és keresési metódusok ugyanabban a sorrendben, mint a mező deklaráció

    5. Kiválasztási metódus (metódusok, amelyeket a kiválasztási mezők számított értékeinek visszaadására használnak)

    6. Korlátozás metódusok (@api.constrains) és onchange metódusok (@api.onchange)

    7. CRUD metódusok (ORM felülírások)

    8. Akció metódusok

    9. É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ájlhoz

  • Haszná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;
}
  • 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.

Kerülje az 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).
Ennek a szabálynak az egyetlen kivétele a webclient: egyszerűen az 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.

Example

my_component.scss
.o_MyComponent {
   color: var(--MyComponent-color, #313131);
}
my_dashboard.scss
.o_MyDashboard {
   // Adapt the component in this context only
   --MyComponent-color: #017e84;
}

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.

Example

secondary_variables.scss
$o-component-color: $o-main-text-color;
$o-dashboard-color: $o-info;
// [...]
component.scss
.o_component {
   color: var(--MyComponent-color, #{$o-component-color});
}
dashboard.scss
.o_dashboard {
   --MyComponent-color: #{$o-dashboard-color};
}

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.