Building a Module

خطر

This tutorial is outdated. We recommend reading Server framework 101 instead.

هشدار

This tutorial requires having installed Odoo

Start/Stop the Odoo server

Odoo uses a client/server architecture in which clients are web browsers accessing the Odoo server via RPC.

Business logic and extension is generally performed on the server side, although supporting client features (e.g. new data representation such as interactive maps) can be added to the client.

In order to start the server, simply invoke the command odoo-bin in the shell, adding the full path to the file if necessary:

odoo-bin

The server is stopped by hitting Ctrl-C twice from the terminal, or by killing the corresponding OS process.

Build an Odoo module

Both server and client extensions are packaged as modules which are optionally loaded in a database.

Odoo modules can either add brand new business logic to an Odoo system, or alter and extend existing business logic: a module can be created to add your country's accounting rules to Odoo's generic accounting support, while the next module adds support for real-time visualisation of a bus fleet.

Everything in Odoo thus starts and ends with modules.

Composition of a module

An Odoo module can contain a number of elements:

Business objects

Declared as Python classes, these resources are automatically persisted by Odoo based on their configuration

Object views

Definition of business objects UI display

Data files

XML or CSV files declaring the model metadata :

Web controllers

Handle requests from web browsers

Static web data

Images, CSS or javascript files used by the web interface or website

ساختار ماژول

Each module is a directory within a module directory. Module directories are specified by using the --addons-path option.

نکته

most command-line options can also be set using a configuration file

An Odoo module is declared by its manifest.

A module is also a Python package with a __init__.py file, containing import instructions for various Python files in the module.

For instance, if the module has a single mymodule.py file __init__.py might contain:

from . import mymodule

Odoo provides a mechanism to help set up a new module, odoo-bin has a subcommand scaffold to create an empty module:

$ odoo-bin scaffold <module name> <where to put it>

The command creates a subdirectory for your module, and automatically creates a bunch of standard files for a module. Most of them simply contain commented code or XML. The usage of most of those files will be explained along this tutorial.

Exercise

Module creation

Use the command line above to create an empty module Open Academy, and install it in Odoo.

Object-Relational Mapping

A key component of Odoo is the ORM layer. This layer avoids having to write most SQL by hand and provides extensibility and security services2.

Business objects are declared as Python classes extending Model which integrates them into the automated persistence system.

Models can be configured by setting a number of attributes at their definition. The most important attribute is _name which is required and defines the name for the model in the Odoo system. Here is a minimally complete definition of a model:

from odoo import models
class MinimalModel(models.Model):
    _name = 'test.model'

Model fields

Fields are used to define what the model can store and where. Fields are defined as attributes on the model class:

from odoo import models, fields

class LessMinimalModel(models.Model):
    _name = 'test.model2'

    name = fields.Char()

Common Attributes

Much like the model itself, its fields can be configured, by passing configuration attributes as parameters:

name = fields.Char(required=True)

Some attributes are available on all fields, here are the most common ones:

