Buidling a Odoo Module

Cảnh báo

Bạn nên sử dụng tài liệu này sau khi đã cài đặt thành công Odoo.

Khởi động/Dừng máy chủ Odoo

Odoo sử dụng kiến ​​trúc Client/Server, trong đó Client là các trình duyệt web truy cập tới Odoo Server thông qua RPC (Tìm hiểu thêm về RPC). Các phần mở rộng và logic nghiệp vụ thường được thực hiện ở phía Server mặc dù một số tính năng cũng có thể được thực hiện ở phía Client (ví dụ như việc trình diễn dữ liệu mới như là bản đồ tương tác). Để khởi động máy chủ, chúng ta cần thực hiện lệnh odoo-bin trong màn hình shell, odoo-bin là một file thực thi, bạn phải thêm đầy đủ đường dẫn tới nó nếu cần:

odoo-bin

Dừng máy chủ bằng cách nhấn Ctrl-C hai lần hoặc bằng cách xóa tiến trình tương ứng trên hệ điều hành.

Xây dựng một Odoo Module

Các phần mở rộng của cả server và client được đóng gói dưới dạng các modules được nạp sẵn trong cơ sở dữ liệu. Các modules được tạo nhằm mục đích thay đổi hoặc bổ sung logic nghiệp vụ hiện có:

Ví dụ: Một module được tạo ra để thêm các quy tắc kế toán của Việt Nam vào hỗ trợ kế toán chung của Odoo, tương tự chúng ta cũng có thể tạo ra một module khác để tích hợp thanh toán với các ngân hàng nội địa tại Việt Nam vào hệ thống Odoo.

Mọi thứ trong Odoo đều được bắt đầu và kết thúc bằng các module.

Các thành phần của một module

Business Objects

Được khai báo là các lớp Python, các tài nguyên này được Odoo tự động duy trì dựa trên cấu hình của chúng.

Object views

Định nghĩa các đối tượng giao diện người dùng.

Data files

Các file XML hoặc CSV với các chức năng:

  • Định nghĩa các view hiển thị hoặc báo cáo.

  • Cấu hình dữ liệu, cấu hình các quy tắc bảo mật.

  • v.v,...

Web controllers

Xử lý các yêu cầu từ trình duyệt.

Static web data

Các file Images, CSS or Javascript được sử dụng trên giao diện web hoặc trang web.

Cấu trúc của một Module

Mỗi module là một thư mục nằm trong thư mục (addons) chứa nhiều module. Các module được chỉ định bằng cách sử dụng tùy chọn --addons-path.

Một module được tổ chức với các thư mục như sau:

  • controllers: chứa các controllers (http routes).

  • data: data xml.

  • demo: demo xml.

  • i18n: chứa các file dịch.

  • security: chứa các config phân quyền.

  • models: model definitions.

  • reports: chứa các model báo cáo (BI/analysis), webkit report templates.

  • static: bao gồm web assets, thư mục css/, js/, img/,.

  • templates: chứa các web templates.

  • views: bao gồm các views và các mẫu in QWeb report.

  • wizards: wizard model và views.

  • manifest: bao gồm các thông tin cấu hình module.

Để tạo nhanh một module với đầy đủ các thư mục cần thiết chúng ta có thể sử dụng lệnh scaffold được Odoo cung cấp.

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

Quan hệ giữa các đối tượng

Một thành phần quan trọng của Odoo là lớp ORM. Lớp này được Odoo cung cấp, và có đầy đủ các thao tác với cơ sở dữ liệu. Chúng ta nên sử dụng ORM và hạn chế sử dụng query khi truy cập vào cơ sở dữ liệu. Các đối tượng nghiệp vụ được khai báo dưới dạng các lớp Python kế thừa từ lớp Model do Odoo định nghĩa.

Ví dụ về một class trong Odoo.

from odoo import models

class EducationClass(models.Model):
    _name = 'education.class'
    _description = 'Education Class'

_name là một trong các thuộc tính cơ bản khi khai báo một lớp đối tượng trong Odoo. Đây là thuộc tính bắt buộc phải có khi khai báo một class. _description là một thuộc tính cơ bản, thuộc tính này có chức năng mô tả cho model. Đây là thuộc tính không bắt buộc phải khai báo. Ví dụ trên sẽ tạo ra một bảng có tên là education_class trong cơ sở dữ liệu.

Các trường của Model

from odoo import models, fields

class EducationClass(models.Model):
    _name = 'education.class'
    _description = 'Education Class'

    name = fields.Char(string='Name', required=True)

Các trường được định nghĩa là các thuộc tính của model class, được sử dụng để xác định những gì được lưu trữ và lưu trữ ở đâu trong cơ sở dữ liệu.

Các thuộc tính chung

Để khai báo các thuộc tính cho các trường của model chúng ta có thể làm như sau:

