├── source-code ├── ch03 │ └── library_app │ │ ├── tests │ │ ├── __init__.py │ │ └── test_book.py │ │ ├── controllers │ │ ├── __init__.py │ │ └── main.py │ │ ├── models │ │ ├── __init__.py │ │ └── library_book.py │ │ ├── __init__.py │ │ ├── static │ │ └── description │ │ │ └── icon.png │ │ ├── security │ │ ├── ir.model.access.csv │ │ └── library_security.xml │ │ ├── views │ │ ├── book_list_template.xml │ │ ├── library_menu.xml │ │ └── book_view.xml │ │ ├── __manifest__.py │ │ └── demo │ │ └── demo.xml ├── ch04 │ ├── library_app │ │ ├── tests │ │ │ ├── __init__.py │ │ │ └── test_book.py │ │ ├── controllers │ │ │ ├── __init__.py │ │ │ ├── __pycache__ │ │ │ │ ├── main.cpython-39.pyc │ │ │ │ ├── __init__.cpython-39.pyc │ │ │ │ └── controllers.cpython-39.pyc │ │ │ ├── main.py │ │ │ └── controllers.py │ │ ├── models │ │ │ ├── __init__.py │ │ │ ├── __pycache__ │ │ │ │ ├── models.cpython-39.pyc │ │ │ │ ├── __init__.cpython-39.pyc │ │ │ │ └── library_book.cpython-39.pyc │ │ │ └── library_book.py │ │ ├── __init__.py │ │ ├── static │ │ │ └── description │ │ │ │ └── icon.png │ │ ├── __pycache__ │ │ │ └── __init__.cpython-39.pyc │ │ ├── security │ │ │ ├── ir.model.access.csv │ │ │ └── library_security.xml │ │ ├── views │ │ │ ├── book_list_template.xml │ │ │ ├── library_menu.xml │ │ │ ├── templates.xml │ │ │ ├── views.xml │ │ │ └── book_view.xml │ │ ├── __manifest__.py │ │ └── demo │ │ │ └── demo.xml │ └── library_member │ │ ├── controllers │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── main.cpython-39.pyc │ │ │ └── __init__.cpython-39.pyc │ │ └── main.py │ │ ├── __init__.py │ │ ├── models │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-39.pyc │ │ │ ├── library_book.cpython-39.pyc │ │ │ └── library_member.cpython-39.pyc │ │ ├── library_member.py │ │ └── library_book.py │ │ ├── __pycache__ │ │ └── __init__.cpython-39.pyc │ │ ├── security │ │ ├── library_security.xml │ │ └── ir.model.access.csv │ │ ├── views │ │ ├── library_menu.xml │ │ ├── book_list_template.xml │ │ ├── book_view.xml │ │ └── member_view.xml │ │ └── __manifest__.py ├── ch05 │ ├── library_app │ │ ├── tests │ │ │ ├── __init__.py │ │ │ └── test_book.py │ │ ├── controllers │ │ │ ├── __init__.py │ │ │ ├── __pycache__ │ │ │ │ ├── main.cpython-39.pyc │ │ │ │ ├── __init__.cpython-39.pyc │ │ │ │ └── controllers.cpython-39.pyc │ │ │ ├── main.py │ │ │ └── controllers.py │ │ ├── models │ │ │ ├── __init__.py │ │ │ ├── __pycache__ │ │ │ │ ├── models.cpython-39.pyc │ │ │ │ ├── __init__.cpython-39.pyc │ │ │ │ └── library_book.cpython-39.pyc │ │ │ └── library_book.py │ │ ├── __init__.py │ │ ├── static │ │ │ └── description │ │ │ │ └── icon.png │ │ ├── __pycache__ │ │ │ └── __init__.cpython-39.pyc │ │ ├── data │ │ │ ├── res.partner.csv │ │ │ ├── library.book.csv │ │ │ ├── library.book.csv │ │ │ └── book_demo.xml │ │ ├── security │ │ │ ├── ir.model.access.csv │ │ │ └── library_security.xml │ │ ├── views │ │ │ ├── book_list_template.xml │ │ │ ├── library_menu.xml │ │ │ ├── templates.xml │ │ │ ├── views.xml │ │ │ └── book_view.xml │ │ ├── __manifest__.py │ │ └── demo │ │ │ └── demo.xml │ └── library_member │ │ ├── controllers │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── main.cpython-39.pyc │ │ │ └── __init__.cpython-39.pyc │ │ └── main.py │ │ ├── __init__.py │ │ ├── models │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-39.pyc │ │ │ ├── library_book.cpython-39.pyc │ │ │ └── library_member.cpython-39.pyc │ │ ├── library_member.py │ │ └── library_book.py │ │ ├── __pycache__ │ │ └── __init__.cpython-39.pyc │ │ ├── security │ │ ├── library_security.xml │ │ └── ir.model.access.csv │ │ ├── views │ │ ├── library_menu.xml │ │ ├── book_list_template.xml │ │ ├── book_view.xml │ │ └── member_view.xml │ │ └── __manifest__.py ├── ch06 │ ├── library_app │ │ ├── tests │ │ │ ├── __init__.py │ │ │ └── test_book.py │ │ ├── controllers │ │ │ ├── __init__.py │ │ │ ├── __pycache__ │ │ │ │ ├── main.cpython-39.pyc │ │ │ │ ├── __init__.cpython-39.pyc │ │ │ │ └── controllers.cpython-39.pyc │ │ │ ├── main.py │ │ │ └── controllers.py │ │ ├── __init__.py │ │ ├── models │ │ │ ├── __init__.py │ │ │ ├── __pycache__ │ │ │ │ ├── models.cpython-39.pyc │ │ │ │ ├── __init__.cpython-39.pyc │ │ │ │ ├── library_book.cpython-39.pyc │ │ │ │ └── res_partner.cpython-39.pyc │ │ │ ├── res_partner.py │ │ │ ├── library_book_category.py │ │ │ └── library_book.py │ │ ├── static │ │ │ └── description │ │ │ │ └── icon.png │ │ ├── __pycache__ │ │ │ └── __init__.cpython-39.pyc │ │ ├── data │ │ │ ├── res.partner.csv │ │ │ ├── library.book.csv │ │ │ ├── library.book.csv │ │ │ └── book_demo.xml │ │ ├── security │ │ │ ├── ir.model.access.csv │ │ │ └── library_security.xml │ │ ├── views │ │ │ ├── book_list_template.xml │ │ │ ├── library_menu.xml │ │ │ ├── templates.xml │ │ │ ├── views.xml │ │ │ └── book_view.xml │ │ ├── __manifest__.py │ │ └── demo │ │ │ └── demo.xml │ └── library_member │ │ ├── controllers │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── main.cpython-39.pyc │ │ │ └── __init__.cpython-39.pyc │ │ └── main.py │ │ ├── __init__.py │ │ ├── models │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-39.pyc │ │ │ ├── library_book.cpython-39.pyc │ │ │ └── library_member.cpython-39.pyc │ │ ├── library_member.py │ │ └── library_book.py │ │ ├── __pycache__ │ │ └── __init__.cpython-39.pyc │ │ ├── security │ │ ├── library_security.xml │ │ └── ir.model.access.csv │ │ ├── views │ │ ├── library_menu.xml │ │ ├── book_list_template.xml │ │ ├── book_view.xml │ │ └── member_view.xml │ │ └── __manifest__.py ├── ch08 │ ├── library_app │ │ ├── tests │ │ │ ├── __init__.py │ │ │ └── test_book.py │ │ ├── controllers │ │ │ ├── __init__.py │ │ │ └── main.py │ │ ├── __init__.py │ │ ├── models │ │ │ ├── __init__.py │ │ │ ├── res_partner.py │ │ │ ├── library_book_category.py │ │ │ └── library_book.py │ │ ├── static │ │ │ └── description │ │ │ │ └── icon.png │ │ ├── data │ │ │ ├── res.partner.csv │ │ │ ├── library.book.csv │ │ │ └── book_demo.xml │ │ ├── security │ │ │ ├── ir.model.access.csv │ │ │ └── library_security.xml │ │ ├── views │ │ │ ├── book_list_template.xml │ │ │ ├── library_menu.xml │ │ │ └── book_view.xml │ │ ├── __manifest__.py │ │ └── demo │ │ │ └── demo.xml │ ├── library_member │ │ ├── controllers │ │ │ ├── __init__.py │ │ │ └── main.py │ │ ├── __init__.py │ │ ├── models │ │ │ ├── __init__.py │ │ │ ├── library_member.py │ │ │ └── library_book.py │ │ ├── security │ │ │ ├── library_security.xml │ │ │ └── ir.model.access.csv │ │ ├── views │ │ │ ├── library_menu.xml │ │ │ ├── book_list_template.xml │ │ │ ├── book_view.xml │ │ │ └── member_view.xml │ │ └── __manifest__.py │ └── library_checkout │ │ ├── wizard │ │ ├── __init__.py │ │ ├── checkout_mass_message_wizard_view.xml │ │ └── checkout_mass_message.py │ │ ├── __init__.py │ │ ├── tests │ │ ├── __init__.py │ │ └── test_checkout_mass_message.py │ │ ├── models │ │ ├── __init__.py │ │ ├── library_checkout_line.py │ │ ├── library_checkout_stage.py │ │ └── library_checkout.py │ │ ├── __manifest__.py │ │ ├── security │ │ └── ir.model.access.csv │ │ ├── views │ │ ├── library_menu.xml │ │ └── checkout_view.xml │ │ └── data │ │ └── library_checkout_stage.xml ├── ch09 │ └── client_app │ │ ├── library_odoorpc.py │ │ ├── library.py │ │ └── library_xmlrpc.py └── ch07 │ └── ch07_recorsets_code.py ├── README.md ├── 13.md ├── 12.md └── 9.md /source-code/ch03/library_app/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import test_book -------------------------------------------------------------------------------- /source-code/ch04/library_app/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import test_book -------------------------------------------------------------------------------- /source-code/ch05/library_app/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import test_book -------------------------------------------------------------------------------- /source-code/ch06/library_app/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import test_book -------------------------------------------------------------------------------- /source-code/ch08/library_app/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import test_book -------------------------------------------------------------------------------- /source-code/ch04/library_member/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from . import main 2 | -------------------------------------------------------------------------------- /source-code/ch05/library_member/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from . import main 2 | -------------------------------------------------------------------------------- /source-code/ch06/library_member/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from . import main 2 | -------------------------------------------------------------------------------- /source-code/ch08/library_member/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from . import main 2 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/wizard/__init__.py: -------------------------------------------------------------------------------- 1 | from . import checkout_mass_message 2 | -------------------------------------------------------------------------------- /source-code/ch04/library_member/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models 2 | from . import controllers 3 | -------------------------------------------------------------------------------- /source-code/ch05/library_member/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models 2 | from . import controllers 3 | -------------------------------------------------------------------------------- /source-code/ch06/library_member/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models 2 | from . import controllers 3 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models 2 | from . import wizard 3 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import test_checkout_mass_message 2 | -------------------------------------------------------------------------------- /source-code/ch08/library_member/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models 2 | from . import controllers 3 | -------------------------------------------------------------------------------- /source-code/ch03/library_app/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import main -------------------------------------------------------------------------------- /source-code/ch04/library_app/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import main -------------------------------------------------------------------------------- /source-code/ch05/library_app/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import main -------------------------------------------------------------------------------- /source-code/ch06/library_app/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import main -------------------------------------------------------------------------------- /source-code/ch08/library_app/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import main -------------------------------------------------------------------------------- /source-code/ch03/library_app/models/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import library_book 4 | -------------------------------------------------------------------------------- /source-code/ch04/library_app/models/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import library_book 4 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/models/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import library_book 4 | -------------------------------------------------------------------------------- /source-code/ch04/library_member/models/__init__.py: -------------------------------------------------------------------------------- 1 | from . import library_book 2 | from . import library_member 3 | -------------------------------------------------------------------------------- /source-code/ch05/library_member/models/__init__.py: -------------------------------------------------------------------------------- 1 | from . import library_book 2 | from . import library_member 3 | -------------------------------------------------------------------------------- /source-code/ch06/library_member/models/__init__.py: -------------------------------------------------------------------------------- 1 | from . import library_book 2 | from . import library_member 3 | -------------------------------------------------------------------------------- /source-code/ch08/library_member/models/__init__.py: -------------------------------------------------------------------------------- 1 | from . import library_book 2 | from . import library_member 3 | -------------------------------------------------------------------------------- /source-code/ch03/library_app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import controllers 4 | from . import models 5 | -------------------------------------------------------------------------------- /source-code/ch04/library_app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import controllers 4 | from . import models 5 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import controllers 4 | from . import models 5 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import controllers 4 | from . import models 5 | -------------------------------------------------------------------------------- /source-code/ch08/library_app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import controllers 4 | from . import models 5 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/models/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import library_book 4 | from . import res_partner -------------------------------------------------------------------------------- /source-code/ch08/library_app/models/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import library_book 4 | from . import res_partner -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/models/__init__.py: -------------------------------------------------------------------------------- 1 | from . import library_checkout_stage 2 | from . import library_checkout 3 | from . import library_checkout_line 4 | -------------------------------------------------------------------------------- /source-code/ch03/library_app/static/description/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch03/library_app/static/description/icon.png -------------------------------------------------------------------------------- /source-code/ch04/library_app/static/description/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_app/static/description/icon.png -------------------------------------------------------------------------------- /source-code/ch05/library_app/static/description/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_app/static/description/icon.png -------------------------------------------------------------------------------- /source-code/ch06/library_app/static/description/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_app/static/description/icon.png -------------------------------------------------------------------------------- /source-code/ch08/library_app/static/description/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch08/library_app/static/description/icon.png -------------------------------------------------------------------------------- /source-code/ch04/library_app/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_app/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_app/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_app/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_app/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_app/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_member/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_member/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_member/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_member/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_member/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_member/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_app/models/__pycache__/models.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_app/models/__pycache__/models.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_app/models/__pycache__/models.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_app/models/__pycache__/models.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_app/models/__pycache__/models.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_app/models/__pycache__/models.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_app/controllers/__pycache__/main.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_app/controllers/__pycache__/main.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_app/models/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_app/models/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_app/controllers/__pycache__/main.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_app/controllers/__pycache__/main.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_app/models/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_app/models/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_app/controllers/__pycache__/main.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_app/controllers/__pycache__/main.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_app/models/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_app/models/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_app/models/__pycache__/library_book.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_app/models/__pycache__/library_book.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_member/controllers/__pycache__/main.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_member/controllers/__pycache__/main.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_member/models/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_member/models/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_app/models/__pycache__/library_book.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_app/models/__pycache__/library_book.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_member/controllers/__pycache__/main.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_member/controllers/__pycache__/main.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_member/models/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_member/models/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_app/models/__pycache__/library_book.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_app/models/__pycache__/library_book.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_app/models/__pycache__/res_partner.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_app/models/__pycache__/res_partner.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_member/controllers/__pycache__/main.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_member/controllers/__pycache__/main.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_member/models/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_member/models/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_app/controllers/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_app/controllers/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_app/controllers/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_app/controllers/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_app/data/res.partner.csv: -------------------------------------------------------------------------------- 1 | id,name 2 | res_partner_alexandre,"Alexandre Fayolle" 3 | res_partner_daniel,"Daniel Reis" 4 | res_partner_holger,"Holger Brunn" 5 | res_partner_packt,"Packt Publishing" -------------------------------------------------------------------------------- /source-code/ch06/library_app/controllers/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_app/controllers/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_app/data/res.partner.csv: -------------------------------------------------------------------------------- 1 | id,name 2 | res_partner_alexandre,"Alexandre Fayolle" 3 | res_partner_daniel,"Daniel Reis" 4 | res_partner_holger,"Holger Brunn" 5 | res_partner_packt,"Packt Publishing" -------------------------------------------------------------------------------- /source-code/ch08/library_app/data/res.partner.csv: -------------------------------------------------------------------------------- 1 | id,name 2 | res_partner_alexandre,"Alexandre Fayolle" 3 | res_partner_daniel,"Daniel Reis" 4 | res_partner_holger,"Holger Brunn" 5 | res_partner_packt,"Packt Publishing" -------------------------------------------------------------------------------- /source-code/ch04/library_app/controllers/__pycache__/controllers.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_app/controllers/__pycache__/controllers.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_member/controllers/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_member/controllers/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_member/models/__pycache__/library_book.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_member/models/__pycache__/library_book.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_member/models/__pycache__/library_member.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch04/library_member/models/__pycache__/library_member.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_app/controllers/__pycache__/controllers.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_app/controllers/__pycache__/controllers.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_member/controllers/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_member/controllers/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_member/models/__pycache__/library_book.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_member/models/__pycache__/library_book.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch05/library_member/models/__pycache__/library_member.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch05/library_member/models/__pycache__/library_member.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_app/controllers/__pycache__/controllers.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_app/controllers/__pycache__/controllers.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_member/controllers/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_member/controllers/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_member/models/__pycache__/library_book.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_member/models/__pycache__/library_book.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch06/library_member/models/__pycache__/library_member.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iTranslateX/odoo-essentials/HEAD/source-code/ch06/library_member/models/__pycache__/library_member.cpython-39.pyc -------------------------------------------------------------------------------- /source-code/ch04/library_member/security/library_security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Librarian 5 | 6 | -------------------------------------------------------------------------------- /source-code/ch05/library_member/security/library_security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Librarian 5 | 6 | -------------------------------------------------------------------------------- /source-code/ch06/library_member/security/library_security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Librarian 5 | 6 | -------------------------------------------------------------------------------- /source-code/ch08/library_member/security/library_security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Librarian 5 | 6 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/models/res_partner.py: -------------------------------------------------------------------------------- 1 | from odoo import models, fields 2 | 3 | 4 | class Partner(models.Model): 5 | _inherit = "res.partner" 6 | published_book_ids = fields.One2many( 7 | "library.book", 8 | "publisher_id", 9 | string="Published Books") -------------------------------------------------------------------------------- /source-code/ch08/library_app/models/res_partner.py: -------------------------------------------------------------------------------- 1 | from odoo import models, fields 2 | 3 | 4 | class Partner(models.Model): 5 | _inherit = "res.partner" 6 | published_book_ids = fields.One2many( 7 | "library.book", 8 | "publisher_id", 9 | string="Published Books") -------------------------------------------------------------------------------- /source-code/ch03/library_app/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | access_book_user,BookUser,model_library_book,library_group_user,1,1,1,0 3 | access_book_manager,BookManager,model_library_book,library_group_manager,1,1,1,1 4 | -------------------------------------------------------------------------------- /source-code/ch04/library_app/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | access_book_user,BookUser,model_library_book,library_group_user,1,1,1,0 3 | access_book_manager,BookManager,model_library_book,library_group_manager,1,1,1,1 4 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | access_book_user,BookUser,model_library_book,library_group_user,1,1,1,0 3 | access_book_manager,BookManager,model_library_book,library_group_manager,1,1,1,1 4 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | access_book_user,BookUser,model_library_book,library_group_user,1,1,1,0 3 | access_book_manager,BookManager,model_library_book,library_group_manager,1,1,1,1 4 | -------------------------------------------------------------------------------- /source-code/ch08/library_app/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | access_book_user,BookUser,model_library_book,library_group_user,1,1,1,0 3 | access_book_manager,BookManager,model_library_book,library_group_manager,1,1,1,1 4 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/data/ library.book.csv: -------------------------------------------------------------------------------- 1 | "id","name","date_published","publisher_id/id","author_ids/id" 2 | library_book_ode11,"Odoo Development Essentials 11","2018-03-01",res_partner_packt,res_partner_daniel 3 | library_book_odc11,"Odoo 11 Development Cookbook","2018-01-01",res_partner_packt,"res_partner_alexandre,res_partner_holger" -------------------------------------------------------------------------------- /source-code/ch05/library_app/data/library.book.csv: -------------------------------------------------------------------------------- 1 | "id","name","date_published","publisher_id/id","author_ids/id" 2 | library_book_ode11,"Odoo Development Essentials 11","2018-03-01",res_partner_packt,res_partner_daniel 3 | library_book_odc11,"Odoo 11 Development Cookbook","2018-01-01",res_partner_packt,"res_partner_alexandre,res_partner_holger" -------------------------------------------------------------------------------- /source-code/ch06/library_app/data/ library.book.csv: -------------------------------------------------------------------------------- 1 | "id","name","date_published","publisher_id/id","author_ids/id" 2 | library_book_ode11,"Odoo Development Essentials 11","2018-03-01",res_partner_packt,res_partner_daniel 3 | library_book_odc11,"Odoo 11 Development Cookbook","2018-01-01",res_partner_packt,"res_partner_alexandre,res_partner_holger" -------------------------------------------------------------------------------- /source-code/ch06/library_app/data/library.book.csv: -------------------------------------------------------------------------------- 1 | "id","name","date_published","publisher_id/id","author_ids/id" 2 | library_book_ode11,"Odoo Development Essentials 11","2018-03-01",res_partner_packt,res_partner_daniel 3 | library_book_odc11,"Odoo 11 Development Cookbook","2018-01-01",res_partner_packt,"res_partner_alexandre,res_partner_holger" -------------------------------------------------------------------------------- /source-code/ch08/library_app/data/library.book.csv: -------------------------------------------------------------------------------- 1 | "id","name","date_published","publisher_id/id","author_ids/id" 2 | library_book_ode11,"Odoo Development Essentials 11","2018-03-01",res_partner_packt,res_partner_daniel 3 | library_book_odc11,"Odoo 11 Development Cookbook","2018-01-01",res_partner_packt,"res_partner_alexandre,res_partner_holger" -------------------------------------------------------------------------------- /source-code/ch04/library_member/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | access_member_user,Member User Access,model_library_member,library_app.library_group_user,1,1,1,0 3 | access_member_manager,Member Manager Access,model_library_member,library_app.library_group_manager,1,1,1,1 -------------------------------------------------------------------------------- /source-code/ch05/library_member/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | access_member_user,Member User Access,model_library_member,library_app.library_group_user,1,1,1,0 3 | access_member_manager,Member Manager Access,model_library_member,library_app.library_group_manager,1,1,1,1 -------------------------------------------------------------------------------- /source-code/ch06/library_member/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | access_member_user,Member User Access,model_library_member,library_app.library_group_user,1,1,1,0 3 | access_member_manager,Member Manager Access,model_library_member,library_app.library_group_manager,1,1,1,1 -------------------------------------------------------------------------------- /source-code/ch08/library_member/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | access_member_user,Member User Access,model_library_member,library_app.library_group_user,1,1,1,0 3 | access_member_manager,Member Manager Access,model_library_member,library_app.library_group_manager,1,1,1,1 -------------------------------------------------------------------------------- /source-code/ch03/library_app/controllers/main.py: -------------------------------------------------------------------------------- 1 | from odoo import http 2 | 3 | 4 | class Books(http.Controller): 5 | @http.route("/library/books") 6 | def list(self, **kwargs): 7 | Book = http.request.env['library.book'] 8 | books = Book.search([]) 9 | return http.request.render( 10 | "library_app.book_list_template", 11 | {"books": books} 12 | ) -------------------------------------------------------------------------------- /source-code/ch04/library_app/controllers/main.py: -------------------------------------------------------------------------------- 1 | from odoo import http 2 | 3 | 4 | class Books(http.Controller): 5 | @http.route("/library/books") 6 | def list(self, **kwargs): 7 | Book = http.request.env['library.book'] 8 | books = Book.search([]) 9 | return http.request.render( 10 | "library_app.book_list_template", 11 | {"books": books} 12 | ) -------------------------------------------------------------------------------- /source-code/ch05/library_app/controllers/main.py: -------------------------------------------------------------------------------- 1 | from odoo import http 2 | 3 | 4 | class Books(http.Controller): 5 | @http.route("/library/books") 6 | def list(self, **kwargs): 7 | Book = http.request.env['library.book'] 8 | books = Book.search([]) 9 | return http.request.render( 10 | "library_app.book_list_template", 11 | {"books": books} 12 | ) -------------------------------------------------------------------------------- /source-code/ch06/library_app/controllers/main.py: -------------------------------------------------------------------------------- 1 | from odoo import http 2 | 3 | 4 | class Books(http.Controller): 5 | @http.route("/library/books") 6 | def list(self, **kwargs): 7 | Book = http.request.env['library.book'] 8 | books = Book.search([]) 9 | return http.request.render( 10 | "library_app.book_list_template", 11 | {"books": books} 12 | ) -------------------------------------------------------------------------------- /source-code/ch08/library_app/controllers/main.py: -------------------------------------------------------------------------------- 1 | from odoo import http 2 | 3 | 4 | class Books(http.Controller): 5 | @http.route("/library/books") 6 | def list(self, **kwargs): 7 | Book = http.request.env['library.book'] 8 | books = Book.search([]) 9 | return http.request.render( 10 | "library_app.book_list_template", 11 | {"books": books} 12 | ) -------------------------------------------------------------------------------- /source-code/ch04/library_member/views/library_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | -------------------------------------------------------------------------------- /source-code/ch05/library_member/views/library_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | -------------------------------------------------------------------------------- /source-code/ch06/library_member/views/library_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | -------------------------------------------------------------------------------- /source-code/ch08/library_member/views/library_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | -------------------------------------------------------------------------------- /source-code/ch04/library_member/views/book_list_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /source-code/ch05/library_member/views/book_list_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /source-code/ch06/library_member/views/book_list_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /source-code/ch08/library_member/views/book_list_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/models/library_checkout_line.py: -------------------------------------------------------------------------------- 1 | from odoo import api, exceptions, fields, models 2 | 3 | 4 | class CheckoutLine(models.Model): 5 | _name = "library.checkout.line" 6 | _description = "Checkout Request Line" 7 | checkout_id = fields.Many2one( 8 | "library.checkout", 9 | required=True, 10 | ) 11 | book_id = fields.Many2one("library.book", required=True) 12 | note = fields.Char("Notes") 13 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/__manifest__.py: -------------------------------------------------------------------------------- 1 | { 2 | 'name': 'Library Book Borrowing', 3 | 'description': 'Members can borrow books from the library.', 4 | 'author': 'Alan Hou', 5 | 'depends': ['library_member', 'mail'], 6 | 'data':[ 7 | 'security/ir.model.access.csv', 8 | "wizard/checkout_mass_message_wizard_view.xml", 9 | 'views/library_menu.xml', 10 | 'views/checkout_view.xml', 11 | 'data/library_checkout_stage.xml', 12 | ], 13 | } -------------------------------------------------------------------------------- /source-code/ch04/library_member/models/library_member.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | 3 | 4 | class Member(models.Model): 5 | _name = 'library.member' 6 | _description = 'Library Member' 7 | _inherit = ["mail.thread", "mail.activity.mixin"] 8 | # _inherits = {"res.partner": "partner_id"} 9 | card_number = fields.Char() 10 | partner_id = fields.Many2one( 11 | "res.partner", 12 | delegate=True, 13 | ondelete="cascade", 14 | required=True) -------------------------------------------------------------------------------- /source-code/ch05/library_member/models/library_member.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | 3 | 4 | class Member(models.Model): 5 | _name = 'library.member' 6 | _description = 'Library Member' 7 | _inherit = ["mail.thread", "mail.activity.mixin"] 8 | # _inherits = {"res.partner": "partner_id"} 9 | card_number = fields.Char() 10 | partner_id = fields.Many2one( 11 | "res.partner", 12 | delegate=True, 13 | ondelete="cascade", 14 | required=True) -------------------------------------------------------------------------------- /source-code/ch06/library_member/models/library_member.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | 3 | 4 | class Member(models.Model): 5 | _name = 'library.member' 6 | _description = 'Library Member' 7 | _inherit = ["mail.thread", "mail.activity.mixin"] 8 | # _inherits = {"res.partner": "partner_id"} 9 | card_number = fields.Char() 10 | partner_id = fields.Many2one( 11 | "res.partner", 12 | delegate=True, 13 | ondelete="cascade", 14 | required=True) -------------------------------------------------------------------------------- /source-code/ch08/library_member/models/library_member.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | 3 | 4 | class Member(models.Model): 5 | _name = 'library.member' 6 | _description = 'Library Member' 7 | _inherit = ["mail.thread", "mail.activity.mixin"] 8 | # _inherits = {"res.partner": "partner_id"} 9 | card_number = fields.Char() 10 | partner_id = fields.Many2one( 11 | "res.partner", 12 | delegate=True, 13 | ondelete="cascade", 14 | required=True) -------------------------------------------------------------------------------- /source-code/ch04/library_member/views/book_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Book: add Is Available? field 4 | library.book 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /source-code/ch05/library_member/views/book_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Book: add Is Available? field 4 | library.book 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /source-code/ch06/library_member/views/book_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Book: add Is Available? field 4 | library.book 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /source-code/ch08/library_member/views/book_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Book: add Is Available? field 4 | library.book 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /source-code/ch04/library_member/controllers/main.py: -------------------------------------------------------------------------------- 1 | from odoo import http 2 | from odoo.addons.library_app.controllers.main import Books 3 | 4 | 5 | class BookExtended(Books): 6 | @http.route() 7 | def list(self, **kwargs): 8 | response = super().list(**kwargs) 9 | if kwargs.get("available"): 10 | all_books = response.qcontext["books"] 11 | available_books = all_books.filtered("is_available") 12 | response.qcontext["books"] = available_books 13 | return response 14 | -------------------------------------------------------------------------------- /source-code/ch05/library_member/controllers/main.py: -------------------------------------------------------------------------------- 1 | from odoo import http 2 | from odoo.addons.library_app.controllers.main import Books 3 | 4 | 5 | class BookExtended(Books): 6 | @http.route() 7 | def list(self, **kwargs): 8 | response = super().list(**kwargs) 9 | if kwargs.get("available"): 10 | all_books = response.qcontext["books"] 11 | available_books = all_books.filtered("is_available") 12 | response.qcontext["books"] = available_books 13 | return response 14 | -------------------------------------------------------------------------------- /source-code/ch06/library_member/controllers/main.py: -------------------------------------------------------------------------------- 1 | from odoo import http 2 | from odoo.addons.library_app.controllers.main import Books 3 | 4 | 5 | class BookExtended(Books): 6 | @http.route() 7 | def list(self, **kwargs): 8 | response = super().list(**kwargs) 9 | if kwargs.get("available"): 10 | all_books = response.qcontext["books"] 11 | available_books = all_books.filtered("is_available") 12 | response.qcontext["books"] = available_books 13 | return response 14 | -------------------------------------------------------------------------------- /source-code/ch08/library_member/controllers/main.py: -------------------------------------------------------------------------------- 1 | from odoo import http 2 | from odoo.addons.library_app.controllers.main import Books 3 | 4 | 5 | class BookExtended(Books): 6 | @http.route() 7 | def list(self, **kwargs): 8 | response = super().list(**kwargs) 9 | if kwargs.get("available"): 10 | all_books = response.qcontext["books"] 11 | available_books = all_books.filtered("is_available") 12 | response.qcontext["books"] = available_books 13 | return response 14 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/data/book_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Aldous Huxley 6 | 7 | 8 | Brave New World 9 | 11 | 1932-01-01 12 | 13 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/data/book_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Aldous Huxley 6 | 7 | 8 | Brave New World 9 | 11 | 1932-01-01 12 | 13 | -------------------------------------------------------------------------------- /source-code/ch08/library_app/data/book_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Aldous Huxley 6 | 7 | 8 | Brave New World 9 | 11 | 1932-01-01 12 | 13 | -------------------------------------------------------------------------------- /source-code/ch03/library_app/views/book_list_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /source-code/ch04/library_app/views/book_list_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/views/book_list_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/views/book_list_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /source-code/ch08/library_app/views/book_list_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /source-code/ch04/library_member/__manifest__.py: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Library Members", 3 | "license": "AGPL-3", 4 | "description": "Manage people who will be able to borrow books.", 5 | "author": "Alan Hou", 6 | "depends": ["library_app", "mail"], 7 | "application": False, 8 | "data": [ 9 | "security/library_security.xml", 10 | "security/ir.model.access.csv", 11 | "views/book_view.xml", 12 | "views/member_view.xml", 13 | "views/library_menu.xml", 14 | "views/book_list_template.xml", 15 | ] 16 | } -------------------------------------------------------------------------------- /source-code/ch05/library_member/__manifest__.py: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Library Members", 3 | "license": "AGPL-3", 4 | "description": "Manage people who will be able to borrow books.", 5 | "author": "Alan Hou", 6 | "depends": ["library_app", "mail"], 7 | "application": False, 8 | "data": [ 9 | "security/library_security.xml", 10 | "security/ir.model.access.csv", 11 | "views/book_view.xml", 12 | "views/member_view.xml", 13 | "views/library_menu.xml", 14 | "views/book_list_template.xml", 15 | ] 16 | } -------------------------------------------------------------------------------- /source-code/ch06/library_member/__manifest__.py: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Library Members", 3 | "license": "AGPL-3", 4 | "description": "Manage people who will be able to borrow books.", 5 | "author": "Alan Hou", 6 | "depends": ["library_app", "mail"], 7 | "application": False, 8 | "data": [ 9 | "security/library_security.xml", 10 | "security/ir.model.access.csv", 11 | "views/book_view.xml", 12 | "views/member_view.xml", 13 | "views/library_menu.xml", 14 | "views/book_list_template.xml", 15 | ] 16 | } -------------------------------------------------------------------------------- /source-code/ch08/library_member/__manifest__.py: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Library Members", 3 | "license": "AGPL-3", 4 | "description": "Manage people who will be able to borrow books.", 5 | "author": "Alan Hou", 6 | "depends": ["library_app", "mail"], 7 | "application": False, 8 | "data": [ 9 | "security/library_security.xml", 10 | "security/ir.model.access.csv", 11 | "views/book_view.xml", 12 | "views/member_view.xml", 13 | "views/library_menu.xml", 14 | "views/book_list_template.xml", 15 | ] 16 | } -------------------------------------------------------------------------------- /source-code/ch03/library_app/views/library_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Library Books 7 | library.book 8 | tree,form 9 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /source-code/ch04/library_app/views/library_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Library Books 7 | library.book 8 | tree,form 9 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/views/library_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Library Books 7 | library.book 8 | tree,form 9 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/views/library_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Library Books 7 | library.book 8 | tree,form 9 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/models/library_checkout_stage.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | 3 | 4 | class CheckoutStage(models.Model): 5 | _name = "library.checkout.stage" 6 | _description = "Checkout Stage" 7 | _order = "sequence" 8 | name = fields.Char() 9 | sequence = fields.Integer(default=10) 10 | fold = fields.Boolean() 11 | active = fields.Boolean(default=True) 12 | state = fields.Selection( 13 | [("new", "Requested"), 14 | ("open", "Borrowed"), 15 | ("done", "Returned"), 16 | ("cancel", "Canceled")], 17 | default="new", 18 | ) 19 | -------------------------------------------------------------------------------- /source-code/ch03/library_app/__manifest__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | { 4 | "name": "Library Management", 5 | "summary": "Manage library catalog and book lending.", 6 | "author": "Alan Hou", 7 | "license": "AGPL-3", 8 | "category": "Services/Library", 9 | "website": "https://alanhou.org", 10 | "version": "15.0.1.0.0", 11 | "depends": ["base"], 12 | "data": [ 13 | "security/library_security.xml", 14 | "security/ir.model.access.csv", 15 | "views/book_view.xml", 16 | "views/library_menu.xml", 17 | "views/book_list_template.xml", 18 | ], 19 | "application": True, 20 | } 21 | -------------------------------------------------------------------------------- /source-code/ch04/library_app/__manifest__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | { 4 | "name": "Library Management", 5 | "summary": "Manage library catalog and book lending.", 6 | "author": "Alan Hou", 7 | "license": "AGPL-3", 8 | "category": "Services/Library", 9 | "website": "https://alanhou.org", 10 | "version": "15.0.1.0.0", 11 | "depends": ["base"], 12 | "data": [ 13 | "security/library_security.xml", 14 | "security/ir.model.access.csv", 15 | "views/book_view.xml", 16 | "views/library_menu.xml", 17 | "views/book_list_template.xml", 18 | ], 19 | "application": True, 20 | } 21 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | checkout_user,Checkout User,model_library_checkout,library_app.library_group_user,1,1,1,1 3 | checkout_line_user,Checkout Line User,model_library_checkout_line,library_app.library_group_user,1,1,1,1 4 | checkout_stage_user,Checkout Stage User,model_library_checkout_stage,library_app.library_group_user,1,0,0,0 5 | checkout_stage_manager,Checkout Stage Manager,model_library_checkout_stage,library_app.library_group_manager,1,1,1,1 6 | checkout_massmessage_user,Checkout Mass Message User,model_library_checkout_massmessage,library_app.library_group_user,1,1,1,1 -------------------------------------------------------------------------------- /source-code/ch08/library_app/views/library_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Library Books 7 | library.book 8 | tree,form 9 | 10 | 11 | 15 | 19 | -------------------------------------------------------------------------------- /source-code/ch04/library_app/views/templates.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 24 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/views/templates.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 24 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/views/templates.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 24 | -------------------------------------------------------------------------------- /source-code/ch03/library_app/tests/test_book.py: -------------------------------------------------------------------------------- 1 | from odoo.tests.common import TransactionCase 2 | 3 | 4 | class TestBook(TransactionCase): 5 | def setUp(self, *args, **kwargs): 6 | super().setUp(*args, **kwargs) 7 | user_admin = self.env.ref("base.user_admin") 8 | self.env = self.env(user=user_admin) 9 | self.Book = self.env['library.book'] 10 | self.book1 = self.Book.create({ 11 | "name": "Odoo Development Essentials", 12 | "isbn": "879-1-78439-279-6" 13 | }) 14 | 15 | def test_book_create(self): 16 | "New Books are active by default" 17 | self.assertEqual(self.book1.active, True) 18 | 19 | def test_check_isbn(self): 20 | "Check valid ISBN" 21 | self.assertTrue(self.book1._check_isbn) -------------------------------------------------------------------------------- /source-code/ch04/library_app/tests/test_book.py: -------------------------------------------------------------------------------- 1 | from odoo.tests.common import TransactionCase 2 | 3 | 4 | class TestBook(TransactionCase): 5 | def setUp(self, *args, **kwargs): 6 | super().setUp(*args, **kwargs) 7 | user_admin = self.env.ref("base.user_admin") 8 | self.env = self.env(user=user_admin) 9 | self.Book = self.env['library.book'] 10 | self.book1 = self.Book.create({ 11 | "name": "Odoo Development Essentials", 12 | "isbn": "879-1-78439-279-6" 13 | }) 14 | 15 | def test_book_create(self): 16 | "New Books are active by default" 17 | self.assertEqual(self.book1.active, True) 18 | 19 | def test_check_isbn(self): 20 | "Check valid ISBN" 21 | self.assertTrue(self.book1._check_isbn) -------------------------------------------------------------------------------- /source-code/ch05/library_app/__manifest__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | { 4 | "name": "Library Management", 5 | "summary": "Manage library catalog and book lending.", 6 | "author": "Alan Hou", 7 | "license": "AGPL-3", 8 | "category": "Services/Library", 9 | "website": "https://alanhou.org", 10 | "version": "15.0.1.0.0", 11 | "depends": ["base"], 12 | "data": [ 13 | "security/library_security.xml", 14 | "security/ir.model.access.csv", 15 | "views/book_view.xml", 16 | "views/library_menu.xml", 17 | "views/book_list_template.xml", 18 | ], 19 | "demo": [ 20 | "data/res.partner.csv", 21 | "data/library.book.csv", 22 | "data/book_demo.xml", 23 | ], 24 | "application": True, 25 | } 26 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/tests/test_book.py: -------------------------------------------------------------------------------- 1 | from odoo.tests.common import TransactionCase 2 | 3 | 4 | class TestBook(TransactionCase): 5 | def setUp(self, *args, **kwargs): 6 | super().setUp(*args, **kwargs) 7 | user_admin = self.env.ref("base.user_admin") 8 | self.env = self.env(user=user_admin) 9 | self.Book = self.env['library.book'] 10 | self.book1 = self.Book.create({ 11 | "name": "Odoo Development Essentials", 12 | "isbn": "879-1-78439-279-6" 13 | }) 14 | 15 | def test_book_create(self): 16 | "New Books are active by default" 17 | self.assertEqual(self.book1.active, True) 18 | 19 | def test_check_isbn(self): 20 | "Check valid ISBN" 21 | self.assertTrue(self.book1._check_isbn) -------------------------------------------------------------------------------- /source-code/ch06/library_app/__manifest__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | { 4 | "name": "Library Management", 5 | "summary": "Manage library catalog and book lending.", 6 | "author": "Alan Hou", 7 | "license": "AGPL-3", 8 | "category": "Services/Library", 9 | "website": "https://alanhou.org", 10 | "version": "15.0.1.0.0", 11 | "depends": ["base"], 12 | "data": [ 13 | "security/library_security.xml", 14 | "security/ir.model.access.csv", 15 | "views/book_view.xml", 16 | "views/library_menu.xml", 17 | "views/book_list_template.xml", 18 | ], 19 | "demo": [ 20 | "data/res.partner.csv", 21 | "data/library.book.csv", 22 | "data/book_demo.xml", 23 | ], 24 | "application": True, 25 | } 26 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/tests/test_book.py: -------------------------------------------------------------------------------- 1 | from odoo.tests.common import TransactionCase 2 | 3 | 4 | class TestBook(TransactionCase): 5 | def setUp(self, *args, **kwargs): 6 | super().setUp(*args, **kwargs) 7 | user_admin = self.env.ref("base.user_admin") 8 | self.env = self.env(user=user_admin) 9 | self.Book = self.env['library.book'] 10 | self.book1 = self.Book.create({ 11 | "name": "Odoo Development Essentials", 12 | "isbn": "879-1-78439-279-6" 13 | }) 14 | 15 | def test_book_create(self): 16 | "New Books are active by default" 17 | self.assertEqual(self.book1.active, True) 18 | 19 | def test_check_isbn(self): 20 | "Check valid ISBN" 21 | self.assertTrue(self.book1._check_isbn) -------------------------------------------------------------------------------- /source-code/ch08/library_app/__manifest__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | { 4 | "name": "Library Management", 5 | "summary": "Manage library catalog and book lending.", 6 | "author": "Alan Hou", 7 | "license": "AGPL-3", 8 | "category": "Services/Library", 9 | "website": "https://alanhou.org", 10 | "version": "15.0.1.0.0", 11 | "depends": ["base"], 12 | "data": [ 13 | "security/library_security.xml", 14 | "security/ir.model.access.csv", 15 | "views/book_view.xml", 16 | "views/library_menu.xml", 17 | "views/book_list_template.xml", 18 | ], 19 | "demo": [ 20 | "data/res.partner.csv", 21 | "data/library.book.csv", 22 | "data/book_demo.xml", 23 | ], 24 | "application": True, 25 | } 26 | -------------------------------------------------------------------------------- /source-code/ch08/library_app/tests/test_book.py: -------------------------------------------------------------------------------- 1 | from odoo.tests.common import TransactionCase 2 | 3 | 4 | class TestBook(TransactionCase): 5 | def setUp(self, *args, **kwargs): 6 | super().setUp(*args, **kwargs) 7 | user_admin = self.env.ref("base.user_admin") 8 | self.env = self.env(user=user_admin) 9 | self.Book = self.env['library.book'] 10 | self.book1 = self.Book.create({ 11 | "name": "Odoo Development Essentials", 12 | "isbn": "879-1-78439-279-6" 13 | }) 14 | 15 | def test_book_create(self): 16 | "New Books are active by default" 17 | self.assertEqual(self.book1.active, True) 18 | 19 | def test_check_isbn(self): 20 | "Check valid ISBN" 21 | self.assertTrue(self.book1._check_isbn) -------------------------------------------------------------------------------- /source-code/ch04/library_member/models/library_book.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | 3 | 4 | class Book(models.Model): 5 | _inherit = 'library.book' 6 | is_available = fields.Boolean('Is Available?') 7 | isbn = fields.Char(help="Use a valid ISBN-13 or ISBN-10.") 8 | publisher_id = fields.Many2one(index=True) 9 | 10 | def _check_isbn(self): 11 | self.ensure_one() 12 | isbn = self.isbn.replace('-', '') 13 | digits = [int(x) for x in isbn if x.isdigit()] 14 | if len(digits) == 10: 15 | ponderators = [1, 2, 3, 4, 5, 6, 7, 8, 9] 16 | total = sum(a * b for a, b in zip(digits[:9], ponderators)) 17 | check = total % 11 18 | return digits[-1] == check 19 | else: 20 | return super()._check_isbn() 21 | -------------------------------------------------------------------------------- /source-code/ch05/library_member/models/library_book.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | 3 | 4 | class Book(models.Model): 5 | _inherit = 'library.book' 6 | is_available = fields.Boolean('Is Available?') 7 | isbn = fields.Char(help="Use a valid ISBN-13 or ISBN-10.") 8 | publisher_id = fields.Many2one(index=True) 9 | 10 | def _check_isbn(self): 11 | self.ensure_one() 12 | isbn = self.isbn.replace('-', '') 13 | digits = [int(x) for x in isbn if x.isdigit()] 14 | if len(digits) == 10: 15 | ponderators = [1, 2, 3, 4, 5, 6, 7, 8, 9] 16 | total = sum(a * b for a, b in zip(digits[:9], ponderators)) 17 | check = total % 11 18 | return digits[-1] == check 19 | else: 20 | return super()._check_isbn() 21 | -------------------------------------------------------------------------------- /source-code/ch06/library_member/models/library_book.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | 3 | 4 | class Book(models.Model): 5 | _inherit = 'library.book' 6 | is_available = fields.Boolean('Is Available?') 7 | isbn = fields.Char(help="Use a valid ISBN-13 or ISBN-10.") 8 | publisher_id = fields.Many2one(index=True) 9 | 10 | def _check_isbn(self): 11 | self.ensure_one() 12 | isbn = self.isbn.replace('-', '') 13 | digits = [int(x) for x in isbn if x.isdigit()] 14 | if len(digits) == 10: 15 | ponderators = [1, 2, 3, 4, 5, 6, 7, 8, 9] 16 | total = sum(a * b for a, b in zip(digits[:9], ponderators)) 17 | check = total % 11 18 | return digits[-1] == check 19 | else: 20 | return super()._check_isbn() 21 | -------------------------------------------------------------------------------- /source-code/ch08/library_member/models/library_book.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | 3 | 4 | class Book(models.Model): 5 | _inherit = 'library.book' 6 | is_available = fields.Boolean('Is Available?') 7 | isbn = fields.Char(help="Use a valid ISBN-13 or ISBN-10.") 8 | publisher_id = fields.Many2one(index=True) 9 | 10 | def _check_isbn(self): 11 | self.ensure_one() 12 | isbn = self.isbn.replace('-', '') 13 | digits = [int(x) for x in isbn if x.isdigit()] 14 | if len(digits) == 10: 15 | ponderators = [1, 2, 3, 4, 5, 6, 7, 8, 9] 16 | total = sum(a * b for a, b in zip(digits[:9], ponderators)) 17 | check = total % 11 18 | return digits[-1] == check 19 | else: 20 | return super()._check_isbn() 21 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/models/library_book_category.py: -------------------------------------------------------------------------------- 1 | from odoo import api, fields, models 2 | 3 | 4 | class BookCategory(models.Model): 5 | _name = "library.book.category" 6 | _description = "Book Category" 7 | _parent_store = True 8 | name = fields.Char(translate=True, required=True) 9 | # Hierarchy fields 10 | parent_id = fields.Many2one( 11 | "library.book.category", 12 | "Parent Category", 13 | ondelete="restrict") 14 | parent_path = fields.Char(index=True) 15 | # Optional, but nice to have: 16 | child_ids = fields.Many2one( 17 | "library.book.category", 18 | "parent_id", 19 | "Subcategories") 20 | highlighted_id = fields.Reference( 21 | [('library.book', 'Book'), 22 | ('res.partner', 'Author')], 23 | 'Category Highlight' 24 | ) 25 | 26 | -------------------------------------------------------------------------------- /source-code/ch08/library_app/models/library_book_category.py: -------------------------------------------------------------------------------- 1 | from odoo import api, fields, models 2 | 3 | 4 | class BookCategory(models.Model): 5 | _name = "library.book.category" 6 | _description = "Book Category" 7 | _parent_store = True 8 | name = fields.Char(translate=True, required=True) 9 | # Hierarchy fields 10 | parent_id = fields.Many2one( 11 | "library.book.category", 12 | "Parent Category", 13 | ondelete="restrict") 14 | parent_path = fields.Char(index=True) 15 | # Optional, but nice to have: 16 | child_ids = fields.Many2one( 17 | "library.book.category", 18 | "parent_id", 19 | "Subcategories") 20 | highlighted_id = fields.Reference( 21 | [('library.book', 'Book'), 22 | ('res.partner', 'Author')], 23 | 'Category Highlight' 24 | ) 25 | 26 | -------------------------------------------------------------------------------- /source-code/ch04/library_app/controllers/controllers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # from odoo import http 3 | 4 | 5 | # class LibraryApp(http.Controller): 6 | # @http.route('/library_app/library_app', auth='public') 7 | # def index(self, **kw): 8 | # return "Hello, world" 9 | 10 | # @http.route('/library_app/library_app/objects', auth='public') 11 | # def list(self, **kw): 12 | # return http.request.render('library_app.listing', { 13 | # 'root': '/library_app/library_app', 14 | # 'objects': http.request.env['library_app.library_app'].search([]), 15 | # }) 16 | 17 | # @http.route('/library_app/library_app/objects/', auth='public') 18 | # def object(self, obj, **kw): 19 | # return http.request.render('library_app.object', { 20 | # 'object': obj 21 | # }) 22 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/controllers/controllers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # from odoo import http 3 | 4 | 5 | # class LibraryApp(http.Controller): 6 | # @http.route('/library_app/library_app', auth='public') 7 | # def index(self, **kw): 8 | # return "Hello, world" 9 | 10 | # @http.route('/library_app/library_app/objects', auth='public') 11 | # def list(self, **kw): 12 | # return http.request.render('library_app.listing', { 13 | # 'root': '/library_app/library_app', 14 | # 'objects': http.request.env['library_app.library_app'].search([]), 15 | # }) 16 | 17 | # @http.route('/library_app/library_app/objects/', auth='public') 18 | # def object(self, obj, **kw): 19 | # return http.request.render('library_app.object', { 20 | # 'object': obj 21 | # }) 22 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/controllers/controllers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # from odoo import http 3 | 4 | 5 | # class LibraryApp(http.Controller): 6 | # @http.route('/library_app/library_app', auth='public') 7 | # def index(self, **kw): 8 | # return "Hello, world" 9 | 10 | # @http.route('/library_app/library_app/objects', auth='public') 11 | # def list(self, **kw): 12 | # return http.request.render('library_app.listing', { 13 | # 'root': '/library_app/library_app', 14 | # 'objects': http.request.env['library_app.library_app'].search([]), 15 | # }) 16 | 17 | # @http.route('/library_app/library_app/objects/', auth='public') 18 | # def object(self, obj, **kw): 19 | # return http.request.render('library_app.object', { 20 | # 'object': obj 21 | # }) 22 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/views/library_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Checkouts 4 | library.checkout 5 | tree,form 6 | 7 | 11 | 12 | Stages 13 | library.checkout.stage 14 | tree,form 15 | 16 | 20 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/data/library_checkout_stage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Draft 4 | 10 5 | new 6 | 7 | 8 | Borrowed 9 | 20 10 | open 11 | 12 | 13 | Completed 14 | 90 15 | done 16 | 17 | 18 | Canceled 19 | 95 20 | cancel 21 | 22 | -------------------------------------------------------------------------------- /source-code/ch03/library_app/demo/demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | 30 | -------------------------------------------------------------------------------- /source-code/ch04/library_app/demo/demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | 30 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/demo/demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | 30 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/demo/demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | 30 | -------------------------------------------------------------------------------- /source-code/ch08/library_app/demo/demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | 30 | -------------------------------------------------------------------------------- /source-code/ch04/library_member/views/member_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Library Member Form View 4 | library.member 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 |
18 |
19 |
20 |
21 | 22 | Library Member List View 23 | library.member 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
-------------------------------------------------------------------------------- /source-code/ch05/library_member/views/member_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Library Member Form View 4 | library.member 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 |
18 |
19 |
20 |
21 | 22 | Library Member List View 23 | library.member 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
-------------------------------------------------------------------------------- /source-code/ch06/library_member/views/member_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Library Member Form View 4 | library.member 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 |
18 |
19 |
20 |
21 | 22 | Library Member List View 23 | library.member 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
-------------------------------------------------------------------------------- /source-code/ch08/library_member/views/member_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Library Member Form View 4 | library.member 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 |
18 |
19 |
20 |
21 | 22 | Library Member List View 23 | library.member 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
-------------------------------------------------------------------------------- /source-code/ch09/client_app/library_odoorpc.py: -------------------------------------------------------------------------------- 1 | import odoorpc 2 | 3 | 4 | class LibraryAPI(): 5 | def __init__(self, host, port, db, user, pwd): 6 | self.api = odoorpc.ODOO(host, port=port) 7 | self.api.login(db, user, pwd) 8 | self.uid = self.api.env.uid 9 | self.model = "library.book" 10 | self.Model = self.api.env[self.model] 11 | 12 | def _execute(self, method, arg_list, kwarg_dict=None): 13 | return self.api.execute( 14 | self.model, 15 | method, *arg_list, **kwarg_dict) 16 | 17 | def search_read(self, title=None): 18 | domain = [("name", "ilike", title)] if title else [] 19 | fields = ["id", "name"] 20 | return self.Model.search_read(domain, fields) 21 | 22 | def create(self, title): 23 | vals = {"name": title} 24 | return self.Model.create(vals) 25 | 26 | def write(self, id, title): 27 | vals = {"name": title} 28 | self.Model.write(id, vals) 29 | 30 | def unlink(self, id): 31 | return self.Model.unlink(id) 32 | 33 | 34 | if __name__ == "__main__": 35 | # 测试配置 36 | host, port, db = "localhost", 8069, "odoo-dev" 37 | user, pwd = "admin", "admin" 38 | api = LibraryAPI(host, port, db, user, pwd) 39 | from pprint import pprint 40 | pprint(api.search_read()) -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/wizard/checkout_mass_message_wizard_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Library Checkout Mass Message Wizard 4 | library.checkout.massmessage 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 |
13 |
16 |
17 |
18 |
19 | 20 | Send Messages 21 | library.checkout.massmessage 22 | form 23 | 24 | form,list 25 | new 26 | 27 |
-------------------------------------------------------------------------------- /source-code/ch03/library_app/security/library_security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | User 6 | 7 | 9 | 10 | 11 | 12 | Manager 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | Library Book User Access 23 | 24 | 25 | [('active', '=', True)] 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /source-code/ch04/library_app/security/library_security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | User 6 | 7 | 9 | 10 | 11 | 12 | Manager 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | Library Book User Access 23 | 24 | 25 | [('active', '=', True)] 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/security/library_security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | User 6 | 7 | 9 | 10 | 11 | 12 | Manager 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | Library Book User Access 23 | 24 | 25 | [('active', '=', True)] 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/security/library_security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | User 6 | 7 | 9 | 10 | 11 | 12 | Manager 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | Library Book User Access 23 | 24 | 25 | [('active', '=', True)] 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /source-code/ch08/library_app/security/library_security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | User 6 | 7 | 9 | 10 | 11 | 12 | Manager 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | Library Book User Access 23 | 24 | 25 | [('active', '=', True)] 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /source-code/ch09/client_app/library.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | # from library_xmlrpc import LibraryAPI 3 | from library_odoorpc import LibraryAPI 4 | 5 | parser = ArgumentParser() 6 | parser.add_argument( 7 | "command", 8 | choices=["list", "add", "set", "del"]) 9 | parser.add_argument("params", nargs="*") # 可选参数 10 | args = parser.parse_args() 11 | 12 | host, port, db = "localhost", 8069, "odoo-dev" 13 | user, pwd = "admin", "admin" 14 | api = LibraryAPI(host, port, db, user, pwd) 15 | 16 | 17 | if args.command == "list": 18 | title = args.params[:1] 19 | if len(title) != 0: 20 | title = title[0] 21 | books = api.search_read(title) 22 | for book in books: 23 | print("%(id)d %(name)s" % book) 24 | 25 | if args.command == "add": 26 | title = args.params[0] 27 | book_id = api.create(title) 28 | print("Book added with ID %d for title %s." % (book_id, title)) 29 | 30 | if args.command == "set": 31 | if len(args.params) != 2: 32 | print("set command requires a Title and ID.") 33 | else: 34 | book_id, title = int(args.params[0]), args.params[1] 35 | api.write(book_id, title) 36 | print("Title of Book ID %d set to %s." % (book_id, title)) 37 | 38 | if args.command == "del": 39 | book_id = int(args.params[0]) 40 | api.unlink(book_id) 41 | print("Book with ID %s was deleted." % book_id) -------------------------------------------------------------------------------- /source-code/ch03/library_app/models/library_book.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | from odoo.exceptions import ValidationError 3 | 4 | 5 | class Book(models.Model): 6 | _name = 'library.book' 7 | _description = 'Book' 8 | name = fields.Char("Title", required=True) 9 | isbn = fields.Char('ISBN') 10 | active = fields.Boolean('Active?', default=True) 11 | date_published = fields.Date() 12 | image = fields.Binary('Cover') 13 | publisher_id = fields.Many2one('res.partner', string='Publisher') 14 | author_ids = fields.Many2many('res.partner', string='Authors') 15 | 16 | def _check_isbn(self): 17 | self.ensure_one() 18 | isbn = self.isbn.replace('-', '') 19 | digits = [int(x) for x in isbn if x.isdigit()] 20 | if len(digits) == 13: 21 | ponderations = [1, 3] * 6 22 | terms = [a * b for a, b in zip(digits[:12], ponderations)] 23 | remain = sum(terms) % 10 24 | check = 10 - remain if remain !=0 else 0 25 | return digits[-1] == check 26 | 27 | def button_check_isbn(self): 28 | for book in self: 29 | if not book.isbn: 30 | raise ValidationError("Please provide an ISBN for %s" % book.name) 31 | if book.isbn and not book._check_isbn(): 32 | raise ValidationError("%s ISBN is invalid" % book.isbn) 33 | return True -------------------------------------------------------------------------------- /source-code/ch04/library_app/models/library_book.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | from odoo.exceptions import ValidationError 3 | 4 | 5 | class Book(models.Model): 6 | _name = 'library.book' 7 | _description = 'Book' 8 | name = fields.Char("Title", required=True) 9 | isbn = fields.Char('ISBN') 10 | active = fields.Boolean('Active?', default=True) 11 | date_published = fields.Date() 12 | image = fields.Binary('Cover') 13 | publisher_id = fields.Many2one('res.partner', string='Publisher') 14 | author_ids = fields.Many2many('res.partner', string='Authors') 15 | 16 | def _check_isbn(self): 17 | self.ensure_one() 18 | isbn = self.isbn.replace('-', '') 19 | digits = [int(x) for x in isbn if x.isdigit()] 20 | if len(digits) == 13: 21 | ponderations = [1, 3] * 6 22 | terms = [a * b for a, b in zip(digits[:12], ponderations)] 23 | remain = sum(terms) % 10 24 | check = 10 - remain if remain !=0 else 0 25 | return digits[-1] == check 26 | 27 | def button_check_isbn(self): 28 | for book in self: 29 | if not book.isbn: 30 | raise ValidationError("Please provide an ISBN for %s" % book.name) 31 | if book.isbn and not book._check_isbn(): 32 | raise ValidationError("%s ISBN is invalid" % book.isbn) 33 | return True -------------------------------------------------------------------------------- /source-code/ch05/library_app/models/library_book.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models 2 | from odoo.exceptions import ValidationError 3 | 4 | 5 | class Book(models.Model): 6 | _name = 'library.book' 7 | _description = 'Book' 8 | name = fields.Char("Title", required=True) 9 | isbn = fields.Char('ISBN') 10 | active = fields.Boolean('Active?', default=True) 11 | date_published = fields.Date() 12 | image = fields.Binary('Cover') 13 | publisher_id = fields.Many2one('res.partner', string='Publisher') 14 | author_ids = fields.Many2many('res.partner', string='Authors') 15 | 16 | def _check_isbn(self): 17 | self.ensure_one() 18 | isbn = self.isbn.replace('-', '') 19 | digits = [int(x) for x in isbn if x.isdigit()] 20 | if len(digits) == 13: 21 | ponderations = [1, 3] * 6 22 | terms = [a * b for a, b in zip(digits[:12], ponderations)] 23 | remain = sum(terms) % 10 24 | check = 10 - remain if remain !=0 else 0 25 | return digits[-1] == check 26 | 27 | def button_check_isbn(self): 28 | for book in self: 29 | if not book.isbn: 30 | raise ValidationError("Please provide an ISBN for %s" % book.name) 31 | if book.isbn and not book._check_isbn(): 32 | raise ValidationError("%s ISBN is invalid" % book.isbn) 33 | return True -------------------------------------------------------------------------------- /source-code/ch09/client_app/library_xmlrpc.py: -------------------------------------------------------------------------------- 1 | import xmlrpc.client 2 | 3 | 4 | class LibraryAPI: 5 | def __init__(self, host, port, db, user, pwd): 6 | common = xmlrpc.client.ServerProxy( 7 | "http://%s:%d/xmlrpc/2/common" % (host, port)) 8 | self.api = xmlrpc.client.ServerProxy( 9 | "http://%s:%d/xmlrpc/2/object" % (host, port)) 10 | self.uid = common.authenticate(db, user, pwd, {}) 11 | self.pwd = pwd 12 | self.db = db 13 | self.model = "library.book" 14 | 15 | def _execute(self, method, arg_list, kwarg_dict=None): 16 | return self.api.execute_kw( 17 | self.db, self.uid, self.pwd, self.model, 18 | method, arg_list, kwarg_dict or {}) 19 | 20 | def search_read(self, title=None): 21 | domain = [("name", "ilike", title)] if title else [] 22 | fields = ["id", "name"] 23 | return self._execute("search_read", [domain, fields]) 24 | 25 | def create(self, title): 26 | vals = {"name": title} 27 | return self._execute("create", [vals]) 28 | 29 | def write(self, id, title): 30 | vals = {"name": title} 31 | return self._execute("write", [[id], vals]) 32 | 33 | def unlink(self, id): 34 | return self._execute("unlink", [[id]]) 35 | 36 | 37 | if __name__ == "__main__": 38 | # 测试配置 39 | host, port, db = "localhost", 8069, "odoo-dev" 40 | user, pwd = "admin", "admin" 41 | api = LibraryAPI(host, port, db, user, pwd) 42 | from pprint import pprint 43 | 44 | pprint(api.search_read()) 45 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/tests/test_checkout_mass_message.py: -------------------------------------------------------------------------------- 1 | from odoo import exceptions 2 | from odoo.tests import common 3 | 4 | 5 | class TestWizard(common.SingleTransactionCase): 6 | def setUp(self,*args, **kwargs): 7 | super(TestWizard, self).setUp(*args, **kwargs) 8 | # 配置测试数据 9 | admin_user = self.env.ref("base.user_admin") 10 | self.Checkout = self.env["library.checkout"].with_user(admin_user) 11 | self.Wizard = self.env["library.checkout.massmessage"].with_user(admin_user) 12 | a_member = self.env["library.member"].create({"partner_id": admin_user.partner_id.id}) 13 | self.checkout0 = self.Checkout.create({"member_id": a_member.id}) 14 | 15 | def test_01_button_send(self): 16 | """发送按钮应对借阅记录创建消息""" 17 | count_before = len(self.checkout0.message_ids) 18 | Wizard0 = self.Wizard.with_context(active_ids=self.checkout0.ids) 19 | wizard0 = Wizard0.create({ 20 | "message_subject": "Hello", 21 | "message_body": "This is a message.", 22 | }) 23 | wizard0.button_send() 24 | count_after = len(self.checkout0.message_ids) 25 | self.assertEqual( 26 | count_before + 1, 27 | count_after, 28 | "Expected one additional message in the Checkout.", 29 | ) 30 | 31 | def test_02_button_send_empty_body(self): 32 | """消息体而空时发送按钮报错""" 33 | Wizard0 = self.Wizard.with_context(active_ids=self.checkout0.ids) 34 | wizard0 = Wizard0.create({}) 35 | with self.assertRaises(exceptions.UserError) as e: 36 | wizard0.button_send() 37 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/wizard/checkout_mass_message.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from odoo import api, exceptions, fields, models 3 | 4 | 5 | _logger = logging.getLogger(__name__) 6 | 7 | 8 | class CheckoutMassMessage(models.TransientModel): 9 | _name = "library.checkout.massmessage" 10 | _description = "Send Message to Borrowers" 11 | checkout_ids = fields.Many2many( 12 | "library.checkout", 13 | string="Checkouts" 14 | ) 15 | message_subject = fields.Char() 16 | message_body = fields.Html() 17 | 18 | @api.model 19 | def default_get(self, field_names): 20 | defaults_dict = super().default_get(field_names) 21 | checkout_ids = self.env.context["active_ids"] 22 | defaults_dict["checkout_ids"] = [(6, 0, checkout_ids)] 23 | return defaults_dict 24 | 25 | def button_send(self): 26 | # import pdb; pdb.set_trace() 27 | self.ensure_one() 28 | if not self.checkout_ids: 29 | raise exceptions.UserError("No checkouts were selected") 30 | if not self.message_body: 31 | raise exceptions.UserError("A message body is required") 32 | for checkout in self.checkout_ids: 33 | checkout.message_post( 34 | body=self.message_body, 35 | subject=self.message_subject, 36 | subtype_xmlid="mail.mt_comment" 37 | ) 38 | _logger.debug( 39 | "Message on %d to followers: %s", 40 | checkout.id, 41 | checkout.message_follower_ids) 42 | _logger.info( 43 | "Posted %d messages to the Checkouts: %s", 44 | len(self.checkout_ids), 45 | str(self.checkout_ids), 46 | ) 47 | return True 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Odoo开发手册 2 | 3 | 4 | 5 | ## Odoo 15开发手册目录 6 | 7 | * [第一章 使用开发者模式快速入门 Odoo 15](1.md) 8 | * [第二章 Odoo 15开发之开发环境准备](2.md) 9 | * [第三章 Odoo 15开发之创建第一个 Odoo 应用](3.md) 10 | * [第四章 Odoo 15开发之模块继承](4.md) 11 | * [第五章 Odoo 15开发之导入、导出以及模块数据](5.md) 12 | * [第六章 Odoo 15开发之模型 - 结构化应用数据](6.md) 13 | * [第七章 Odoo 15开发之记录集 - 使用模型数据](7.md) 14 | * [第八章 Odoo 15开发之业务逻辑 - 业务流程的支持](8.md) 15 | * [第九章 Odoo 15开发之外部 API - 集成第三方系统](9.md) 16 | 17 | 代码地址:[Source Code](./source-code/) 18 | 19 | ## Odoo 12开发手册目录 20 | 21 | * 第一章 [使用开发者模式快速入门 Odoo 12](https://github.com/iTranslateX/odoo-essentials/tree/v12/1.md) 22 | 23 | * 第二章 [Odoo 12开发之开发环境准备](https://github.com/iTranslateX/odoo-essentials/tree/v12/2.md) 24 | 25 | * 第三章 [Odoo 12 开发之创建第一个 Odoo 应用](https://github.com/iTranslateX/odoo-essentials/tree/v12/3.md) 26 | 27 | * 第四章 [Odoo 12 开发之模块继承](https://github.com/iTranslateX/odoo-essentials/tree/v12/4.md) 28 | 29 | * 第五章 [Odoo 12开发之导入、导出以及模块数据](https://github.com/iTranslateX/odoo-essentials/tree/v12/5.md) 30 | 31 | * 第六章 [Odoo 12开发之模型 - 结构化应用数据](https://github.com/iTranslateX/odoo-essentials/tree/v12/6.md) 32 | 33 | * 第七章 [Odoo 12开发之记录集 - 使用模型数据](https://github.com/iTranslateX/odoo-essentials/tree/v12/7.md) 34 | 35 | * 第八章 [Odoo 12开发之业务逻辑 - 业务流程的支持](https://github.com/iTranslateX/odoo-essentials/tree/v12/8.md) 36 | 37 | * 第九章 [Odoo 12开发之外部 API - 集成第三方系统](https://github.com/iTranslateX/odoo-essentials/tree/v12/9.md) 38 | 39 | * 第十章 [Odoo 12开发之后台视图 - 设计用户界面](https://github.com/iTranslateX/odoo-essentials/tree/v12/10.md) 40 | 41 | * 第十一章 [Odoo 12开发之看板视图和用户端 QWeb](https://github.com/iTranslateX/odoo-essentials/tree/v12/11.md) 42 | 43 | * 第十二章 [Odoo 12开发之报表和服务端 QWeb](https://github.com/iTranslateX/odoo-essentials/tree/v12/12.md) 44 | 45 | * 第十三章 [Odoo 12开发之创建网站前端功能](https://github.com/iTranslateX/odoo-essentials/tree/v12/13.md) 46 | 47 | * 第十四章 [Odoo 12开发之部署和维护生产实例](https://github.com/iTranslateX/odoo-essentials/tree/v12/14.md) 48 | 49 | 代码地址:[Source Code](https://github.com/iTranslateX/odoo-essentials/tree/v12/source-code) 50 | -------------------------------------------------------------------------------- /source-code/ch04/library_app/views/views.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 26 | 27 | 28 | 42 | 43 | 44 | 47 | 48 | 52 | 53 | 59 | 60 | -------------------------------------------------------------------------------- /source-code/ch05/library_app/views/views.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 26 | 27 | 28 | 42 | 43 | 44 | 47 | 48 | 52 | 53 | 59 | 60 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/views/views.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 26 | 27 | 28 | 42 | 43 | 44 | 47 | 48 | 52 | 53 | 59 | 60 | -------------------------------------------------------------------------------- /source-code/ch08/library_checkout/views/checkout_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Checkout Tree 4 | library.checkout 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Checkout Form 14 | library.checkout 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | 43 | 44 | 45 |
46 |
47 |
48 |
49 |
-------------------------------------------------------------------------------- /source-code/ch03/library_app/views/book_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Book Form 5 | library.book 6 | 7 |
8 |
9 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 | 30 | 31 | Book List 32 | library.book 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Book Filters 45 | library.book 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
-------------------------------------------------------------------------------- /source-code/ch04/library_app/views/book_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Book Form 5 | library.book 6 | 7 |
8 |
9 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 | 30 | 31 | Book List 32 | library.book 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Book Filters 45 | library.book 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
-------------------------------------------------------------------------------- /source-code/ch05/library_app/views/book_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Book Form 5 | library.book 6 | 7 |
8 |
9 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 | 30 | 31 | Book List 32 | library.book 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Book Filters 45 | library.book 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
-------------------------------------------------------------------------------- /source-code/ch06/library_app/views/book_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Book Form 5 | library.book 6 | 7 |
8 |
9 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 | Book List 36 | library.book 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Book Filters 49 | library.book 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
-------------------------------------------------------------------------------- /source-code/ch08/library_app/views/book_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Book Form 5 | library.book 6 | 7 |
8 |
9 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 | Book List 36 | library.book 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Book Filters 49 | library.book 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
-------------------------------------------------------------------------------- /source-code/ch08/library_checkout/models/library_checkout.py: -------------------------------------------------------------------------------- 1 | from odoo import api, fields, models, exceptions 2 | 3 | 4 | class Checkout(models.Model): 5 | _name = "library.checkout" 6 | _description = "Checkout Request" 7 | _inherit = ["mail.thread", "mail.activity.mixin"] 8 | member_id = fields.Many2one( 9 | "library.member", 10 | required=True, 11 | ) 12 | user_id = fields.Many2one( 13 | "res.users", 14 | "Librarian", 15 | default=lambda s: s.env.user, 16 | ) 17 | request_date = fields.Date( 18 | default=lambda s: fields.Date.today(), 19 | compute="_compute_request_date_onchange", 20 | store=True, 21 | readonly=False, 22 | ) 23 | line_ids = fields.One2many( 24 | 'library.checkout.line', 25 | 'checkout_id', 26 | string="Borrowed Books", 27 | ) 28 | 29 | @api.model 30 | def _default_stage_id(self): 31 | Stage = self.env["library.checkout.stage"] 32 | return Stage.search([("state", "=", "new")], limit=1) 33 | stage_id = fields.Many2one( 34 | "library.checkout.stage", 35 | default=_default_stage_id, 36 | group_expand="_group_expand_stage_id") 37 | state = fields.Selection(related="stage_id.state") 38 | checkout_date = fields.Date(readonly=True) 39 | close_date = fields.Date(readonly=True) 40 | 41 | @api.depends("member_id") 42 | def _compute_request_date_onchange(self): 43 | today_date = fields.Date.today() 44 | if self.request_date != today_date: 45 | self.request_date = today_date 46 | return { 47 | "warning": { 48 | "title": "Changed Request Date", 49 | "message": "Request date changed to today!", 50 | } 51 | } 52 | 53 | # @api.onchange("member_id") 54 | # def onchange_member_id(self): 55 | # today_date = fields.Date.today() 56 | # if self.request_date != today_date: 57 | # self.request_date = today_date 58 | # return { 59 | # "warning": { 60 | # "title": "Changed Request Date", 61 | # "message": "Request date changed to today!", 62 | # } 63 | # } 64 | 65 | @api.model 66 | def _group_expand_stage_id(self, stages, domain, order): 67 | return stages.search([], order=order) 68 | 69 | @api.model 70 | def create(self, vals): 71 | # 创建前执行的代码,应使用vals字典 72 | new_record = super().create(vals) 73 | # 创建后执行的代码,应使用new_record 74 | if new_record.stage_id.state in ('open', 'close'): 75 | raise exceptions.UserError("State not allowed for new checkouts.") 76 | return new_record 77 | 78 | def write(self, vals): 79 | # 写入之前的代码,self为老值 80 | if "stage_id" in vals: 81 | Stage = self.env["library.checkout.stage"] 82 | old_state = self.stage_id.state 83 | new_state = Stage.browse(vals["stage_id"]).state 84 | if new_state != old_state and new_state == "open": 85 | vals["checkout_date"] = fields.Date.today() 86 | if new_state != old_state and new_state == "done": 87 | vals["close_date"] = fields.Date.today() 88 | # old_state = self.stage_id.state 89 | super().write(vals) 90 | # 写入之后的代码,可使用更新后的self 91 | # new_state = self.stage_id.state 92 | # if not self.env.context.get("_checkout_write"): 93 | # if new_state != old_state and new_state == "open": 94 | # self.with_context(_checkout_write=True).write( 95 | # {"checkout_date": fields.Date.today()}) 96 | # if new_state != old_state and new_state == "done": 97 | # self.with_context(_checkout_write=True).write( 98 | # {"close_date": fields.Date.today()}) 99 | return True 100 | 101 | -------------------------------------------------------------------------------- /source-code/ch06/library_app/models/library_book.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models, api 2 | from odoo.exceptions import ValidationError 3 | 4 | 5 | class Book(models.Model): 6 | _name = 'library.book' 7 | _description = 'Book' 8 | _order = "name, date_published desc" 9 | _recname = "name" 10 | _table = "library_book" 11 | _log_access = True 12 | _auto = True 13 | # String fields 14 | name = fields.Char( 15 | "Title", 16 | default=None, 17 | help="Book cover title.", 18 | readonly=False, 19 | required=True, 20 | index=True, 21 | copy=False, 22 | deprecated=True, 23 | groups="", 24 | states={}, 25 | ) 26 | isbn = fields.Char('ISBN') 27 | book_type = fields.Selection( 28 | [("paper", "Paperback"), 29 | ("hard", "Hardcover"), 30 | ("electronic", "Electronic"), 31 | ("other", "Other")], 32 | "Type") 33 | notes = fields.Text("Internal Notes") 34 | desc = fields.Html("Description") 35 | # Numeric fields 36 | copies = fields.Integer(default=1) 37 | avg_rating = fields.Float("Average Rating", (3,2)) 38 | price = fields.Monetary("Price", "currency_id") 39 | # price helper 40 | currency_id = fields.Many2one("res.currency") 41 | # Date and time fields 42 | date_published = fields.Date() 43 | last_borrow_date = fields.Datetime( 44 | "Last Borrowed On", 45 | default=lambda self: fields.Datetime.now()) 46 | # Other fields: 47 | active = fields.Boolean('Active?', default=True) 48 | image = fields.Binary('Cover') 49 | # Relational fields 50 | publisher_id = fields.Many2one('res.partner', string='Publisher') 51 | author_ids = fields.Many2many('res.partner', string='Authors') 52 | publisher_country_id = fields.Many2one( 53 | "res.country", string="Publisher Country", 54 | related="publisher_id.country_id", 55 | readonly=False, 56 | # compute="_compute_publisher_country", 57 | # inverse="_inverse_publisher_country", 58 | # search="_search_publisher_country", 59 | ) 60 | 61 | _sql_constraints = [ 62 | ("library_book_name_uq", 63 | "UNIQUE (name, date_published)", 64 | "Title and publication date must be unique."), 65 | ("library_book_check_date", 66 | "CHECK (date_published <= current_date)", 67 | "Publication date must not be in the future."), 68 | ] 69 | 70 | @api.constrains("isbn") 71 | def _constrain_isbn_valid(self): 72 | for book in self: 73 | if book.isbn and not book._check_isbn(): 74 | raise ValidationError("%s is an invalid ISBN" % book.isbn) 75 | 76 | def _inverse_publisher_country(self): 77 | for book in self: 78 | book.publisher_id.country_id = book.publisher_country_id 79 | 80 | def _search_publisher_country(self, operator, value): 81 | return [ 82 | ("publisher_id.country_id", operator, value) 83 | ] 84 | 85 | @api.depends("publisher_id.country_id") 86 | def _compute_publisher_country(self): 87 | for book in self: 88 | book.publisher_country_id = book.publisher_id.country_id 89 | 90 | def _check_isbn(self): 91 | self.ensure_one() 92 | isbn = self.isbn.replace('-', '') 93 | digits = [int(x) for x in isbn if x.isdigit()] 94 | if len(digits) == 13: 95 | ponderations = [1, 3] * 6 96 | terms = [a * b for a, b in zip(digits[:12], ponderations)] 97 | remain = sum(terms) % 10 98 | check = 10 - remain if remain !=0 else 0 99 | return digits[-1] == check 100 | 101 | def button_check_isbn(self): 102 | for book in self: 103 | if not book.isbn: 104 | raise ValidationError("Please provide an ISBN for %s" % book.name) 105 | if book.isbn and not book._check_isbn(): 106 | raise ValidationError("%s ISBN is invalid" % book.isbn) 107 | return True -------------------------------------------------------------------------------- /source-code/ch08/library_app/models/library_book.py: -------------------------------------------------------------------------------- 1 | from odoo import fields, models, api 2 | from odoo.exceptions import ValidationError 3 | 4 | 5 | class Book(models.Model): 6 | _name = 'library.book' 7 | _description = 'Book' 8 | _order = "name, date_published desc" 9 | _recname = "name" 10 | _table = "library_book" 11 | _log_access = True 12 | _auto = True 13 | # String fields 14 | name = fields.Char( 15 | "Title", 16 | default=None, 17 | help="Book cover title.", 18 | readonly=False, 19 | required=True, 20 | index=True, 21 | copy=False, 22 | deprecated=True, 23 | groups="", 24 | states={}, 25 | ) 26 | isbn = fields.Char('ISBN') 27 | book_type = fields.Selection( 28 | [("paper", "Paperback"), 29 | ("hard", "Hardcover"), 30 | ("electronic", "Electronic"), 31 | ("other", "Other")], 32 | "Type") 33 | notes = fields.Text("Internal Notes") 34 | desc = fields.Html("Description") 35 | # Numeric fields 36 | copies = fields.Integer(default=1) 37 | avg_rating = fields.Float("Average Rating", (3,2)) 38 | price = fields.Monetary("Price", "currency_id") 39 | # price helper 40 | currency_id = fields.Many2one("res.currency") 41 | # Date and time fields 42 | date_published = fields.Date() 43 | last_borrow_date = fields.Datetime( 44 | "Last Borrowed On", 45 | default=lambda self: fields.Datetime.now()) 46 | # Other fields: 47 | active = fields.Boolean('Active?', default=True) 48 | image = fields.Binary('Cover') 49 | # Relational fields 50 | publisher_id = fields.Many2one('res.partner', string='Publisher') 51 | author_ids = fields.Many2many('res.partner', string='Authors') 52 | publisher_country_id = fields.Many2one( 53 | "res.country", string="Publisher Country", 54 | related="publisher_id.country_id", 55 | readonly=False, 56 | # compute="_compute_publisher_country", 57 | # inverse="_inverse_publisher_country", 58 | # search="_search_publisher_country", 59 | ) 60 | 61 | _sql_constraints = [ 62 | ("library_book_name_uq", 63 | "UNIQUE (name, date_published)", 64 | "Title and publication date must be unique."), 65 | ("library_book_check_date", 66 | "CHECK (date_published <= current_date)", 67 | "Publication date must not be in the future."), 68 | ] 69 | 70 | @api.constrains("isbn") 71 | def _constrain_isbn_valid(self): 72 | for book in self: 73 | if book.isbn and not book._check_isbn(): 74 | raise ValidationError("%s is an invalid ISBN" % book.isbn) 75 | 76 | def _inverse_publisher_country(self): 77 | for book in self: 78 | book.publisher_id.country_id = book.publisher_country_id 79 | 80 | def _search_publisher_country(self, operator, value): 81 | return [ 82 | ("publisher_id.country_id", operator, value) 83 | ] 84 | 85 | @api.depends("publisher_id.country_id") 86 | def _compute_publisher_country(self): 87 | for book in self: 88 | book.publisher_country_id = book.publisher_id.country_id 89 | 90 | def _check_isbn(self): 91 | self.ensure_one() 92 | isbn = self.isbn.replace('-', '') 93 | digits = [int(x) for x in isbn if x.isdigit()] 94 | if len(digits) == 13: 95 | ponderations = [1, 3] * 6 96 | terms = [a * b for a, b in zip(digits[:12], ponderations)] 97 | remain = sum(terms) % 10 98 | check = 10 - remain if remain !=0 else 0 99 | return digits[-1] == check 100 | 101 | def button_check_isbn(self): 102 | for book in self: 103 | if not book.isbn: 104 | raise ValidationError("Please provide an ISBN for %s" % book.name) 105 | if book.isbn and not book._check_isbn(): 106 | raise ValidationError("%s ISBN is invalid" % book.isbn) 107 | return True -------------------------------------------------------------------------------- /source-code/ch07/ch07_recorsets_code.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | >>> self 4 | res.users(1,) 5 | >>> self._name 6 | 'res.users' 7 | >>> self.name 8 | 'OdooBot' 9 | >>> self.login 10 | '__system__' 11 | 12 | >>> self.env 13 | 14 | 15 | >>> self.env["res.partner"].search([("display_name", "like", "Azure")]) 16 | res.partner(14, 26, 33, 27) 17 | 18 | >>> self.env.context 19 | {'lang': 'en_US', 'tz': 'Europe/Brussels'} 20 | 21 | >>> self.env.ref('base.user_root') 22 | res.users(1,) 23 | 24 | >>> self.env['res.partner'].search([('display_name', 'like', 'Lumber')]) 25 | res.partner(15, 34) 26 | 27 | >>> self.env['res.partner'].browse([15, 34]) 28 | res.partner(15, 34) 29 | """ 30 | 31 | >>> self.env["res.partner"].read_group([("display_name", "like", "Azure")], fields=["state_id:count_distinct",], groupby=["country_id"], lazy=False) 32 | [{'__count': 4, 'state_id': 1, 'country_id': (233, ), '__domain': ['&', ('country_id', '=', 233), ('display_name', 'like', 'Azure')]}] 33 | >>> self.env["res.country"].browse(233).name 34 | 'United States' 35 | 36 | >>> print(self.name) 37 | OdooBot 38 | 39 | >>> for rec in self: print(rec.name) 40 | ... 41 | OdooBot 42 | 43 | >>> self.company_id 44 | res.company(1,) 45 | >>> self.company_id.name 46 | 'YourCompany' 47 | >>> self.company_id.currency_id 48 | res.currency(1,) 49 | >>> self.company_id.currency_id.name 50 | 'EUR' 51 | 52 | >>> self.company_id.parent_id 53 | res.company() 54 | >>> self.company_id.parent_id.name 55 | False 56 | 57 | >>> self.browse(2).login_date 58 | datetime.datetime(2022, 5, 6, 3, 26, 21, 714562) 59 | 60 | >>> root = self.env["res.users"].browse(1) 61 | >>> print(root.name) 62 | OdooBot 63 | >>> root.name = "Superuser" 64 | >>> print(root.name) 65 | Superuser 66 | 67 | >>> from datetime import date 68 | >>> self.date = date(2020, 12, 1) 69 | >>> self.date 70 | datetime.date(2020, 12, 1) 71 | >>> self.date = "2020-12-02" 72 | >>> self.date 73 | datetime.date(2020, 12, 2) 74 | 75 | >>> import base64 76 | >>> blackdot_binary = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x04\x00\x00\x00\xb5\x1c\x0c\x02\x00\x00\x00\x0bIDATx\xdacd\xf8\x0f\x00\x01\x05\x01\x01'\x18\xe3f\x00\x00\x00\x00IEND\xaeB'\x82" 77 | >>> self.image_1920 = base64.b64encode(blackdot_binary).decode("utf-8") 78 | 79 | >>> self.child_ids = None 80 | >>> self.child_ids 81 | res.partner() 82 | 83 | >>> mycompany_partner = self.company_id.partner_id 84 | >>> myaddress = self.partner_id 85 | >>> mycompany_partner.child_ids = mycompany_partner.child_ids | myaddress 86 | 87 | >>> Partner = self.env["res.partner"] 88 | >>> recs = Partner.search([("name", "ilike", "Azure")]) 89 | >>> recs.write({"comment": "Hello!"}) 90 | True 91 | 92 | >>> self.write({ 'child_ids': address1 | address2}) 93 | 94 | >>> Partner = self.env['res.partner'] 95 | >>> new = Partner.create({'name': 'ACME', 'is_company': True}) 96 | >>> print(new) 97 | res.partner(56,) 98 | 99 | >>> rec = Partner.search([('name', '=', 'ACME')]) 100 | >>> rec.unlink() 101 | 2022-06-05 01:53:09,906 43 INFO odoo-dev odoo.models.unlink: User #1 deleted mail.message records with IDs: [22] 102 | 2022-06-05 01:53:09,952 43 INFO odoo-dev odoo.models.unlink: User #1 deleted res.partner records with IDs: [56] 103 | 2022-06-05 01:53:09,961 43 INFO odoo-dev odoo.models.unlink: User #1 deleted mail.followers records with IDs: [6] 104 | True 105 | 106 | >>> demo = self.env.ref("base.user_demo") 107 | >>> new = demo.copy({"name": "John", "login": "john@example.com"}) 108 | 109 | >>> from datetime import date 110 | >>> date.today() 111 | datetime.date(2022, 6, 5) 112 | >>> from datetime import timedelta 113 | >>> date(2022, 6, 5) + timedelta(days=7) 114 | datetime.date(2022, 6, 12) 115 | 116 | >>> from dateutil.relativedelta import relativedelta 117 | >>> date(2022, 6, 5) + relativedelta(years=1, months=1) 118 | datetime.date(2023, 7, 5) 119 | 120 | >>> from odoo.tools import date_utils 121 | >>> from datetime import datetime 122 | >>> now = datetime(2022, 6, 5, 0, 0, 0) 123 | >>> date_utils.start_of(now, 'week') 124 | datetime.datetime(2022, 5, 30, 0, 0) 125 | >>> date_utils.end_of(now, 'week') 126 | datetime.datetime(2022, 6, 5, 23, 59, 59, 999999) 127 | >>> today = date(2022, 6, 5) 128 | >>> date_utils.add(today, months=2) 129 | datetime.date(2022, 8, 5) 130 | >>> date_utils.subtract(today, months=2) 131 | datetime.date(2022, 4, 5) 132 | 133 | >>> date(2022, 6, 5).strftime("%d/%m/%Y") 134 | '05/06/2022' 135 | 136 | >>> from odoo import fields 137 | >>> fields.Datetime.to_datetime("2020-11-21 23:11:55") 138 | datetime.datetime(2020, 11, 21, 23, 11, 55) 139 | 140 | >>> from datetime import datetime 141 | >>> datetime.strptime("03/11/2020", "%d/%m/%Y") 142 | datetime.datetime(2020, 11, 3, 0, 0) 143 | 144 | >>> from datetime import datetime 145 | >>> import pytz 146 | >>> naive_date = datetime(2020, 12, 1, 0, 30, 0) 147 | >>> client_tz = self.env.context["tz"] 148 | >>> client_date = pytz.timezone(client_tz).localize(naive_date) 149 | >>> utc_date = client_date.astimezone(pytz.utc) 150 | >>> print(utc_date) 151 | 2020-11-30 23:30:00+00:00 152 | 153 | 154 | >>> rs0 = self.env["res.partner"].search([("display_name", "like", "Azure")]) 155 | >>> len(rs0) 156 | 4 157 | >>> rs0.filtered(lambda r: r.name.startswith("Nicole")) 158 | res.partner(27,) 159 | >>> rs0.filtered("is_company") 160 | res.partner(14,) 161 | >>> rs0.mapped("name") 162 | ['Azure Interior', 'Brandon Freeman', 'Colleen Diaz', 'Nicole Ford'] 163 | >>> rs0.sorted("name", reverse=True).mapped("name") 164 | ['Nicole Ford', 'Colleen Diaz', 'Brandon Freeman', 'Azure Interior'] 165 | >>> rs0.mapped(lambda r: (r.id, r.name)) 166 | [(14, 'Azure Interior'), (26, 'Brandon Freeman'), (33, 'Colleen Diaz'), (27, 'Nicole Ford')] 167 | 168 | >>> Partner = self.env['res.partner'] 169 | >>> recs = self.env['res.partner']>>> for i in range(3):... rec = Partner.create({"name": "Partner %s" % i}) 170 | ... recs |= rec 171 | ... 172 | >>> print(recs)res.partner(58, 59, 60) 173 | 174 | 175 | >>> self.env.cr.execute("SELECT id, login FROM res_users WHERE login=%s OR id=%s", ("demo", 1)) 176 | >>> self.env.cr.execute("SELECT id, login FROM res_users WHERE login=%(login)s OR id=%(id)s", {"login": "demo", "id": 1}) 177 | 178 | >>> self.env.cr.fetchall() 179 | [(1, '__system__'), (6, 'demo')] 180 | 181 | >>> self.env.cr.dictfetchall() 182 | [{'id': 1, 'login': '__system__'}, {'id': 6, 'login': 'demo'}] 183 | 184 | 185 | -------------------------------------------------------------------------------- /13.md: -------------------------------------------------------------------------------- 1 | # 第十三章 Odoo 12开发之创建网站前端功能 2 | 3 | 本文为[最好用的免费ERP系统Odoo 12开发手册](README.md)系列文章第十三篇。 4 | 5 | Odoo 起初是一个后台系统,但很快就有了前端界面的需求。早期基于后台界面的门户界面不够灵活并且对移动端不友好。为解决这一问题,Odoo 引入了新的网站功能,为系统添加了 CMS(Content Management System)内容管理系统。这使得我们无需集成第三方 CMS 便可创建美观又高效的前端。本文中我们将学习如何利用 Odoo 自带的网站功能开发面向前端的插件模块。 6 | 7 | 本文主要内容有: 8 | 9 | - 学习项目 - 自助图书馆 10 | - 第一个网页 11 | - 创建网站 12 | 13 | 14 | 15 | ## 开发准备 16 | 17 | 我将用第十一章 [Odoo 12开发之看板视图和用户端 QWeb](11.md)中最后编辑的library_checkout插件模块,代码请见[GitHub 仓库](source-code/chapter11)。本文完成后的代码也请参见[GitHub 仓库](source-code/chapter13)。 18 | 19 | ## 学习项目 - 自助图书馆 20 | 21 | 本文中我们将为图书会员添加一个自助服务功能。可供会员分别登录账号来访问他们的借阅请求列表。这样我们就可以学习网站开发的基本技术:创建动态页面、在页面间传递参数、创建表单以及处理表单数据验证。对这些新的图书网站功能,我们要新建一个插件模块library_website。 22 | 23 | 大家应该已经轻车熟路了,首先创建插件的声明文件ibrary_website/__manifest__.py,代码如下: 24 | 25 | ``` 26 | { 27 | 'name': 'Library Website', 28 | 'description': 'Create and check book checkout requests.', 29 | 'author': 'Alan Hou', 30 | 'depends': [ 31 | 'library_checkout' 32 | ], 33 | 'data': [ 34 | 'security/ir.model.access.csv', 35 | 'security/library_security.xml', 36 | 'views/library_member.xml', 37 | ], 38 | } 39 | ``` 40 | 41 | 网站功能将会依赖于library_checkout。我们并没有添加对website核心插件模块的依赖。website插件为创建完整功能的网站提供了有用的框架,但现在我们仅探讨核心框架自带的基础网站功能,尚无需使用website。我们想要图书会员通过登录信息在图书网站上访问自己的借阅请求。为此需要在图书会员模型中添加一个user_id字段,需要分别在模型和视图中添加,下面就开始进行网站的创建: 42 | 43 | 1、添加library_website/models/library_member.py文件 44 | 45 | ``` 46 | from odoo import fields, models 47 | 48 | class Member(models.Model): 49 | _inherit = 'library.member' 50 | user_id = fields.Many2one('res.users') 51 | ``` 52 | 53 | 2、添加library_website/models/__init__.py文件: 54 | 55 | ``` 56 | from . import library_member 57 | ``` 58 | 59 | 3、添加library_website/__init__.py文件: 60 | 61 | ``` 62 | from . import models 63 | ``` 64 | 65 | 4、添加library_website/views/library_member.xml文件: 66 | 67 | ``` 68 | 69 | 70 | 71 | Member Form 72 | library.member 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | ``` 82 | 83 | 访问这些网页的都是门户用户,无需访问后台菜单。我们需要为这个用户组设置安全访问权限,否则会在使用图书网站功能时报权限错误。 84 | 85 | 5、添加library_website/security/ir.model.access.csv文件,添加对图书模型的读权限: 86 | 87 | ``` 88 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 89 | access_book_portal,Book Portal Access,library_app.model_library_book,base.group_ 90 | portal,1,0,0,0 91 | access_member_portal,Member Portal Access,library_member.model_library_member,ba 92 | se.group_portal,1,0,0,0 93 | access_checkout_portal,Checkout Portal Access,library_checkout.model_library_che 94 | ckout,base.group_portal,1,0,0,0 95 | ``` 96 | 97 | 6、在library_website/security/library_security.xml文件中添加记录规则来限制门户用户所能访问的记录: 98 | 99 | ``` 100 | 101 | 102 | 103 | 104 | Library Member Portal Access 105 | 106 | 107 | [('user_id', '=', user.id)] 108 | 109 | 110 | 111 | 112 | 113 | Library Checkout Portal Access 114 | 115 | 116 | [('member_id.user_id', '=', user.id)] 117 | 118 | 119 | 120 | 121 | 122 | ``` 123 | 124 | base.group_portal是门户用户组的标识符。在创建门户用户时,应设置他们的用户类型为 Portal,而不是Internal User。这会让他们属于门户用户组并继承我们上面定义的访问权限: 125 | 126 | ![Odoo 12门户用户类型](http://alanhou.org/homepage/wp-content/uploads/2019/01/user-type-portal.jpg) 127 | 128 | 补充:以上内容需开启开发者模式才可见 129 | 130 | 一旦为图书会员创建了一个门户用户,就应在我们会员表单中的用户字段中使用。该登录信息将可以访问相应会员的借阅请求。 131 | 132 | > **小贴士:**在模型中使用 ACL 和记录规则来实现安全权限比使用控制器的逻辑要更为安全。这是因为攻击者有可能跳过网页控制器直接使用RPC 来访问模型 API 。 133 | 134 | 了解了这些,我们就可以开始实现图书网站的功能了。但首先我们来使用简单的Hello World网页简短地介绍下基本网站概念。 135 | 136 | ## 第一个网页 137 | 138 | 要开始了解 Odoo 网页开发的基础,我们将先实现一个Hello World网页来展示基本概念和技术。很有想象空间,是不是? 139 | 140 | 要创建第一个网页,我们需要一个控制器对象。首先来添加controllers/hello.py文件: 141 | 142 | 1、在library_website/__init__.py文件中添加如下行: 143 | 144 | ``` 145 | from . import controllers 146 | ``` 147 | 148 | 2、在library_website/controllers/__init__.py文件中添加如下行: 149 | 150 | ``` 151 | from . import hello 152 | ``` 153 | 154 | 3、添加实际的控制器文件 library_website/controllers/hello.py,代码如下: 155 | 156 | ``` 157 | from odoo import http 158 | 159 | class Hello(http.Controller): 160 | @http.route('/helloworld', auth="public") 161 | def helloworld(self): 162 | return('