string (unicode, default: field's name)

The label of the field in UI (visible by users).

required (bool, default: False)

If True, the field can not be empty, it must either have a default value or always be given a value when creating a record.

help (unicode, default: '')

Long-form, provides a help tooltip to users in the UI.

index (bool, default: False)

Requests that Odoo create a database index on the column.

Simple fields

There are two broad categories of fields: "simple" fields which are atomic values stored directly in the model's table and "relational" fields linking records (of the same model or of different models).

Example of simple fields are Boolean, Date, Char.

Reserved fields

Odoo creates a few fields in all models1. These fields are managed by the system and shouldn't be written to. They can be read if useful or necessary:

id (Id)

The unique identifier for a record in its model.

create_date (Datetime)

Creation date of the record.

create_uid (Many2one)

User who created the record.

write_date (Datetime)

Last modification date of the record.

write_uid (Many2one)

user who last modified the record.

Special fields

By default, Odoo also requires a name field on all models for various display and search behaviors. The field used for these purposes can be overridden by setting _rec_name.

Exercise

Define a model

Define a new data model Course in the openacademy module. A course has a title and a description. Courses must have a title.

Data files

Odoo is a highly data driven system. Although behavior is customized using Python code part of a module's value is in the data it sets up when loaded.

نکته

some modules exist solely to add data into Odoo

Module data is declared via data files, XML files with <record> elements. Each <record> element creates or updates a database record.

<odoo>

        <record model="{model name}" id="{record identifier}">
            <field name="{a field name}">{a value}</field>
        </record>

</odoo>
  • model is the name of the Odoo model for the record.

  • id is an external identifier, it allows referring to the record (without having to know its in-database identifier).

  • <field> elements have a name which is the name of the field in the model (e.g. description). Their body is the field's value.

Data files have to be declared in the manifest file to be loaded, they can be declared in the 'data' list (always loaded) or in the 'demo' list (only loaded in demonstration mode).

Exercise

Define demonstration data

Create demonstration data filling the Courses model with a few demonstration courses.

نکته

The content of the data files is only loaded when a module is installed or updated.

پس از انجام برخی تغییرات، فراموش نکنید از odoo-bin -u openacademy برای ذخیرهٔ تغییرات در پایگاه‌دادهٔ خود استفاده کنید.

اکشن‌ها و منوها

اکشن‌ها و منوها رکوردهای معمولی در پایگاه‌داده هستند که معمولاً از طریق فایل‌های داده اعلام می‌شوند. اکشن‌ها به سه روش قابل فعال‌سازی هستند:

  1. با کلیک کردن روی آیتم‌های منو (پیوندخورده به اکشن‌های مشخص)

  2. با کلیک کردن روی دکمه‌ها در نماها (در صورتی که به اکشن‌ها متصل باشند)

  3. به‌عنوان اکشن‌های زمینه‌ای روی شیء

از آنجایی که اعلام منوها تا حدودی پیچیده است، یک میانبر <menuitem> وجود دارد که اعلام یک ir.ui.menu و اتصال آن به اکشن متناظر را آسان‌تر می‌کند.

<record model="ir.actions.act_window" id="action_list_ideas">
    <field name="name">Ideas</field>
    <field name="res_model">idea.idea</field>
    <field name="view_mode">list,form</field>
</record>
<menuitem id="menu_ideas" parent="menu_root" name="Ideas" sequence="10"
          action="action_list_ideas"/>

خطر

اکشن باید قبل از منوی متناظرش در فایل XML اعلام شود.

فایل‌های داده به‌صورت ترتیبی اجرا می‌شوند، id اکشن باید قبل از ایجاد منو در پایگاه‌داده وجود داشته باشد.

Exercise

تعریف ورودی‌های جدید منو

ورودی‌های جدید منو برای دسترسی به دوره‌ها تحت ورودی منوی OpenAcademy تعریف کنید. یک کاربر باید بتواند:

  • فهرستی از همهٔ دوره‌ها را نمایش دهد

  • دوره‌ها را ایجاد/اصلاح کند

نماهای پایه

نماها روش نمایش رکوردهای یک مدل را تعریف می‌کنند. هر نوع نما یک حالت بصری‌سازی را نشان می‌دهد (یک فهرست از رکوردها، یک نمودار از تجمیع آن‌ها، …). نماها را می‌توان به‌صورت عمومی از طریق نوع آن‌ها (مثلاً a list of partners) یا به‌صورت اختصاصی از طریق شناسهٔ آن‌ها درخواست کرد. برای درخواست‌های عمومی، نمای با نوع صحیح و کمترین اولویت استفاده می‌شود (بنابراین نمای با کمترین اولویت از هر نوع، نمای پیش‌فرض برای آن نوع است).

ارث‌بری نما اجازهٔ تغییر نماهای اعلام‌شده در جای دیگر را می‌دهد (افزودن یا حذف محتوا).

اعلام عمومی نما

یک نما به‌عنوان رکوردی از مدل ir.ui.view اعلام می‌شود. نوع نما توسط عنصر ریشهٔ فیلد arch تعیین می‌شود:

<record model="ir.ui.view" id="view_id">
    <field name="name">view.name</field>
    <field name="model">object_name</field>
    <field name="priority" eval="16"/>
    <field name="arch" type="xml">
        <!-- view content: <form>, <list>, <graph>, ... -->
    </field>
</record>

خطر

محتوای نما XML است.

بنابراین فیلد arch باید به‌عنوان type="xml" اعلام شود تا به‌درستی تجزیه شود.

نماهای فهرست

نماهای فهرست، که نماهای فهرست نیز نامیده می‌شوند، رکوردها را در قالب جدولی نمایش می‌دهند.

عنصر ریشهٔ آن‌ها <list> است. ساده‌ترین شکل نمای فهرست به‌سادگی همهٔ فیلدها را برای نمایش در جدول فهرست می‌کند (هر فیلد به‌عنوان یک ستون):

<list string="Idea list">
    <field name="name"/>
    <field name="inventor_id"/>
</list>

نماهای فرم

فرم‌ها برای ایجاد و ویرایش رکوردهای منفرد استفاده می‌شوند.

عنصر ریشهٔ آن‌ها <form> است. آن‌ها از عناصر ساختاری سطح بالا (گروه‌ها، نوت‌بوک‌ها) و عناصر تعاملی (دکمه‌ها و فیلدها) تشکیل شده‌اند:

<form string="Idea form">
    <group colspan="4">
        <group colspan="2" col="2">
            <separator string="General stuff" colspan="2"/>
            <field name="name"/>
            <field name="inventor_id"/>
        </group>

        <group colspan="2" col="2">
            <separator string="Dates" colspan="2"/>
            <field name="active"/>
            <field name="invent_date" readonly="1"/>
        </group>

        <notebook colspan="4">
            <page string="Description">
                <field name="description" nolabel="1"/>
            </page>
        </notebook>

        <field name="state"/>
    </group>
</form>

Exercise

سفارشی‌سازی نمای فرم با استفاده از XML

نمای فرم خود را برای شیء Course ایجاد کنید. داده‌های نمایش‌داده‌شده باید نام و توضیحات دوره باشد.

Exercise

نوت‌بوک‌ها

در نمای فرم Course، فیلد توضیحات را زیر یک تب قرار دهید، به‌گونه‌ای که افزودن تب‌های دیگر بعداً، حاوی اطلاعات اضافی، آسان‌تر باشد.

نماهای فرم می‌توانند از HTML خالص نیز برای طراحی‌های انعطاف‌پذیرتر استفاده کنند:

<form string="Idea Form">
    <header>
        <button string="Confirm" type="object" name="action_confirm"
                invisible="state != 'draft'" class="oe_highlight" />
        <button string="Mark as done" type="object" name="action_done"
                invisible="state != 'confirmed'" class="oe_highlight"/>
        <button string="Reset to draft" type="object" name="action_draft"
                invisible="state not in ['confirmed', 'done']" />
        <field name="state" widget="statusbar"/>
    </header>
    <sheet>
        <div class="oe_title">
            <label for="name" class="oe_edit_only" string="Idea Name" />
            <h1><field name="name" /></h1>
        </div>
        <separator string="General" colspan="2" />
        <group colspan="2" col="2">
            <field name="description" placeholder="Idea description..." />
        </group>
    </sheet>
</form>

نماهای جستجو

نماهای جستجو، فیلد جستجوی مرتبط با نمای فهرست (و سایر نماهای تجمیع‌شده) را سفارشی می‌کنند. عنصر ریشهٔ آن‌ها <search> است و از فیلدهایی تشکیل شده‌اند که مشخص می‌کنند روی کدام فیلدها می‌توان جستجو کرد:

<search>
    <field name="name"/>
    <field name="inventor_id"/>
</search>

اگر هیچ نمای جستجویی برای مدل وجود نداشته باشد، Odoo یکی تولید می‌کند که فقط اجازهٔ جستجو روی فیلد name را می‌دهد.

Exercise

جستجوی دوره‌ها

اجازهٔ جستجوی دوره‌ها بر اساس عنوان یا توضیحات آن‌ها را بدهید.

روابط بین مدل‌ها

یک رکورد از یک مدل ممکن است به یک رکورد از مدل دیگری مرتبط باشد. برای مثال، یک رکورد سفارش فروش به یک رکورد کلاینت که حاوی داده‌های کلاینت است، مرتبط است؛ همچنین به رکوردهای خط سفارش فروش خود مرتبط است.

Exercise

ایجاد یک مدل session

برای ماژول Open Academy، یک مدل برای sessions در نظر می‌گیریم: یک session نمونه‌ای از یک دوره است که در زمان مشخصی برای مخاطبان مشخصی تدریس می‌شود.

یک مدل برای sessions ایجاد کنید. یک session دارای یک نام، یک تاریخ شروع، یک مدت زمان و تعداد صندلی است. یک اکشن و یک آیتم منو برای نمایش آن‌ها اضافه کنید. مدل جدید را از طریق یک آیتم منو قابل مشاهده کنید.

فیلدهای رابطه‌ای

فیلدهای رابطه‌ای رکوردها را به هم پیوند می‌دهند، چه از یک مدل (سلسله‌مراتب) و چه بین مدل‌های مختلف.

انواع فیلدهای رابطه‌ای عبارتند از:

Many2one(other_model, ondelete='set null')

یک پیوند ساده به یک شیء دیگر:

print(foo.other_id.name)

همچنین ببینید

کلیدهای خارجی

One2many(other_model, related_field)

یک رابطهٔ مجازی، معکوس یک Many2one. یک One2many به‌عنوان یک ظرف رکوردها رفتار می‌کند، دسترسی به آن منجر به یک مجموعهٔ (احتمالاً خالی) از رکوردها می‌شود:

for other in foo.other_ids:
    print(other.name)

خطر

از آنجایی که یک One2many یک رابطهٔ مجازی است، باید یک فیلد Many2one در other_model وجود داشته باشد، و نام آن باید related_field باشد

Many2many(other_model)

رابطهٔ چندگانهٔ دوطرفه، هر رکوردی در یک طرف می‌تواند به هر تعداد رکورد در طرف دیگر مرتبط باشد. به‌عنوان یک ظرف رکوردها رفتار می‌کند، دسترسی به آن نیز منجر به یک مجموعهٔ احتمالاً خالی از رکوردها می‌شود:

for other in foo.other_ids:
    print(other.name)

Exercise

روابط Many2one

با استفاده از یک many2one، مدل‌های Course و Session را اصلاح کنید تا رابطهٔ آن‌ها با سایر مدل‌ها را منعکس کنند:

  • یک دوره دارای یک کاربر responsible است؛ مقدار آن فیلد یک رکورد از مدل درون‌ساخت res.users است.

  • یک session دارای یک instructor است؛ مقدار آن فیلد یک رکورد از مدل درون‌ساخت res.partner است.

  • یک session با یک course مرتبط است؛ مقدار آن فیلد یک رکورد از مدل openacademy.course است و الزامی است.

  • نماها را سازگار کنید.

Exercise

روابط معکوس one2many

با استفاده از فیلد رابطه‌ای معکوس one2many، مدل‌ها را اصلاح کنید تا رابطهٔ بین دوره‌ها و session‌ها را منعکس کنند.

Exercise

روابط چندگانهٔ many2many

با استفاده از فیلد رابطه‌ای many2many، مدل Session را اصلاح کنید تا هر session را به مجموعه‌ای از attendees مرتبط کند. شرکت‌کنندگان توسط رکوردهای partner نمایش داده می‌شوند، بنابراین به مدل درون‌ساخت res.partner مرتبط خواهیم شد. نماها را بر این اساس سازگار کنید.

وراثت

ارث‌بری مدل

Odoo دو مکانیسم inheritance را برای گسترش یک مدل موجود به روش ماژولار ارائه می‌دهد.

اولین مکانیسم ارث‌بری به یک ماژول اجازه می‌دهد رفتار یک مدل تعریف‌شده در ماژول دیگر را اصلاح کند:

  • افزودن فیلدها به یک مدل،

  • بازنویسی تعریف فیلدها روی یک مدل،

  • افزودن محدودیت‌ها به یک مدل،

  • افزودن متدها به یک مدل،

  • بازنویسی متدهای موجود روی یک مدل.

مکانیسم ارث‌بری دوم (تفویض) اجازه می‌دهد هر رکوردی از یک مدل به یک رکورد در مدل والد پیوند داده شود، و دسترسی شفاف به فیلدهای رکورد والد را فراهم می‌کند.

../../_images/inheritance_methods1.png

همچنین ببینید

  • _inherit

  • _inherits

ارث‌بری نما

به‌جای اصلاح نماهای موجود در جای خود (با بازنویسی آن‌ها)، Odoo ارث‌بری نما را فراهم می‌کند که در آن نماهای «extension» فرزند روی نماهای ریشه اعمال می‌شوند، و می‌توانند محتوا را از والد خود اضافه یا حذف کنند.

یک نمای extension با استفاده از فیلد inherit_id به والد خود ارجاع می‌دهد، و به‌جای یک نمای منفرد، فیلد arch آن از هر تعداد عنصر xpath تشکیل شده است که محتوای نمای والد خود را انتخاب و تغییر می‌دهند:

<!-- improved idea categories list -->
<record id="idea_category_list2" model="ir.ui.view">
    <field name="name">id.category.list2</field>
    <field name="model">idea.category</field>
    <field name="inherit_id" ref="id_category_list"/>
    <field name="arch" type="xml">
        <!-- find field description and add the field
             idea_ids after it -->
        <xpath expr="//field[@name='description']" position="after">
          <field name="idea_ids" string="Number of ideas"/>
        </xpath>
    </field>
</record>
expr

یک عبارت XPath که یک عنصر منفرد در نمای والد را انتخاب می‌کند. اگر با هیچ عنصری یا بیش از یکی مطابقت نداشته باشد، خطا می‌دهد

position

عملیات قابل اعمال روی عنصر مطابقت‌یافته:

inside

بدنهٔ xpath را در انتهای عنصر مطابقت‌یافته الحاق می‌کند

replace

عنصر مطابقت‌یافته را با بدنهٔ xpath جایگزین می‌کند، و هر رخداد گرهٔ $0 در بدنهٔ جدید را با عنصر اصلی جایگزین می‌کند

before

بدنهٔ xpath را به‌عنوان یک هم‌رده قبل از عنصر مطابقت‌یافته درج می‌کند

after

بدنهٔ xpaths را به‌عنوان یک هم‌رده بعد از عنصر مطابقت‌یافته درج می‌کند

attributes

ویژگی‌های عنصر مطابقت‌یافته را با استفاده از عناصر ویژهٔ attribute در بدنهٔ xpath تغییر می‌دهد

نکته

هنگام مطابقت با یک عنصر منفرد، می‌توان ویژگی position را مستقیماً روی عنصری که قرار است یافت شود تنظیم کرد. هر دو ارث‌بری زیر نتیجهٔ یکسانی خواهند داد.

<xpath expr="//field[@name='description']" position="after">
    <field name="idea_ids" />
</xpath>

<field name="description" position="after">
    <field name="idea_ids" />
</field>

Exercise

تغییر محتوای موجود

  • با استفاده از ارث‌بری مدل، مدل Partner موجود را اصلاح کنید تا یک فیلد بولین instructor و یک فیلد many2many که با رابطهٔ session-partner مطابقت دارد، اضافه شود

  • با استفاده از ارث‌بری نما، این فیلدها را در نمای فرم partner نمایش دهید

دامنه‌ها

در Odoo، دامنه‌های جستجو مقادیری هستند که شرایط روی رکوردها را کدگذاری می‌کنند. یک دامنه یک فهرست از معیارهاست که برای انتخاب زیرمجموعه‌ای از رکوردهای یک مدل استفاده می‌شود. هر معیار یک سه‌گانه با یک نام فیلد، یک عملگر و یک مقدار است.

برای مثال، هنگامی که روی مدل Product استفاده شود، دامنهٔ زیر همهٔ services را با قیمت واحد بیش از 1000 انتخاب می‌کند:

[('product_type', '=', 'service'), ('unit_price', '>', 1000)]

به‌طور پیش‌فرض معیارها با یک AND ضمنی ترکیب می‌شوند. عملگرهای منطقی & (AND)، | (OR) و ! (NOT) را می‌توان برای ترکیب صریح معیارها استفاده کرد. آن‌ها در موقعیت پیشوندی استفاده می‌شوند (عملگر قبل از آرگومان‌های خود درج می‌شود به‌جای بین آن‌ها). برای مثال، برای انتخاب محصولاتی که «services هستند OR قیمت واحدی دارند که NOT بین 1000 و 2000 است»:

['|',
    ('product_type', '=', 'service'),
    '!', '&',
        ('unit_price', '>=', 1000),
        ('unit_price', '<', 2000)]

یک پارامتر domain می‌تواند به فیلدهای رابطه‌ای اضافه شود تا رکوردهای معتبر برای رابطه را هنگام تلاش برای انتخاب رکوردها در رابط کلاینت محدود کند.

Exercise

دامنه‌ها روی فیلدهای رابطه‌ای

هنگام انتخاب مدرس برای یک Session، تنها مدرسان (شرکایی که instructor آن‌ها روی True تنظیم شده است) باید قابل مشاهده باشند.

Exercise

دامنه‌های پیچیده‌تر

دسته‌بندی‌های جدید شرکا Teacher / Level 1 و Teacher / Level 2 را ایجاد کنید. مدرس یک جلسه می‌تواند یا یک مدرس باشد یا یک معلم (در هر سطحی).

فیلدهای محاسبه‌شده و مقادیر پیش‌فرض

تا کنون فیلدها مستقیماً در پایگاه‌داده ذخیره و از آن بازیابی می‌شدند. فیلدها می‌توانند computed (محاسبه‌شده) نیز باشند. در این حالت، مقدار فیلد از پایگاه‌داده بازیابی نمی‌شود بلکه به‌صورت آنی با فراخوانی یک متد از مدل محاسبه می‌شود.

برای ایجاد یک فیلد محاسبه‌شده، یک فیلد بسازید و ویژگی compute آن را به نام یک متد تنظیم کنید. متد محاسبه باید به‌سادگی مقدار فیلد را روی هر رکوردی در self تنظیم کند.

خطر

self یک مجموعه است

شیء self یک recordset است، یعنی یک مجموعهٔ مرتب از رکوردها. این شیء از عملیات استاندارد Python روی مجموعه‌ها مانند len(self) و iter(self) و همچنین عملیات اضافی روی مجموعه‌ها مانند recs1 + recs2 پشتیبانی می‌کند.

پیمایش روی self رکوردها را یک‌به‌یک می‌دهد، که هر رکورد خود یک مجموعهٔ به اندازهٔ ۱ است. شما می‌توانید با استفاده از نمادگذاری نقطه‌ای، مانند record.name، به فیلدهای رکوردهای منفرد دسترسی پیدا کنید یا به آن‌ها مقدار بدهید.

import random
from odoo import models, fields, api

class ComputedModel(models.Model):
    _name = 'test.computed'

    name = fields.Char(compute='_compute_name')

    def _compute_name(self):
        for record in self:
            record.name = str(random.randint(1, 1e6))

وابستگی‌ها

مقدار یک فیلد محاسبه‌شده معمولاً به مقادیر فیلدهای دیگر در رکورد محاسبه‌شده وابسته است. ORM انتظار دارد که توسعه‌دهنده این وابستگی‌ها را روی متد محاسبه با دکوراتور depends() مشخص کند. وابستگی‌های داده‌شده توسط ORM برای راه‌اندازی محاسبهٔ مجدد فیلد هر زمان که برخی از وابستگی‌های آن تغییر کرده باشد استفاده می‌شوند:

from odoo import models, fields, api

class ComputedModel(models.Model):
    _name = 'test.computed'

    name = fields.Char(compute='_compute_name')
    value = fields.Integer()

    @api.depends('value')
    def _compute_name(self):
        for record in self:
            record.name = "Record with value %s" % record.value

Exercise

فیلدهای محاسبه‌شده

  • درصد صندلی‌های اشغال‌شده را به مدل Session اضافه کنید

  • آن فیلد را در نماهای لیست و فرم نمایش دهید

  • فیلد را به‌صورت یک نوار پیشرفت نمایش دهید

مقادیر پیش‌فرض

به هر فیلدی می‌توان یک مقدار پیش‌فرض داد. در تعریف فیلد، گزینهٔ default=X را اضافه کنید که در آن X یا یک مقدار literal در Python (بولین، عدد صحیح، اعشاری، رشته) است، یا تابعی است که یک recordset را می‌گیرد و مقداری را برمی‌گرداند:

name = fields.Char(default="Unknown")
user_id = fields.Many2one('res.users', default=lambda self: self.env.user)

توجه

شیء self.env به پارامترهای درخواست و سایر چیزهای مفید دسترسی می‌دهد:

  • self.env.cr یا self._cr شیء cursor پایگاه‌داده است؛ این شیء برای پرس‌وجو از پایگاه‌داده استفاده می‌شود

  • self.env.uid یا self._uid شناسهٔ پایگاه‌دادهٔ کاربر فعلی است

  • self.env.user رکورد کاربر فعلی است

  • self.env.context یا self._context دیکشنری زمینه (context) است

  • self.env.ref(xml_id) رکورد متناظر با یک شناسهٔ XML را برمی‌گرداند

  • self.env[model_name] یک نمونه از مدل داده‌شده را برمی‌گرداند

Exercise

اشیاء فعال – مقادیر پیش‌فرض

  • مقدار پیش‌فرض start_date را به‌عنوان امروز تعریف کنید (به Date مراجعه کنید).

  • یک فیلد active به کلاس Session اضافه کنید و جلسات را به‌طور پیش‌فرض فعال تنظیم کنید.

Onchange

مکانیسم «onchange» راهی برای رابط کلاینت فراهم می‌کند تا هر زمان که کاربر مقداری را در یک فیلد وارد کرد، بدون ذخیرهٔ چیزی در پایگاه‌داده، فرم را به‌روزرسانی کند.

برای مثال، فرض کنید یک مدل سه فیلد amount، unit_price و price دارد، و می‌خواهید قیمت روی فرم را زمانی که هر یک از فیلدهای دیگر تغییر کرد به‌روزرسانی کنید. برای دستیابی به این هدف، یک متد تعریف کنید که در آن self رکورد را در نمای فرم نشان می‌دهد، و آن را با onchange() دکوره کنید تا مشخص کنید روی کدام فیلد باید فعال شود. هر تغییری که در self ایجاد کنید روی فرم منعکس خواهد شد.

<!-- content of form view -->
<field name="amount"/>
<field name="unit_price"/>
<field name="price" readonly="1"/>
# onchange handler
@api.onchange('amount', 'unit_price')
def _onchange_price(self):
    # set auto-changing field
    self.price = self.amount * self.unit_price
    # Can optionally return a warning and domains
    return {
        'warning': {
            'title': "Something bad happened",
            'message': "It was very bad indeed",
        }
    }

برای فیلدهای محاسبه‌شده، رفتار onchange ارزش‌گذاری‌شده درون‌ساخت است، همان‌طور که با کار با فرم Session قابل مشاهده است: تعداد صندلی‌ها یا شرکت‌کنندگان را تغییر دهید، و نوار پیشرفت taken_seats به‌طور خودکار به‌روزرسانی می‌شود.

Exercise

هشدار

یک onchange صریح اضافه کنید تا دربارهٔ مقادیر نامعتبر، مانند تعداد صندلی منفی، یا تعداد شرکت‌کنندگان بیشتر از تعداد صندلی، هشدار دهد.

محدودیت‌های مدل

Odoo دو راه برای راه‌اندازی ثوابت تأیید‌شده به‌صورت خودکار ارائه می‌دهد: Python constraints و SQL constraints. به همین ترتیب، می‌توانید SQL indexes پیچیده‌تری اضافه کنید.

یک محدودیت Python به‌صورت متدی تعریف می‌شود که با constrains() دکوره شده و روی یک recordset فراخوانی می‌شود. این دکوراتور مشخص می‌کند کدام فیلدها در محدودیت دخیل هستند، تا محدودیت زمانی که یکی از آن‌ها تغییر کرد به‌صورت خودکار ارزیابی شود. انتظار می‌رود متد در صورتی که ثابت آن برآورده نشود، یک استثنا ایجاد کند:

from odoo.exceptions import ValidationError

@api.constrains('age')
def _check_something(self):
    for record in self:
        if record.age > 20:
            raise ValidationError("Your record is too old: %s" % record.age)
    # all records passed the test, don't return anything

Exercise

افزودن محدودیت‌های Python

محدودیتی اضافه کنید که بررسی می‌کند مدرس در میان شرکت‌کنندگان جلسهٔ خودش حضور نداشته باشد.

محدودیت‌ها و ایندکس‌ها با استفاده از موارد زیر تعریف می‌شوند: Constraint، Index و UniqueIndex.

Exercise

افزودن محدودیت‌های SQL

با کمک PostgreSQL's documentation، محدودیت‌های زیر را اضافه کنید:

  1. بررسی کنید (CHECK) که توضیحات دوره و عنوان دوره متفاوت باشند

  2. نام دوره را UNIQUE (یکتا) کنید

Exercise

تمرین ۶ - افزودن گزینهٔ duplicate

از آنجایی که محدودیتی برای یکتایی نام دوره اضافه کردیم، دیگر امکان استفاده از تابع «duplicate» وجود ندارد (Form ↤ Duplicate).

متد «copy» خود را دوباره پیاده‌سازی کنید که اجازه می‌دهد شیء Course را تکرار کنید و نام اصلی را به «Copy of [original name]» تغییر دهید.

نماهای پیشرفته

نماهای فهرست

نماهای لیست می‌توانند ویژگی‌های تکمیلی بپذیرند تا رفتار آن‌ها بیشتر سفارشی شود:

decoration-{$name}

اجازه می‌دهد سبک متن یک ردیف را بر اساس ویژگی‌های رکورد متناظر تغییر دهید.

Values are Python expressions. For each record, the expression is evaluated with the record's attributes as context values and if true, the corresponding style is applied to the row. Here are some of the other values available in the context:

  • uid: the id of the current user,

  • today: the current local date as a string of the form YYYY-MM-DD,

  • now: same as today with the addition of the current time. This value is formatted as YYYY-MM-DD hh:mm:ss.

{$name} می‌تواند bf (font-weight: boldit (font-style: italic)، یا هر bootstrap contextual color (danger، info، muted، primary، success یا warning) باشد.

<list string="Idea Categories" decoration-info="state=='draft'"
    decoration-danger="state=='trashed'">
    <field name="name"/>
    <field name="state"/>
</list>
editable

یا "top" یا "bottom". نمای لیست را قابل ویرایش در همان مکان می‌کند (به‌جای اینکه مجبور به رفتن از طریق نمای فرم باشید)، مقدار، موقعیتی است که ردیف‌های جدید در آن ظاهر می‌شوند.

Exercise

رنگ‌آمیزی لیست

نمای لیست Session را به‌گونه‌ای تغییر دهید که جلساتی که کمتر از ۵ روز طول می‌کشند آبی رنگ شوند، و آن‌هایی که بیش از ۱۵ روز طول می‌کشند قرمز شوند.

تقویم‌ها

رکوردها را به‌عنوان رویدادهای تقویم نمایش می‌دهد. عنصر ریشهٔ آن‌ها <calendar> است و رایج‌ترین ویژگی‌های آن‌ها عبارتند از:

color

نام فیلد استفاده‌شده برای تقسیم‌بندی رنگ. رنگ‌ها به‌طور خودکار به رویدادها توزیع می‌شوند، اما رویدادهایی که در یک قسمت رنگی هستند (رکوردهایی که مقدار یکسانی برای فیلد @color خود دارند) رنگ یکسانی خواهند گرفت.

date_start

فیلد رکورد که تاریخ/زمان شروع رویداد را نگهداری می‌کند

date_stop (اختیاری)

فیلد رکورد که تاریخ/زمان پایان رویداد را نگهداری می‌کند

string

فیلد رکورد برای تعریف برچسب هر رویداد تقویم

<calendar string="Ideas" date_start="invent_date" color="inventor_id">
    <field name="name"/>
</calendar>

Exercise

نمای تقویم

یک نمای Calendar به مدل Session اضافه کنید که به کاربر اجازه می‌دهد رویدادهای مرتبط با Open Academy را مشاهده کند.

نماهای جستجو

عناصر <field> در نمای جستجو می‌توانند یک @filter_domain داشته باشند که دامنهٔ تولیدشده برای جستجو روی فیلد داده‌شده را بازنویسی می‌کند. در دامنهٔ داده‌شده، self مقدار وارد‌شده توسط کاربر را نشان می‌دهد. در مثال زیر، از آن برای جستجو روی هر دو فیلد name و description استفاده می‌شود.

نماهای جستجو همچنین می‌توانند حاوی عناصر <filter> باشند، که به‌عنوان کلیدهای تغییر وضعیت برای جستجوهای از پیش‌تعریف‌شده عمل می‌کنند. فیلترها باید یکی از ویژگی‌های زیر را داشته باشند:

domain

دامنهٔ داده‌شده را به جستجوی فعلی اضافه می‌کند

context

زمینه‌ای را به جستجوی فعلی اضافه می‌کند؛ از کلید group_by برای گروه‌بندی نتایج بر اساس نام فیلد داده‌شده استفاده کنید

<search string="Ideas">
    <field name="name"/>
    <field name="description" string="Name and description"
           filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"/>
    <field name="inventor_id"/>
    <field name="country_id" widget="selection"/>

    <filter name="my_ideas" string="My Ideas"
            domain="[('inventor_id', '=', uid)]"/>
    <group string="Group By">
        <filter name="group_by_inventor" string="Inventor"
                context="{'group_by': 'inventor_id'}"/>
    </group>
</search>

برای استفاده از یک نمای جستجوی غیرپیش‌فرض در یک action، باید آن را با استفاده از فیلد search_view_id رکورد action پیوند داد.

action همچنین می‌تواند مقادیر پیش‌فرض را برای فیلدهای جستجو از طریق فیلد context خود تنظیم کند: کلیدهای context به شکل search_default_field_name field_name را با مقدار ارائه‌شده مقداردهی اولیه می‌کنند. فیلترهای جستجو باید یک @name اختیاری داشته باشند تا مقدار پیش‌فرض داشته باشند و به‌صورت بولین رفتار کنند (آن‌ها فقط می‌توانند به‌صورت پیش‌فرض فعال شوند).

Exercise

نماهای جستجو

  1. یک دکمه اضافه کنید تا دوره‌هایی که کاربر فعلی مسئول آن‌ها است را در نمای جستجوی دوره فیلتر کند. آن را به‌صورت پیش‌فرض انتخاب‌شده قرار دهید.

  2. یک دکمه برای گروه‌بندی دوره‌ها بر اساس کاربر مسئول اضافه کنید.

گانت

هشدار

نمای gantt به ماژول web_gantt نیاز دارد که در نسخهٔ enterprise edition موجود است.

نمودارهای میله‌ای افقی که معمولاً برای نمایش برنامه‌ریزی و پیشرفت پروژه استفاده می‌شوند، عنصر ریشهٔ آن‌ها <gantt> است.

<gantt string="Ideas"
       date_start="invent_date"
       date_stop="date_finished"
       progress="progress"
       default_group_by="inventor_id" />

Exercise

نمودارهای Gantt

یک نمودار Gantt اضافه کنید که به کاربر اجازه می‌دهد زمان‌بندی جلسات مرتبط با ماژول Open Academy را مشاهده کند. جلسات باید بر اساس مدرس گروه‌بندی شوند.

نماهای گراف

نماهای گراف اجازهٔ نمای کلی و تحلیل تجمیعی مدل‌ها را می‌دهند، عنصر ریشهٔ آن‌ها <graph> است.

توجه

نماهای Pivot (عنصر <pivot>) یک جدول چندبعدی است، اجازهٔ انتخاب فیلترها و ابعاد را می‌دهد تا قبل از حرکت به نمای کلی گرافیکی‌تر، مجموعهٔ تجمیعی صحیح را دریافت کنید. نمای pivot همان تعریف محتوا را با نماهای graph به اشتراک می‌گذارد.

نماهای graph دارای ۴ حالت نمایش هستند، حالت پیش‌فرض با استفاده از ویژگی @type انتخاب می‌شود.

Bar (پیش‌فرض)

نمودار میله‌ای، بعد اول برای تعریف گروه‌ها روی محور افقی استفاده می‌شود، ابعاد دیگر میله‌های تجمیع‌شده در هر گروه را تعریف می‌کنند.

به‌طور پیش‌فرض میله‌ها در کنار هم قرار دارند، می‌توان با استفاده از @stacked="True" روی <graph> آن‌ها را روی هم قرار داد

Line

نمودار خطی ۲ بعدی

Pie

نمودار دایره‌ای ۲ بعدی

Graph views contain <field> with a mandatory @type attribute taking the values:

row (default)

فیلد به‌طور پیش‌فرض باید تجمیع شود

measure

فیلد باید تجمیع شود به‌جای اینکه روی آن گروه‌بندی شود

<graph string="Total idea score by Inventor">
    <field name="inventor_id"/>
    <field name="score" type="measure"/>
</graph>

هشدار

Graph views perform aggregations on database values, they do not work with non-stored computed fields.

Exercise

نمای گراف

Add a Graph view in the Session object that displays, for each course, the number of attendees under the form of a bar chart.

کانبان

Used to organize tasks, production processes, etc… their root element is <kanban>.

A kanban view shows a set of cards possibly grouped in columns. Each card represents a record, and each column the values of an aggregation field.

For instance, project tasks may be organized by stage (each column is a stage), or by responsible (each column is a user), and so on.

Kanban views define the structure of each card as a mix of form elements (including basic HTML) and QWeb Templates.

Exercise

نمای کانبان

Add a Kanban view that displays sessions grouped by course (columns are thus courses).

امنیت

Access control mechanisms must be configured to achieve a coherent security policy.

مکانیسم‌های کنترل دسترسی مبتنی بر گروه

Groups are created as normal records on the model res.groups, and granted menu access via menu definitions. However even without a menu, objects may still be accessible indirectly, so actual object-level permissions (read, write, create, unlink) must be defined for groups. They are usually inserted via CSV files inside modules. It is also possible to restrict access to specific fields on a view or object using the field's groups attribute.

حقوق دسترسی

Access rights are defined as records of the model ir.model.access. Each access right is associated to a model, a group (or no group for global access), and a set of permissions: read, write, create, unlink. Such access rights are usually created by a CSV file named after its model: ir.model.access.csv.

id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_idea_idea,idea.idea,model_idea_idea,base.group_user,1,1,1,0
access_idea_vote,idea.vote,model_idea_vote,base.group_user,1,1,1,0

Exercise

افزودن کنترل دسترسی از طریق رابط Odoo

Create a new user "John Smith". Then create a group "OpenAcademy / Session Read" with read access to the Session model.

Exercise

افزودن کنترل دسترسی از طریق فایل‌های داده در ماژول شما

با استفاده از فایل‌های داده،

  • Create a group OpenAcademy / Manager with full access to all OpenAcademy models

  • Session و Course را برای همهٔ کاربران قابل خواندن کنید

قواعد رکورد

A record rule restricts the access rights to a subset of records of the given model. A rule is a record of the model ir.rule, and is associated to a model, a number of groups (many2many field), permissions to which the restriction applies, and a domain. The domain specifies to which records the access rights are limited.

Here is an example of a rule that prevents the deletion of leads that are not in state cancel. Notice that the value of the field groups must follow the same convention as the method write() of the ORM.

<record id="delete_cancelled_only" model="ir.rule">
    <field name="name">Only cancelled leads may be deleted</field>
    <field name="model_id" ref="crm.model_crm_lead"/>
    <field name="groups" eval="[(4, ref('sales_team.group_sale_manager'))]"/>
    <field name="perm_read" eval="0"/>
    <field name="perm_write" eval="0"/>
    <field name="perm_create" eval="0"/>
    <field name="perm_unlink" eval="1" />
    <field name="domain_force">[('state','=','cancel')]</field>
</record>

Exercise

قاعدهٔ رکورد

Add a record rule for the model Course and the group "OpenAcademy / Manager", that restricts write and unlink accesses to the responsible of a course. If a course has no responsible, all users of the group must be able to modify it.

ویزاردها

Wizards describe interactive sessions with the user (or dialog boxes) through dynamic forms. A wizard is simply a model that extends the class TransientModel instead of Model. The class TransientModel extends Model and reuse all its existing mechanisms, with the following particularities:

  • Wizard records are not meant to be persistent; they are automatically deleted from the database after a certain time. This is why they are called transient.

  • Wizard records may refer to regular records or wizard records through relational fields(many2one or many2many), but regular records cannot refer to wizard records through a many2one field.

We want to create a wizard that allow users to create attendees for a particular session, or for a list of sessions at once.

Exercise

ویزارد را تعریف کنید

Create a wizard model with a many2one relationship with the Session model and a many2many relationship with the Partner model.

راه‌اندازی ویزاردها

Wizards are simply window actions with a target field set to the value new, which opens the view (usually a form) in a separate dialog. The action may be triggered via a menu item, but is more generally triggered by a button.

An other way to launch wizards is through the Action menu of a list or form view. This is done through the binding_model_id field of the action. Setting this field will make the action appear on the views of the model the action is "bound" to.

<record id="launch_the_wizard" model="ir.actions.act_window">
    <field name="name">Launch the Wizard</field>
    <field name="res_model">wizard.model.name</field>
    <field name="view_mode">form</field>
    <field name="target">new</field>
    <field name="binding_model_id" ref="model_context_model_ref"/>
</record>

نکته

While wizards use regular views and buttons, normally clicking any button in a form would first save the form then close the dialog. Because this is often undesirable in wizards, a special attribute special="cancel" is available which immediately closes the wizard without saving the form.

Exercise

ویزارد را راه‌اندازی کنید

  1. یک نمای فرم برای ویزارد تعریف کنید.

  2. action برای راه‌اندازی آن را در زمینهٔ مدل Session اضافه کنید.

  3. Define a default value for the session field in the wizard; use the context parameter self._context to retrieve the current session.

Exercise

ثبت‌نام شرکت‌کنندگان

Add buttons to the wizard, and implement the corresponding method for adding the attendees to the given session.

Exercise

ثبت‌نام شرکت‌کنندگان در چند جلسه

Modify the wizard model so that attendees can be registered to multiple sessions.

بین‌المللی‌سازی

Each module can provide its own translations within the i18n directory, by having files named LANG.po where LANG is the locale code for the language, or the language and country combination when they differ (e.g. pt.po or pt_BR.po). Translations will be loaded automatically by Odoo for all enabled languages. Developers always use English when creating a module, then export the module terms using Odoo's gettext POT export feature (Settings ↤ Translations ↤ Import/Export ↤ Export Translation without specifying a language), to create the module template POT file, and then derive the translated PO files. Many IDE's have plugins or modes for editing and merging PO/POT files.

نکته

The Portable Object files generated by Odoo are published on Odoo's Translations Platform, making it easy to translate the software.

|- idea/ # The module directory
   |- i18n/ # Translation files
      | - idea.pot # Translation Template (exported from Odoo)
      | - fr.po # French translation
      | - pt_BR.po # Brazilian Portuguese translation
      | (...)

نکته

By default Odoo's POT export only extracts labels inside XML files or inside field definitions in Python code, but any Python string can be translated this way by surrounding it with the function odoo._() (e.g. _("Label"))

Exercise

ترجمهٔ یک ماژول

Choose a second language for your Odoo installation. Translate your module using the facilities provided by Odoo.

گزارش‌گیری

گزارش‌های چاپ‌شده

Odoo uses a report engine based on QWeb Templates, Twitter Bootstrap and Wkhtmltopdf.

یک گزارش ترکیبی از دو عنصر است:

  • an ir.actions.report which configures various basic parameters for the report (default type, whether the report should be saved to the database after generation,…)

    <record id="account_invoices" model="ir.actions.report">
        <field name="name">Invoices</field>
        <field name="model">account.invoice</field>
        <field name="report_type">qweb-pdf</field>
        <field name="report_name">account.report_invoice</field>
        <field name="report_file">account.report_invoice</field>
        <field name="attachment_use" eval="True"/>
        <field name="attachment">(object.state in ('open','paid')) and
            ('INV'+(object.number or '').replace('/','')+'.pdf')</field>
        <field name="binding_model_id" ref="model_account_invoice"/>
        <field name="binding_type">report</field>
    </record>
    

    نکته

    Because it largerly a standard action, as with ویزاردها it is generally useful to add the report as a contextual item on the list and / or form views of the model being reported on via the binding_model_id field.

    Here we are also using binding_type in order for the report to be in the report contextual menu rather than the action one. There is no technical difference but putting elements in the right place helps users.

  • A standard QWeb view for the actual report:

    <t t-call="web.html_container">
        <t t-foreach="docs" t-as="o">
            <t t-call="web.external_layout">
                <div class="page">
                    <h2>Report title</h2>
                </div>
            </t>
        </t>
    </t>
    

    the standard rendering context provides a number of elements, the most important being:

    docs

    رکوردهایی که گزارش برای آن‌ها چاپ می‌شود

    user

    کاربری که گزارش را چاپ می‌کند

Because reports are standard web pages, they are available through a URL and output parameters can be manipulated through this URL, for instance the HTML version of the Invoice report is available through http://localhost:8069/report/html/account.report_invoice/1 (if account is installed) and the PDF version through http://localhost:8069/report/pdf/account.report_invoice/1.

خطر

If it appears that your PDF report is missing the styles (i.e. the text appears but the style/layout is different from the html version), probably your wkhtmltopdf process cannot reach your web server to download them.

If you check your server logs and see that the CSS styles are not being downloaded when generating a PDF report, most surely this is the problem.

The wkhtmltopdf process will use the web.base.url system parameter as the root path to all linked files, but this parameter is automatically updated each time the Administrator is logged in. If your server resides behind some kind of proxy, that could not be reachable. You can fix this by adding one of these system parameters:

  • report.url, pointing to an URL reachable from your server (probably http://localhost:8069 or something similar). It will be used for this particular purpose only.

  • web.base.url.freeze, when set to True, will stop the automatic updates to web.base.url.

Exercise

ایجاد یک گزارش برای مدل Session

For each session, it should display session's name, its start and end, and list the session's attendees.

داشبوردها

Exercise

تعریف یک داشبورد

Define a dashboard containing the graph view you created, the sessions calendar view and a list view of the courses (switchable to a form view). This dashboard should be available through a menuitem in the menu, and automatically displayed in the web client when the OpenAcademy main menu is selected.

1

it is possible to disable the automatic creation of some fields

2

writing raw SQL queries is possible, but requires care as it bypasses all Odoo authentication and security mechanisms.