name = field.Char(required=True)

Các attributes cơ bản của một trường trong Odoo:

string (unicode, default: field’s name)

Nhãn của trường.

required (bool, default: False)

Nếu required=True thì bắt buộc người dùng phải nhập dữ liệu vào trường này.

help (unicode, default: '')

Giải thích, hướng dẫn cho trường dữ liệu này.

index (bool, default: False)

Thiết lập chế độ indexing trên một trường / cột (column) trong cơ sở dữ liệu. (Chỉ áp dụng với các trường được lưu trữ trong cơ sở dữ liệu).

Các trường cơ bản

Một số trường cơ bản trong Odoo như: Boolean, Char, Integer, Float.

Các trường dành riêng của Odoo

Odoo tự động tạo ra một số trường khi khai báo model, nó có thể được sử dụng khi cần thiết:

id (Id)

Giá trị nhận dạng duy nhất của một bản ghi trong model.

create_date (Datetime)

Ngày tạo của bản ghi.

create_uid (Many2one)

Người tạo ra bản ghi.

write_date (Datetime)

Ngày sửa bản ghi gần nhất.

write_uid (Many2one)

Người sửa bản ghi gần nhất.

Các tệp dữ liệu

Mẹo

Một số module được tạo ra chỉ để thêm dữ liệu vào Odoo.

Dữ liệu của module được khai báo thông qua tệp dữ liệu (data files). Mỗĩ thẻ <record> sẽ tạo hoặc cập nhật một bản ghi trong cơ sở dữ liệu.

<record id="education_class_view_tree" model="ir.ui.view">
    <field name="name">education.class.tree</field>
    <field name="model">education.class</field>
    <field name="arch" type="xml">
        <tree string="Class Tree">
            <field name="name"/>
        </tree>
    </field>
</record>
  • model là tên model của bản ghi.

  • id là một id định danh cho bản ghi, nó cho phép tham chiếu đến bản ghi.

  • field là tên của field trong model.

  • Tệp dữ liệu (data files) thường được lưu trữ trong thư mục data và phải được khai báo trong manifest để tải dữ liệu.

  • Các tệp dữ liệu lưu trữ trong thư mục demo và chỉ được tải dữ liệu trong chế độ demonstration.

Mẹo

Nội dung của tệp dữ liệu chỉ được nạp khi mudule được cài đặt hoặc cập nhật.

Actions và Menus

Các actions và menu là các bản ghi thông thường của model ir.actions.act_window trong cơ sở dữ liệu, thường được khai báo thông qua các tệp dữ liệu. Các actions có thế được kích hoạt theo ba cách sau:

  1. Bằng cách nhấn vào các mục menu.

  2. Bằng cách nhấn vào các nút trong giao diện (nếu chúng được liên kết với các actions).

  3. Dưới dạng các hành động theo ngữ cảnh trên đối tượng.

<record id="education_class_action" model="ir.actions.act_window">
    <field name="name">Class</field>
    <field name="res_model">education.class</field>
    <field name="view_mode">tree,form</field>
</record>

<menuitem id="education_class_menu" action="education_class_action" parent="education_configuration_menu_root" sequence="10"/>

Nguy hiểm

Trong Odoo tệp dữ liệu XML được thực thi tuần tự nên chúng ta phải khai báo action trước rồi mới khai báo menuitem tương ứng của nó.

View cơ bản

Các views xác định cách các bản ghi của một model được hiển thị. Mỗi một loại view đại diện cho một giao diện trực quan riêng (danh sách bản ghi, biểu đồ tổng hợp). Đối với các yêu cầu chung, giao diện với kiểu chính xác nhất và giá trị priority thấp nhất sẽ được sử dụng (do đó giao diện có priority thấp nhất của mỗi loại sẽ là giao diện mặc định cho loại đó).

Kế thừa views cho phép thay đổi các giao diện được khai báo ở những nơi khác (có thể thêm hoặc bớt nội dung trên các view đó).

Khai báo Generic view

Khai báo giao diện chung. Mỗi view được khai báo như một bản ghi của model ir.ui.view và nó được định nghĩa trong phần thân của field 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>, <tree>, <graph>, ... -->
    </field>
</record>

Nguy hiểm

Nội dung của view được viết bằng XML. Do đó chúng ta phải khai báo field arch với type="xml".

Tree views

Tree views được gọi là giao diện dạng danh sách, hiển thị tất cả các bản ghi dưới dạng bảng (mỗi dòng tương ứng với một bản ghi, mỗi cột tương ứng là một trường). Để khai báo tree views chúng ta bắt đầu với thẻ <tree>.

<tree string="Class Tree">
    <field name="name"/>
    <field name="class_group_id"/>
    <field name="next_class_id"/>
    <field name="school_id"/>