Hello World!

') 163 | ``` 164 | 165 | odoo.http模块提供 Odoo 网页相关的功能。我们用于渲染页面的控制器,应该是一个继承了odoo.http.Controller类的对象。实际使用的名称并不是太重要,这里选择了 Hello(),一个常用的选择是 Main()。 166 | 167 | 在控制器类中使用了匹配 URL 路由的方法。这些路由用于做一些处理并返回结果,通常是返回用户网页浏览器的 HTML 页面。odoo.http.route装饰器用于为 URL 路由绑定方法,本例中使用的是/helloworld 路由。 168 | 169 | 安装library_website模块(~/odoo-dev/odoo/odoo-bin -d dev12 -i library_website)就可以在浏览器中打开http://xxx:8069/helloworld,我们应该就可以看到Hello World问候语了。 170 | 171 | 本例中方法执行的处理非常简单,它返回一个带有 HTML 标记的文本字符串,Hello World。 172 | 173 | > ℹ️使用这里的简单 URL 访问按制器,如果同一 Odoo 实例有多个数据库时,在没有指定目标数据库的情况下将会失败。这可通过在启动配置中设置-d或--db-filter来解决,参见第二章 [Odoo 12开发之开发环境准备](2.md)。 174 | 175 | 你可能注意到在路由装饰中使用了auth='public'参数,对于无需登录的用户开放的页面就需要使用它。如果删除该参数,仅有登录用户方可浏览此页面。如果没有活跃会话(session)则会进入登录页面。 176 | 177 | > **小贴士:**auth='public'参数实际表示如果访客未登录则使用public特殊用户运行网页控制器。如果登录了,则使用登录用户来代替public。 178 | 179 | ![Odoo 12 Hello World](http://alanhou.org/homepage/wp-content/uploads/2019/01/hello-world.jpg) 180 | 181 | ### 使用 QWeb 模板的 Hello World 182 | 183 | 使用 Python 字符串来创建 HTML 很快就会觉得乏味。QWeb可用来增添色彩,下面就使用模板来写一个改进版的Hello World网页。QWeb模板通过 XML 数据文件添加,技术层面上它是与表单、列表视图类似的一种视图类型。它们甚至存储在同一个技术模型ir.ui.view中。 184 | 185 | 老规矩,需要在声明文件中添加声明来加载文件,编辑library_website/__manifest__.py文件并添加内容如下: 186 | 187 | ``` 188 | 'data': [ 189 | ... 190 | 'views/helloworld_template.xml', 191 | ], 192 | ``` 193 | 194 | 然后添加实际的数据文件views/helloworld_template.xml,内容如下: 195 | 196 | ``` 197 | 198 | 199 | 202 | 203 | ``` 204 | 205 |