</tree>

Form views

Form views được sử dụng để tạo và chỉnh sửa từng bản ghi đơn lẻ. Form view bao gồm các thẻ cấu trúc cấp cao (groups, notebooks) và các phần tử tương tác (các nút và trường). Để khai báo form views chúng ta bắt đầu với thẻ <Form>.

<form string="Class Form">
    <sheet>
        <widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}" />
        <field name="active" invisible="1" />
        <group>
            <group>
                <field name="name" />
                <field name="school_id" />
                <field name="class_group_id" />
            </group>
            <group>
                <field name="next_class_id" />
                <field name="company_id" groups="base.group_multi_company"/>
            </group>
        </group>
    </sheet>
</form>

Form views cũng có thể sử dụng HTML thuần túy để có bố cục linh hoạt hơn:

<form string="Student Form">
    <header>
        <field name="state" widget="statusbar" />
    </header>
    <sheet>
        <field name="image_1920" widget="image" class="oe_avatar" options='{"preview_image": "image_128"}' />
        <div class="oe_title">
            <h1>
                <field name="name" placeholder="Full Name" required="True" />
            </h1>
        </div>
        <group>
            <group>
                <field name="student_code" />
                <field name="phone" widget="phone" />
                <field name="mobile" widget="phone" />
                <field name="email" widget="email" context="{'gravatar_image': True}" />
                <field name="dob" />
                <field name="gender" />
                <field name="class_group_id" />
                <field name="class_id" />
                <field name="responsible_id" />
            </group>
        </group>
        <notebook>
            <page name="general" string="General Information">
                <group>
                    <group>
                        <field name="student_level_id" />
                    </group>
                    <group>
                        <field name="school_id" />
                        <field name="company_id" groups="base.group_multi_company" />
                    </group>
                </group>
            </page>
            <page name="documents" string="Attached Documents">
                <group>
                    <field name="attachment_ids" widget="many2many_binary" />
                </group>
            </page>
        </notebook>
    </sheet>
</form>

Search views

Giao diện tìm kiếm tùy chỉnh trường tìm kiếm được liên kết với giao diện danh sách (và các giao diện tổng hợp khác). Để khai báo Search views chúng ta bắt đầu với thẻ search, search views giúp chúng ta xác định các trường nào có thể được tìm kiếm.

<search string="Class Search">
    <field name="name" />
    <field name="school_id" />
    <field name="class_group_id" />
</search>

Nếu không khai báo giao diện tìm kiếm nào cho model thì Odoo sẽ tự động tạo ra một giao diện chỉ cho phép tìm kiếm các bản ghi với trường name.

Mối quan hệ giữa các model

Một bản ghi từ model này có thể liên kết đến một bản ghi ở model khác.

Ví dụ: Model lớp học với model học sinh thì giữa chúng có mối quan hệ là một lớp học có nhiều học sinh.

Các trường quan hệ

Các trường quan hệ liên kết các bản ghi trên cùng một model (cấu trúc phân cấp) hoặc giữa các model khác nhau. Các loại trường quan hệ là:

Quan hệ tới một đối tượng khác (khóa ngoại).

Many2one(other_model, ondelete='set null')

  • other_model: tên (_name) của đối tượng liên kết (bắt buộc phải có).

  • ondelete: cho hệ thống biết sẽ làm gì khi xóa dữ liệu trong bảng dữ liệu của đối tượng mà trường này liên kết tới.

  • ondelete có thể nhận các giá trị : cascade, restrict, set null.

from odoo import fields, models

class EducationClass(models.Model):
    _name = 'education.class'
    _description = 'Education Class'

    class_group_id = fields.Many2one('education.class.group', string='Class Group', ondelete='restrict')
    next_class_id = fields.Many2one('education.class', string='Next Class')

Quan hệ một nhiều tới đối tượng khác (ngược lại với many2one).

One2many(other_model, related_field)

  • other_model: tên (_name) của đối tượng liên kết (bắt buộc phải có).

  • related_field: tên trường kiểu many2one liên kết đến đối tượng chứa trường đang khai báo này và bắt buộc phải có.

from odoo import fields, models

class EductionClassGroup(models.Model):
    _name = 'education.class.group'
    _description = 'Education Class Group'
    _order = 'sequence'

    class_ids = fields.One2many('education.class', 'class_group_id', string='Class of Groups')

Quan hệ nhiều nhiều giữa các đối tượng.

Many2many(other_model)

  • other_model: tên (_name) của đối tượng liên kết (bắt buộc phải có).

Nếu chỉ khai báo như trên thì khi đó odoo sẽ tự động tạo ra bảng dữ liệu quan hệ trung gian giữa 2 đối tượng với tên mặc định.

Đối với trường Many2many có thể khai báo đầy đủ hơn với cấu trúc.

Many2many(other_model, rel, field1, field2)

  • rel: tên bảng dữ liệu quan hệ trung gian giữa 2 đối tượng.

  • field1: tên của cột trong bảng dữ liệu, chứa id của đối tượng hiện tại (bắt buộc phải có).

  • field2: tên của cột trong bảng dữ liệu, chứa id của đối tượng liên kết (bắt buộc phải có).

attachment_ids = fields.Many2many('ir.attachment', 'education_student_ir_attachment_rel',
                                'attachment_id', 'student_id', string='Attachments')

Kế thừa

Kế thừa Model

Odoo cung cấp hai cơ chế kế thừa để mở rộng một model hiện có.

  • Cơ chế kế thừa thứ nhất (Traditional Inheritance):

    1. Class Inheritance: Kiểu kế thừa này cho phép chúng ta mở rộng thêm các trường mới vào model, ghi đè lại định nghĩa của các trường, chỉnh sửa các phương thức trong model đã có, và tổ chức database lưu trữ chung một bảng với model gốc.

    2. Prototype Inheritance: Kiểu kế thừa này sẽ sao chép các trường, các phương thức trong model đã có và được lưu trữ trên bảng mới trong cơ sở dữ liệu.

  • Cơ chế kế thừa thứ hai (Delegation Inheritance):

    Cho phép đa kế thừa, không kế thừa lại các phương thức. Kiểu kế thừa này sẽ tạo bảng mới khác với bảng của model gốc và model mới có field liên kết đến model gốc

Kế thừa View

Thay vì sửa đổi các giao diện hiện có bằng cách ghi đè, Odoo cung cấp tính năng cho phép kế thừa các giao diện hiện có và có thể thêm hoặc xóa nội dung khỏi giao diện gốc của chúng. Các view kế thừa tham chiếu đến view gốc bằng cách sử dụng trường inherit_id. Khi sử dụng kế thừa view, các chỉnh sửa bổ sung sẽ nằm gọn trong module mới mà chúng ta đang phát triển, mà không phải sửa trực tiếp trên module gốc. Khi gỡ bỏ module mới đồng thời sẽ gỡ bỏ các kế thừa, khi đó view sẽ trở về nguyên trạng.

<record id="education_student_view_form" model="ir.ui.view">
    <field name="name">education.student.inherit</field>
    <field name="model">education.student</field>
    <field name="inherit_id" ref="viin_education.education_student_view_form" />
    <field name="arch" type="xml">
        <xpath expr="//field[@widget='image']" position="before">
            <div class="oe_button_box" name="button_box">
                <button name="action_view_opportunity_student" type="object" class="oe_stat_button" icon="fa-inbox">
                    <div class="o_stat_info">
                        <field name="opportunity_student_count" class="o_stat_value" />
                        <span class="o_stat_text">Opportunities</span>
                    </div>
                </button>
            </div>
        </xpath>
    </field>
</record>

XPath được sử dụng để tìm vị trí của bất kỳ phần tử nào trên view. Chúng ta sử dụng thẻ xpath để thay đổi nội dung trên field gốc. Tìm hiểu thêm về Xpath

Cú pháp: Xpath=//tagname[@attribute='value']

  • Biểu thức xpath được sử dụng linh hoạt với thuộc tính position sau.

inside: Thêm nội dung vào cuối phần tử xpath.

replace: Thay thế nội dung của phần tử xpath, hạn chế dùng trừ trường hợp thật sự cần thiết.

before: Thêm nội dung vào phía trước phần tử xpath.

after: Thêm nội dung vào phía sau phần tử xpath.

attributes: Thay đổi attribute của phần tử xpath bằng attribute trong phần thân của thẻ xpath.

Mẹo

Hai cách kế thừa sau sẽ cho cùng một kết quả:

<xpath expr="//field[@name='class_id']" position="after">
    <field name="final_year" />
</xpath>
<field name="class_id" position="after">
    <field name="final_year"/>
</field>

Domain

Trong Odoo, Search domains (miền tìm kiếm) là các giá trị mã hóa các điều kiện trên bản ghi. Domain là danh sách các tiêu chí được sử dụng để chọn một tập hợp con các bản ghi của model. Mỗi tiêu chí là một bộ ba với tên trường, toán tử và giá trị.

Ví dụ:

[('state', '=', 'confirmed')]

Theo mặc định, nếu các tiêu chí được ngăn cách với nhau bởi dấu phẩy thì chúng được ngầm định hiểu là biểu thức AND. Các toán tử logic & (AND), | (Hoặc) và ! (KHÔNG) có thể được sử dụng để kết hợp các tiêu chí một cách rõ ràng.

Domain có thể được thêm vào các trường quan hệ để lấy ra giới hạn các bản ghi hợp lệ cho mối quan hệ khi cố gắng chọn các bản ghi trên giao diện client.

Các trường được tính toán và giá trị mặc định

Các trường có thể được tính toán lại (thay vì đọc trực tiếp từ cơ sở dữ liệu) bằng cách sử dụng tham số tính toán. Khi đó, giá trị của trường không được truy xuất từ ​​cơ sở dữ liệu mà được tính toán bằng cách gọi một phương thức compute của model. Để tạo một trường được tính toán, chúng ta tạo một trường bình thường và đặt thuộc tính compute của nó thành tên của một phương thức. Phương thức compute sẽ tính toán lại giá của trường trên mọi bản ghi trong self.

Nguy hiểm

self là một tập bản ghi có thứ tự. Nó hỗ trợ các phép toán Python tiêu chuẩn trên collections, ví dụ như len(self), iter(self) và các hoạt động tập hợp bổ sung như recs1 + resc2.

Khi sử dụng vòng lặp (ví dụ dùng vòng lặp for) để lấy từng bản ghi trên self, chúng ta có thể truy cập / gán các trường trên các bản ghi đơn lẻ bằng cách sử dụng ký hiệu dấu chấm, như record.name.

from odoo import fields, models

class EducationClass(models.Model):
    _name = 'education.class'
    _description = 'Education Class'

    name = fields.Char(string='Name', required=True, compute='_compute_name')

    def _compute_name(self):
        for r in self:
            r.name = r.name.upper()

Sự phụ thuộc

Giá trị của một trường được tính toán thường phụ thuộc vào giá trị của các trường khác trên bản ghi được tính toán. Nếu mong muốn trường compute đó được tính toán lại khi các trường khác (các trường được chỉ định) có sự thay đổi giá trị thì nó phải chỉ định các trường đó bằng cách sử dụng depend(). Các phần phụ thuộc đã cho được ORM sử dụng để kích hoạt tính toán lại trường bất cứ khi nào một số phần phụ thuộc của nó được sửa đổi:

Hàm compute sẽ được gọi khi giá trị của các trường depends có sự thay đổi.

from odoo import models, fields, api

class EducationAdmission(models.Model):
    _inherit = 'education.admission'

    lead_ids = fields.One2many('crm.lead', 'admission_id', string='Opportunities')
    leads_count = fields.Integer(string='Number Of Leads', compute='_compute_leads_count')

    @api.depends('lead_ids')
    def _compute_leads_count(self):
        for r in self:
            r.leads_count = len(r.lead_ids)

Các phụ thuộc (dependencies) có thể là các đường dẫn chấm khi sử dụng các trường con:

@api.depends('student_ids.student_id.state')
def _compute_studying_student_count(self):
    for r in self:
        studying_student_count = 0
        if r.student_ids:
            students = r.student_ids.filtered(lambda s: s.student_id.state == 'studying')
            studying_student_count = len(students)
        r.studying_student_count = studying_student_count

Các trường tính toán không được lưu trữ mặc định trong cơ sở dữ liệu, chúng được tính toán và trả về giá trị khi được yêu cầu. Thêm store=True sẽ lưu trữ chúng trong cơ sở dữ liệu và tự động cho phép tìm kiếm. Các trường compute thì mặc định là trường readonly. Để có thể sửa được giá trị của trường đó trên form thì chúng ta sử dụng thuộc tính readonly và gán giá trị bằng False.

Giá trị mặc định

Trong Odoo bất kì trường nào cũng có thể được cung cấp giá trị mặc định. Khi định nghĩa trường chúng ta thêm tùy chọn default = X để gán giá trị mặc định, trong đó X là một giá trị Python (Boolean, Integer, Float, Char) hoặc định nghĩa một hàm lấy một tập bản ghi và trả về một giá trị:

start_date = fields.Date(string='Start Date', default=fields.Date.today)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)

Ghi chú

Đối tượng self.env cấp quyền truy cập vào các tham số yêu cầu và những thứ hữu ích khác:

  • self.env.cr hoặc self._cr là đối tượng con trỏ cơ sở dữ liệu, nó được sử dụng để truy vấn cơ sở dữ liệu.

  • self.env.uid hoặc self._uid là id cơ sở dữ liệu của người dùng hiện tại.

  • self.env.user là hồ sơ của người dùng hiện tại.

  • self.env.context hoặc self._context là từ điển ngữ cảnh.

  • self.env.ref(xml_id) trả về bản ghi tương ứng với một id XML.

  • self.env[model_name] trả về một instance của model.

Onchange

Cơ chế onchange cung cấp một cách để giao diện người dùng cập nhật dữ liệu trên view form bất cứ khi nào người dùng thay đổi dữ liệu của một trường mà không cần lưu bất kỳ thứ gì vào cơ sở dữ liệu.

<field name="ethnicity_id" placeholder="Ethnicity"/>
@api.onchange('ethnicity_id')
def _onchange_ethnicity_id(self):
    if self.ethnicity_id:
        self.country_id = self.ethnicity_id.country_id

Hàm _onchange_ethnicity_id sẽ được gọi khi giá trị ethnicity_id trên view thay đổi.

Model constraints

Odoo cung cấp hai cách để ràng buộc là: Python constraints and SQL constraints.

Để định nghĩa một ràng buộc Python constraints: chúng ta sử dụng một method decorated: @api.constrains(), decorated sẽ chỉ định trường nào có liên quan đến ràng buộc, do đó, ràng buộc được tự động đánh giá khi một trong số chúng có sự thay đổi về giá trị. Phương thức constraints đã được định nghĩa sẽ tạo ra một ngoại lệ nếu điều kiện mong muốn của phương thức không được thỏa mãn.

Ví dụ về Python constraints:

from odoo.exceptions import ValidationError

@api.constrains('start_date', 'end_date')
def _check_date(self):
    if self.filtered(lambda self: self.start_date and self.end_date and self.start_date > self.end_date):
        raise ValidationError(_("The School year start date must be earlier than or equal to end date."))

Các ràng buộc SQL được xác định thông qua thuộc tính của model là _sql_constraints. Để định nghĩa một SQL constraints chúng ta làm như sau.

_sql_constraints = [('class_name_unique', 'unique(name, company_id)', "The name of school year must be unique per company!")]

Advanced Views

Tree views

Là giao diện dạng danh sách, liệt kê tất cả các bản ghi trong cơ sở dữ liệu. Các cột trên view tương ứng là các fields trong cơ sở dữ liệu. Đây là giao diện cơ bản được Odoo hỗ trợ trong gần như toàn bộ các menu.

Giao diện dạng tree view có thể thêm các thuộc tính bổ sung để tùy chỉnh thêm hành vi của chúng:

decoration-{$name} Cho phép thay đổi kiểu văn bản của hàng dựa trên các thuộc tính của bản ghi tương ứng. Giá trị là biểu thức Python. Đối với mỗi bản ghi, biểu thức nếu có giá trị True sẽ được đánh giá với các thuộc tính của bản ghi dưới dạng giá trị ngữ cảnh, style tương ứng sẽ được áp dụng cho hàng. Chúng ta có thể sử dụng một số giá trị ngữ cảnh sau:

  • uid: id của người dùng hiện tại.

  • today: Ngày hiện tại dưới dạng YYYY-MM-DD.

  • now: Ngày giờ hiện tại dưới dạng YYYY-MM-DD hh:mm:ss.

{$name} có thể là các giá trị như: danger, info, muted, primary, success or warning.

<tree string="Class Tree" decoration-info="active=='True'" editable="bottom">
    <field name="name" />
    <field name="class_group_id" />
</tree>

editable

editable nhận giá trị là top hoặc bottom. Làm cho giao diện dạng tree view có thể được chỉnh sửa tại chỗ (thay vì phải đi qua giao diện form view để sửa).

Calendars

Hiển thị dạng danh sách các bản ghi theo thời gian. Để khai báo calendars view chúng ta bắt đầu với thẻ <calendar> và các thuộc tính phổ biến nhất hay dùng là:

color Thuộc tính này được sử dụng để tô màu các mục trên view.

date_start Thuộc tính này là bắt buộc để chỉ định ngày bắt đầu cho sự kiện.

date_stop Thuộc tính này là bắt buộc để chỉ định ngày kết thúc cho sự kiện. Nếu được đề cập trong bản ghi, bản ghi sẽ có thể di chuyển được trong view.

string Tên của calendar view.

<calendar string="Education Class Calendar" date_start="start_date" color="company_id">
    <field name="name"/>
    <field name="start_date"/>
    <field name="end_date"/>
    <field name="company_id"/>
</calendar>

Search views

Các phần tử <field> của giao diện tìm kiếm có thể có @filter_domain để tìm kiếm các bản ghi có giá trị thỏa mãn với domain. Trong domain đã cho, self đại diện cho giá trị được nhập vào bởi người dùng. Giao diện search views cũng có thể chứa các phần tử <filter>, hoạt động như nút chuyển đổi cho các tìm kiếm được xác định trước.

Bộ lọc phải có một trong các thuộc tính sau:

domain Thêm domain đã cho vào tìm kiếm hiện tại.

context Thêm một số ngữ cảnh vào tìm kiếm hiện tại, chúng ta sử dụng key group_by để nhóm các kết quả theo tên trường đã cho.

<search string="School Search">
    <field name="name"/>
    <field name="code"/>
    <filter string="Archived" name="ftr_inactive" domain="[('active', '=', False)]" />
    <group expand="0" string="Group By">
        <filter string="Company" name="grp_company" domain="[]" context="{'group_by' : 'company_id'}"/>
    </group>
</search>

Chúng ta có thể tự đặt giao diện tìm kiếm mặc định trong action bằng các sử dụng trường search_view_id khi khai báo action và chỉ định đến search view đã khai báo.

Gantt

Biểu đồ Gantt, thường được sử dụng trong quản lý dự án, là một trong những cách phổ biến và hữu ích nhất để hiển thị các hoạt động (nhiệm vụ hoặc sự kiện) được hiển thị theo thời gian. Ở bên trái của biểu đồ là danh sách các hoạt động và dọc theo trên cùng là thanh thời gian phù hợp. Mỗi hoạt động được thể hiện bằng một thanh, vị trí và độ dài của thanh phản ánh ngày bắt đầu, thời lượng và ngày kết thúc của hoạt động.

Để định nghĩa biểu đồ Gantt chúng ta sử dụng thẻ <gantt>.

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

Graph views

Tự động hỗ trợ việc chuyển các số liệu về dạng đồ thị. Đưa chuột trực tiếp tới các vị trí trong biểu đồ để lấy số liệu. Có thể chọn các tiêu chí bằng cách sử dụng thước đo.

Các dạng hiển thị của Graph views:

Đồ thị cột:

Viindoo CMS

Hình ảnh: Graph views dạng đồ thị cột trong Odoo

Đồ thị dạng bánh:

Viindoo CMS

Hình ảnh: Graph views đồ thị dạng bánh trong Odoo

Đồ thị dạng đường:

Viindoo CMS

Hình ảnh: Graph views đồ thị dạng đường trong Odoo

Kanban

Là giao diện dạng thẻ được Odoo tiếp thu từ Phương pháp Kanban có nguồn gốc từ Hệ thống sản xuất tinh gọn [Lean production system] do hãng Toyota phát triển. Kanban view thể hiện các hạng mục công việc ở từng công đoạn của quá trình phát triển. Mỗi bản ghi tương ứng với một thẻ. Tùy từng menu mà có thể kéo/ thả các thẻ từ cột này sang cột khác.

Để định nghĩa view kanban chúng ta sử dụng thẻ <kanban>. Giao diện Kanban xác định cấu trúc của mỗi thẻ là sự kết hợp của các phần tử biểu mẫu (bao gồm HTML cơ bản) và Mẫu QWeb.

Phân quyền

Trong Odoo, mỗi hệ thống đều có công cụ giúp doanh nghiệp có thể phân quyền người dùng cho từng nhân viên theo những cấp độ khác nhau. Việc phân quyền người dùng trong hệ thống giúp nhân viên có đủ công cụ để hoàn thành công việc hàng ngày của mình nhưng cũng giúp doanh nghiệp đảm bảo việc bảo mật thông tin.

Có ba cấp độ phân quyền trong Odoo:

  1. Phân quyền cấp phân hệ (module): Chỉ những người được phân quyền sử dụng phân hệ nào mới thấy hình ảnh và icon để truy cập vào phân hệ đó.

  2. Phân quyền cấp bản ghi: Có thể chỉ được thấy và thao tác với những menu mình được phân quyền và không thấy những menu cấp cao hơn nếu không được phân quyền.

  3. Phân quyền theo cấp độ trường thông tin trong cùng một form: Có những trường thông tin chỉ được thấy nếu bạn được phân quyền.

Việc phân quyền người dùng đều được thực hiện thông qua các Nhóm người dùng (Group). Một người dùng (User) có thể thuộc về nhiều Group khác nhau. Khi chúng ta áp đặt việc phân quyền vào Group, thì những User thuộc về Group đó sẽ bị ảnh hưởng theo.

Access Control

Được quản lý trong bảng ir.model.access. Xác định rõ ràng cho từng nhóm người dùng có được truy cập vào từng bảng dữ liệu hay không.

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink

access_education_school_user,education.school user,model_education_school,viin_education_group_user,1,0,0,0
access_education_school_admin,education.school admin,model_education_school,viin_education_group_admin,1,1,1,1

Record rules

Được dùng để định nghĩa quyền người dùng cho từng hành động Đọc, Thêm, Sửa, Xóa trên từng dòng dữ liệu (record) dựa trên các điều kiện (Condition).

<record id="education_school_rule_multi_company" model="ir.rule">
                <field name="name">School multi-company</field>
                <field ref="model_education_school" name="model_id" />
                <field eval="True" name="global" />
                <field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
        </record>

Field Access

Cho phép phân quyền trên từng field dựa vào thuộc tính groups.

class ResPartner(models.Model):
_inherit = 'res.partner'

is_parent = fields.Boolean(string='Is Parent')
student_ids = fields.One2many('student.relation', 'parent_id',
                    string='Student Relation', groups='viin_education.viin_education_group_user')

education_student_ids = fields.One2many('education.student', 'partner_id',
                    string='Students', groups='viin_education.viin_education_group_user')

Wizards

Wizards mô tả các phiên tương tác với người dùng (hoặc hộp thoại) thông qua các biểu mẫu động. Wizard chỉ đơn giản là một model mở rộng TransientModel mà không phải là Model và sử dụng lại tất cả các cơ chế của Model.

Đặc điểm nổi bật của Wizards là: Dữ liệu điền vào form sẽ đổ vào một Transient model (class odoo.models.TransientModel) và dữ liệu trong model này không được lưu bền vững, các dữ liệu sẽ được xóa khỏi cơ sở dữ liệu sau một thời gian nhất định, rất thích hợp để lưu trữ tạm thời.

Internationalization

Odoo là một hệ thống đa ngôn ngữ. Chúng ta hoàn toàn có thể chọn ngôn ngữ phù hợp với nhu cầu cho tài khoản truy cập (user) của mình. Tính năng này đặc biệt hữu ích với các công ty đa quốc gia khi mà nhân viên thuộc nhiều quốc tịch khác nhau. Các bản dịch nằm trong thư mục i18n gồm một file .pot và các file .po Trong đó file .pot gồm các msgid có thể được dịch sang các ngôn ngữ khác, chúng ta không dịch trên file này. Với mỗi ngôn ngữ cần dịch sẽ cần một file .po tương ứng với ngôn ngữ đó.

Reporting

Printed reports

Odoo sử dụng công cụ báo cáo dựa trên Mẫu QWeb, Twitter Bootstrap và Wkhtmltopdf.

Một báo cáo là sự kết hợp của hai yếu tố:

  • ir.actions.report cấu hình các thông số cơ bản khác nhau cho báo cáo.

  • Giao diện QWeb tiêu chuẩn cho báo cáo thực tế:

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

Ngữ cảnh kết xuất tiêu chuẩn cung cấp một số yếu tố như:

docs: các bản ghi báo cáo được in.

user: người dùng in báo cáo.

WebServices

Web-services module cung cấp một giao diện chung cho tất cả web-services:

  • XML-RPC

  • JSON-RPC

Các đối tượng nghiệp vụ cũng có thể được truy cập thông qua cơ chế đối tượng phân tán. Tất cả chúng đều có thể được sửa đổi thông qua giao diện máy khách với các khung nhìn theo ngữ cảnh.

Odoo có thể truy cập được thông qua các XML-RPC/JSON-RPC interfaces, mà các thư viện tồn tại ở nhiều ngôn ngữ.

XML-RPC Library

Ví dụ sau là một chương trình Python 3 tương tác với máy chủ Odoo với thư viện xmlrpc.client:

import xmlrpc.client

root = 'http://%s:%d/xmlrpc/' % (HOST, PORT)

uid = xmlrpc.client.ServerProxy(root + 'common').login(DB, USER, PASS)
print("Logged in as %s (uid: %d)" % (USER, uid))

# Create a new viin_education
sock = xmlrpc.client.ServerProxy(root + 'object')
args = {
    'color' : 8,
    'memo' : 'This is a viin_education',
    'create_uid': uid,
}
viin_education_id = sock.execute(DB, uid, PASS, 'education.class', 'create', args)

JSON-RPC Library

Ví dụ sau là một chương trình Python 3 tương tác với máy chủ Odoo với các thư viện Python tiêu chuẩn urllib.requestjson. Ví dụ này giả định rằng ứng dụng Viin Education đã được cài đặt.

import json
import random
import urllib.request

HOST = 'localhost'
PORT = 8069
DB = 'viin_education'
USER = 'admin'
PASS = 'admin'

def json_rpc(url, method, params):
    data = {
        "jsonrpc": "2.0",
        "method": method,
        "params": params,
        "id": random.randint(0, 1000000000),
    }
    req = urllib.request.Request(url=url, data=json.dumps(data).encode(), headers={
        "Content-Type":"application/json",
    })
    reply = json.loads(urllib.request.urlopen(req).read().decode('UTF-8'))
    if reply.get("error"):
        raise Exception(reply["error"])
    return reply["result"]

def call(url, service, method, *args):
    return json_rpc(url, "call", {"service": service, "method": method, "args": args})

# log in the given database
url = "http://%s:%s/jsonrpc" % (HOST, PORT)
uid = call(url, "common", "login", DB, USER, PASS)

# create a new viin_education
args = {
    'color': 8,
    'memo': 'This is another viin_education',
    'create_uid': uid,
}
viin_education_id = call(url, "object", "execute", DB, uid, PASS, 'education.class', 'create', args)

Các ví dụ có thể dễ dàng điều chỉnh từ XML-RPC sang JSON-RPC.