Compare commits
54 Commits
Author | SHA1 | Date |
---|---|---|
Fabien BOURGEOIS | a70b630c71 | |
Fabien BOURGEOIS | 7d874226be | |
Fabien BOURGEOIS | b4c272a3da | |
Fabien BOURGEOIS | bc19f41588 | |
Fabien BOURGEOIS | 48f5e75d8d | |
Fabien BOURGEOIS | e043acfca4 | |
Fabien BOURGEOIS | 908ed5f723 | |
Fabien BOURGEOIS | f6982b7d16 | |
Fabien BOURGEOIS | e678fac46e | |
Fabien BOURGEOIS | 1d10e5a89a | |
Fabien BOURGEOIS | dbcfdc14e5 | |
Fabien BOURGEOIS | 85e8663218 | |
Fabien BOURGEOIS | 747afe2f14 | |
Fabien BOURGEOIS | 1296e952af | |
Fabien BOURGEOIS | f1261c1085 | |
Fabien BOURGEOIS | cf6f40220a | |
Fabien BOURGEOIS | ae30f47886 | |
Fabien BOURGEOIS | 1a4bed211a | |
Fabien BOURGEOIS | 6db81ee501 | |
Fabien BOURGEOIS | 214503ae4f | |
Fabien BOURGEOIS | 40f6c88fee | |
Fabien BOURGEOIS | b75fded6f5 | |
Fabien BOURGEOIS | b08752d767 | |
Fabien BOURGEOIS | 6b49ee69f4 | |
Fabien BOURGEOIS | cc40759909 | |
Fabien BOURGEOIS | c2ad72a3a6 | |
Fabien BOURGEOIS | 0cc980b7a9 | |
Fabien BOURGEOIS | b6f62571b2 | |
Fabien BOURGEOIS | f5d90d83cf | |
Fabien BOURGEOIS | 8767b1aaeb | |
Fabien BOURGEOIS | 401b3b5ffc | |
Fabien BOURGEOIS | d7aeafdc6f | |
Fabien BOURGEOIS | 265453589b | |
Fabien BOURGEOIS | 2e501018d8 | |
Fabien BOURGEOIS | 0d40afd498 | |
Fabien BOURGEOIS | 360832d876 | |
Fabien BOURGEOIS | 264d1e37b2 | |
Fabien BOURGEOIS | c338738e2b | |
Fabien BOURGEOIS | 4f6399c576 | |
Fabien BOURGEOIS | ebef43ad8a | |
Fabien BOURGEOIS | d0a7b6a74e | |
Fabien BOURGEOIS | f6b892efd4 | |
Fabien BOURGEOIS | f2c8818e43 | |
Fabien BOURGEOIS | aa7bfe9449 | |
Fabien BOURGEOIS | 291d4fc394 | |
Fabien BOURGEOIS | 095f4ce7d9 | |
Fabien BOURGEOIS | dd846361cf | |
Fabien BOURGEOIS | d46a51a330 | |
Fabien BOURGEOIS | 2a1a765222 | |
Fabien BOURGEOIS | 3b3846526e | |
Fabien BOURGEOIS | b7c203ac0d | |
Fabien BOURGEOIS | 88e66e0fde | |
Fabien BOURGEOIS | 08d143a5f6 | |
Fabien BOURGEOIS | 4ca4d5169c |
|
@ -22,12 +22,12 @@
|
|||
* For CSV published by Crédit Coopératif on their website customer space ;
|
||||
* Adds option on bank import statement wizard ;
|
||||
* Checks and processes file.""",
|
||||
'version': '10.0.0.1.3',
|
||||
'version': '12.0.0.1.3',
|
||||
'category': 'Banking addons',
|
||||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': ['account_bank_statement_import'],
|
||||
'data': ['wizard/account_bank_statement_import_views.xml']
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from . import override
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
{
|
||||
'name': 'DBFilter from configuration',
|
||||
'summary': 'Database filtering from configuration',
|
||||
'description': """ Database filtering from configuration :
|
||||
- use Odoo own filtering mechanism fist ;
|
||||
- then apply configuration : `domains_to_databases` : string in form of `database:domain1,domain2|database2:domain3`.
|
||||
|
||||
Needs to be added to `server_wide_modules` on configuration or `--load`,
|
||||
for example `server_wide_modules = base,web,dbfilter_from_config`.
|
||||
Also required : `proxy_mode` to `True`
|
||||
|
||||
Inspired from OCA `dbfilter_from_header`.""",
|
||||
'version': '12.0.0.0.6',
|
||||
'category': 'Yaltik',
|
||||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'depends': ['base']
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" Override of databse filtering to allow new features """
|
||||
|
||||
from re import match
|
||||
from json import loads
|
||||
from odoo import http
|
||||
from odoo.tools import config
|
||||
|
||||
db_filter_odoo = http.db_filter
|
||||
|
||||
# Cache usage
|
||||
db_filter_dict = {}
|
||||
all_hosts = []
|
||||
db_filter_str = config.get('database_from_hosts', False)
|
||||
if db_filter_str:
|
||||
for entry in db_filter_str.split('|'):
|
||||
database, hosts = entry.split(':')
|
||||
hosts = hosts.split(',')
|
||||
db_filter_dict[database] = hosts
|
||||
all_hosts += hosts
|
||||
http_host_resolv = {}
|
||||
|
||||
def db_filter(dbs, httprequest=None):
|
||||
""" Override db_filter """
|
||||
if db_filter_str:
|
||||
httprequest = httprequest or http.request.httprequest
|
||||
http_host = httprequest.environ.get('HTTP_HOST', '').split(':')[0]
|
||||
# If in cache and not empty
|
||||
if http_host in http_host_resolv and http_host_resolv.get(http_host):
|
||||
dbs = http_host_resolv[http_host]
|
||||
elif http_host in all_hosts:
|
||||
for database, hosts in db_filter_dict.items():
|
||||
for host in hosts:
|
||||
if host == http_host:
|
||||
dbs = [i for i in dbs if match(database, i)]
|
||||
break
|
||||
if http_host not in http_host_resolv:
|
||||
http_host_resolv[http_host] = dbs
|
||||
else:
|
||||
dbs = db_filter_odoo(dbs, httprequest)
|
||||
else:
|
||||
dbs = db_filter_odoo(dbs, httprequest)
|
||||
return dbs
|
||||
|
||||
|
||||
if (config.get('proxy_mode')
|
||||
and 'dbfilter_from_config' in config.get('server_wide_modules')):
|
||||
http.db_filter = db_filter
|
|
@ -20,12 +20,12 @@
|
|||
'name': 'Grants follow-ups',
|
||||
'summary': 'Grants follow-ups ',
|
||||
'description': 'Grants follow-ups ',
|
||||
'version': '10.0.1.0.0',
|
||||
'version': '12.0.1.0.0',
|
||||
'category': 'Membership',
|
||||
'author': 'Fabien Bourgeois, Youssef ELOUAHBY',
|
||||
'license': 'AGPL-3',
|
||||
'application': True,
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': ['mail', 'document'],
|
||||
'data': ['security/security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
.PHONY: clean
|
||||
clean:
|
||||
find . -name '*.pyc' -delete
|
||||
find . -name '__pycache__' -delete
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
/opt/odoo/OCB/odoo-bin --test-tags hyodoo --workers 0 -d test -i hy_odoo --stop-after-init
|
|
@ -0,0 +1,7 @@
|
|||
# Copyright 2020-2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
import hy
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2019-2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
{
|
||||
'name': 'Hy Odoo',
|
||||
'summary': 'Hy Odoo',
|
||||
'description': """ Hy functions and macros for Odoo """,
|
||||
'version': '16.0.0.8.9',
|
||||
'category': 'Hy',
|
||||
'author': 'Fabien Bourgeois',
|
||||
'license': 'Other OSI approved licence',
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'depends': ['base'],
|
||||
"external_dependencies": {'python' : ['hy==0.25.*', 'hyrule==0.2.*']}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
;; Copyright 2021-2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
;;
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
" Hy General Macros "
|
||||
|
||||
(defmacro instance? [type record] `(isinstance ~record ~type))
|
||||
|
||||
(defmacro hydict [dic]
|
||||
"Generate dict with mangled keys, from HyDict list"
|
||||
(setv mangled-dic
|
||||
(list
|
||||
(map
|
||||
(fn [pair]
|
||||
(if (even? (nth pair 0))
|
||||
(hy.mangle (nth pair 1))
|
||||
(nth pair 1)))
|
||||
(enumerate dic))))
|
||||
`{~@mangled-dic})
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
;; Copyright 2019-2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
;;
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
" Odoo related macros "
|
||||
|
||||
(defmacro o-cmod [model-name] `(. cls env [~model-name]))
|
||||
(defmacro o-mod [model-name] `(. self env [~model-name]))
|
||||
(defmacro o-cref [ref-name] `((. cls env ref) ~ref-name))
|
||||
(defmacro o-ref [ref-name] `((. self env ref) ~ref-name))
|
||||
|
||||
(defmacro hydm [hy-domain]
|
||||
"Generate Odoo domain from Hy like tuple domain"
|
||||
(setv op (second hy-domain)
|
||||
field (hy.mangle (get hy-domain 2))
|
||||
value (get hy-domain 3))
|
||||
`#(~field ~op ~value))
|
||||
|
||||
; Odoo ORM macros
|
||||
|
||||
#_(defmacro/g! compute-fn [field dependencies body]
|
||||
"Macro to make computed definition smoother"
|
||||
(setv fname f"_compute_{(hy.mangle field)}" descr f"Computes {field}"
|
||||
dependencies (list (map hy.mangle dependencies)))
|
||||
(import hy.models [HySymbol]
|
||||
`(defn [(.depends api ~@dependencies)] ~(HySymbol fname) [self]
|
||||
~descr
|
||||
~body)))
|
||||
|
||||
(defmacro compute-field [fname body]
|
||||
"Takes fname Symbol and body to create computed field"
|
||||
(setv fn-name f"_compute_{(hy.mangle fname)}")
|
||||
`(setv ~fname (~@body :compute ~fn-name)))
|
||||
|
||||
; Backend macros
|
||||
|
||||
(defmacro __ [sentence] `((py "_") ~sentence))
|
||||
|
||||
(defmacro logger []
|
||||
`(do
|
||||
(import logging)
|
||||
(setv _logger (.getLogger logging --name--))))
|
||||
|
||||
(defmacro pdb [] `(do (import [pdb [set-trace]]) (set-trace)))
|
|
@ -0,0 +1,35 @@
|
|||
;; Copyright 2021-2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
;;
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
" Hy Odoo Tests Helpers and Macros "
|
||||
|
||||
(defmacro o-assert-equal [left right] `(.assertEqual self ~left ~right))
|
||||
(defmacro o-assert-list-equal [left right] `(.assertListEqual self ~left ~right))
|
||||
(defmacro o-assert-dict-equal [left right] `(.assertDictEqual self ~left ~right))
|
||||
(defmacro o-assert-not-equal [left right] `(.assertNotEqual self ~left ~right))
|
||||
(defmacro o-assert-true [val] `(.assertTrue self ~val))
|
||||
(defmacro o-assert-false [val] `(.assertFalse self ~val))
|
||||
(defmacro o-assert-is [left right] `(.assertIs self ~left ~right))
|
||||
(defmacro o-assert-is-not [left right] `(.assertIsNot self ~left ~right))
|
||||
(defmacro o-assert-is-none [val] `(.assertIsNone self ~val))
|
||||
(defmacro o-assert-is-not-none [val] `(.assertIsNotNone self ~val))
|
||||
(defmacro o-assert-in [left right] `(.assertIn self ~left ~right))
|
||||
(defmacro o-assert-not-in [left right] `(.assertNotIn self ~left ~right))
|
||||
(defmacro o-assert-is-instance [left right] `(.assertIsInstance self ~left ~right))
|
||||
(defmacro o-assert-not-is-instance [left right] `(.assertNotIsInstance self ~left ~right))
|
||||
(defmacro o-assert-raises [Error] `(.assertRaises self ~Error))
|
||||
(defmacro o-assert-raises-regex [Error regexp] `(.assertRaisesRegex self ~Error ~regexp))
|
||||
|
||||
(defmacro odo-assert-raises [Error body]
|
||||
"Macro to test Error with self.assertRaises and do block"
|
||||
`(with [err (.assertRaises self ~Error)] ~body))
|
||||
|
||||
(defmacro o-test [name body]
|
||||
"Macro for unit test case"
|
||||
(setv fn-name (% "test-%s" (str name))
|
||||
humanized-name (.replace (str name) "-" " ")
|
||||
descr (bytes (% "Test %s" humanized-name)))
|
||||
`(defn ~(HySymbol fn-name) [self] ~descr ~body))
|
|
@ -0,0 +1,175 @@
|
|||
;; Copyright 2019-2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
;;
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
" Odoo macros and helpers "
|
||||
|
||||
(require hyrule.collections [assoc]
|
||||
odoo.addons.hy-odoo.mgeneral [instance?])
|
||||
(import os [path]
|
||||
odoo.addons.hy-odoo.xml [xmlroot xmln])
|
||||
|
||||
; Global helpers
|
||||
|
||||
(defn strdm [hy-domain]
|
||||
"Generate Odoo domain from Hy like tuple domain"
|
||||
(do
|
||||
(setv #(op field value) hy-domain
|
||||
field (hy.mangle field)
|
||||
value (when (string? value) f"'{value}'" value))
|
||||
(return f"('{field}', '{op}', {value})")))
|
||||
|
||||
; XML helpers functions and macros
|
||||
|
||||
(defn odoo [children] (xmlroot {"tag" "odoo" "attrs" {} "children" children}))
|
||||
|
||||
(defn data [#* args]
|
||||
"Special data node, allow optional args on data tag"
|
||||
(when (= (len args) 1) (do (setv args (list args))
|
||||
(.insert args 0 {})))
|
||||
(xmln "data" #* args))
|
||||
|
||||
; Aliases
|
||||
(defn function [#* args] (xmln "function" #*args))
|
||||
(defn record [#* args] (xmln "record" #*args))
|
||||
(defn form [#* args] (xmln "form" #*args))
|
||||
(defn tree [#* args] (xmln "tree" #*args))
|
||||
(defn search [#* args] (xmln "search" #*args))
|
||||
|
||||
; Actions
|
||||
(defn act-window [#* args] (xmln "act_window" #*args))
|
||||
|
||||
(defn act-window-model [model attrs]
|
||||
" Build new act_window from model and args"
|
||||
(setv model-und (.replace model "." "_")
|
||||
model-cap (.join " " (lfor w (.split model ".") (.capitalize w)))
|
||||
xmlid f"{model-und}_view_action"
|
||||
name f"{model-cap} Action"
|
||||
cloned-attrs (.copy attrs)) ; Avoid side-effect
|
||||
(.update cloned-attrs {"id" xmlid "name" name "res_model" model})
|
||||
(act-window cloned-attrs))
|
||||
|
||||
(defn actions-server-code [xmlid name modelref code]
|
||||
"Server actions of type code"
|
||||
(record {"id" xmlid "model" "ir.actions.server"}
|
||||
[(field-name name)
|
||||
(field {"name" "model_id" "ref" modelref} [])
|
||||
(field {"name" "state"} ["code"])
|
||||
(field {"name" "code"} [code])]))
|
||||
|
||||
(defn client-action-multi [xmlid name model action]
|
||||
"Client action multi (ir.values), with own xmlid, name, targeted model and
|
||||
action"
|
||||
(setv action f"'ir.actions.server,%d'%{action}")
|
||||
(record {"id" xmlid "model" "ir.values"}
|
||||
[(field-name name)
|
||||
(field {"name" "key2" "eval" "'client_action_multi'"} [])
|
||||
(field {"name" "model" "eval" (% "'%s'" model)} [])
|
||||
(field {"name" "value" "eval" action})]))
|
||||
|
||||
; Menus
|
||||
(defn menuitem [#* args] (xmln "menuitem" #*args))
|
||||
|
||||
(defn menuitem-model [model attrs]
|
||||
" Build new menuitem from model and attrs"
|
||||
(setv model-und (.replace model "." "_")
|
||||
actionid f"{model-und}_view_action"
|
||||
xmlid f"{model-und}_menu"
|
||||
cloned-attrs (.copy attrs))
|
||||
(.update cloned-attrs {"id" xmlid "action" actionid})
|
||||
(menuitem cloned-attrs))
|
||||
|
||||
; Form aliases
|
||||
(defn group [#* args] (xmln "group" #*args))
|
||||
(defn header [#* args] (xmln "header" #*args))
|
||||
(defn footer [#* args] (xmln "footer" #*args))
|
||||
(defn sheet [#* args] (xmln "sheet" #*args))
|
||||
(defn button [#* args] (xmln "button" #*args))
|
||||
(defn p [#* args] (xmln "p" #*args))
|
||||
(defn xpath [#* args] (xmln "xpath" #*args))
|
||||
(defn attribute [name value] (xmln "attribute" {"name" name} [value]))
|
||||
|
||||
; Fields
|
||||
(defn field [#* args]
|
||||
"Special field allowing mangling name attribute"
|
||||
(setv attrs (get args 0))
|
||||
(when (and (instance? dict attrs) (in "name" attrs))
|
||||
(assoc attrs "name" (hy.mangle (get attrs "name")))
|
||||
(setv args (list args))
|
||||
(assoc args 0 attrs)
|
||||
(setv args (tuple args)))
|
||||
(xmln "field" #*args))
|
||||
|
||||
(defn field-name [name] (field {"name" "name"} [name]))
|
||||
(defn field-nval [name value] (field {"name" name} [value]))
|
||||
(defn field-model [model] (field {"name" "model"} [model]))
|
||||
(defn field-inherit [xmlid] (field {"name" "inherit_id" "ref" xmlid} []))
|
||||
(defn field-arch [#* args] (field {"name" "arch" "type" "xml"} #*args))
|
||||
|
||||
; Search
|
||||
(defn filter [#* args] (xmln "filter" #*args))
|
||||
|
||||
; Views
|
||||
(defn view [xmlid children] (record {"id" xmlid "model" "ir.ui.view"} children))
|
||||
|
||||
(defn view-def [xmlid name model arch]
|
||||
"View and first fields simplification with record xmlid, name, targeted model"
|
||||
(view xmlid [(field-name name) (field-model model) (field-arch arch)]))
|
||||
|
||||
(defn view-new[view_type model arch]
|
||||
"View : new view definition, based on type (form, tree, ...) and model ID"
|
||||
(setv model-und (.replace model "." "_")
|
||||
model-cap (.join " " (lfor w (.split model ".") (.capitalize w)))
|
||||
xmlid f"{model-und}_view_{view_type}"
|
||||
name f"{model-cap} {(.capitalize view_type)}")
|
||||
(view-def :xmlid xmlid :name name :model model :arch arch))
|
||||
|
||||
(defn view-inherit [filename model inherit arch]
|
||||
"Inherited View simplification with name of the record, xmlid for model and
|
||||
inherited view"
|
||||
(setv module (get (.split filename ".") 2)
|
||||
inherited (get (.split inherit ".") 1)
|
||||
xmlid f"{inherited}_inherit_{module}"
|
||||
model-cap (.join " " (lfor w (.split model ".") (.capitalize w)))
|
||||
name f"{model-cap} Adaptations")
|
||||
(view xmlid [(field-name name)
|
||||
(field-model model)
|
||||
(field-inherit inherit)
|
||||
(field-arch arch)]))
|
||||
|
||||
; Hy-related helpers
|
||||
|
||||
(defn modulehytopy [module-path hy-files]
|
||||
"Transforms hy to py for translation purpose"
|
||||
(import astor)
|
||||
(import os.path [dirname]
|
||||
io [open :as iopen]
|
||||
hy.lex [hy-parse]
|
||||
hy.compiler [hy-compile]
|
||||
hy.errors [filtered-hy-exceptions])
|
||||
|
||||
(defn hytopy [source path]
|
||||
"Hy source to Py source"
|
||||
(setv hst (with [(filtered-hy-exceptions)] (hy-parse source :filename path))
|
||||
-ast (with [(filtered-hy-exceptions)] (hy-compile hst "__main__" :filename path :source source)))
|
||||
(.to-source (. astor code-gen) -ast))
|
||||
|
||||
(for [hy-file hy-files]
|
||||
(setv hy-path (% "%s/%s.hy" #((dirname module-path) hy-file))
|
||||
hy-source (with [o (iopen hy-path "r" :encoding "utf-8")] (.read o))
|
||||
output-path (.replace hy-path ".hy" ".py")
|
||||
content ["# Generate from Hy AST, for Babel translation purpose only."
|
||||
"# For real source code, please see and use HY source."
|
||||
(hytopy hy-source hy-path)])
|
||||
(setv output-py (.join "\n" content))
|
||||
(with [f (iopen output-path "w")] (.write f output-py))))
|
||||
|
||||
; Migrations
|
||||
|
||||
(defn generate-fn-name [filepath]
|
||||
"Generate function name from filepath"
|
||||
(setv version (.replace (get (.split (.dirname path filepath) "/") -1) "." "_")
|
||||
pre-post (get (.split (.basename path filepath) "-") 0))
|
||||
f"{pre-post}_{version}")
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
import hy
|
||||
from . import test_xml, test_odoo
|
|
@ -0,0 +1,284 @@
|
|||
;; Copyright 2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
;;
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
" Odoo Helpers tests "
|
||||
|
||||
(require
|
||||
hyrule.control [defmain]
|
||||
odoo.addons.hy-odoo.mtest *)
|
||||
|
||||
(import unittest
|
||||
functools [partial]
|
||||
xml.etree.ElementTree :as ET
|
||||
odoo.tests [tagged]
|
||||
odoo.tests.common [TransactionCase]
|
||||
odoo.addons.hy-odoo.xml [XMLDictElement]
|
||||
odoo.addons.hy-odoo.odoo :as od)
|
||||
|
||||
(defclass [(tagged "hyodoo" "hyodoo.odoo")] TextOdooBase [TransactionCase]
|
||||
"Odoo Helpers tests"
|
||||
|
||||
(defn test-odoo [self]
|
||||
" Test odoo function "
|
||||
(setv element (od.odoo []))
|
||||
(o-assert-is-instance element (. ET Element))
|
||||
(o-assert-equal (. element tag) "odoo"))
|
||||
|
||||
(defn test-data [self]
|
||||
" Test data function "
|
||||
(setv element (od.data []))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal element.tag "data")
|
||||
(o-assert-equal element.attrs {})
|
||||
|
||||
(setv element (od.data {"one" "attribute"} []))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal element.tag "data")
|
||||
(o-assert-equal element.attrs {"one" "attribute"}))
|
||||
|
||||
(defn test-aliases [self]
|
||||
" Test simple aliases to xmln "
|
||||
(setv element (od.record {"one" "attribute"} "A child"))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal element.tag "record")
|
||||
(o-assert-equal element.attrs {"one" "attribute"})
|
||||
(o-assert-equal element.children ["A child"])
|
||||
|
||||
(setv element (od.tree))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal element.tag "tree"))
|
||||
|
||||
|
||||
(defn test-act-window-model [self]
|
||||
" Test act_window function "
|
||||
(setv element (od.act_window_model "sample.model" {"view_type" "form"}))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal element.tag "act_window")
|
||||
(o-assert-equal (get (. element attrs) "view_type") "form")
|
||||
(o-assert-equal (get (. element attrs) "id") "sample_model_view_action")
|
||||
(o-assert-equal (get (. element attrs) "res_model") "sample.model")
|
||||
(o-assert-equal (get (. element attrs) "name") "Sample Model Action"))
|
||||
|
||||
(defn test-menunitem-model [self]
|
||||
" Test menuitem function "
|
||||
(setv element (od.menuitem-model "sample.model" {"groups" "base.user_employee"}))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element tag) "menuitem")
|
||||
(o-assert-equal (get (. element attrs) "groups") "base.user_employee")
|
||||
(o-assert-equal (get (. element attrs) "id") "sample_model_menu")
|
||||
(o-assert-equal (get (. element attrs) "action") "sample_model_view_action"))
|
||||
|
||||
(defn test-attribute [self]
|
||||
" Test attribute function "
|
||||
(setv element (od.attribute "invisible" "1"))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element tag) "attribute")
|
||||
(o-assert-equal (get (. element attrs) "name") "invisible")
|
||||
(o-assert-equal (. element children) ["1"]))
|
||||
|
||||
(defn test-fields [self]
|
||||
" Test fields function "
|
||||
(setv element (od.field {"one" "attribute"} "A child"))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal element.tag "field")
|
||||
|
||||
(setv element (od.field-name "A name"))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element tag) "field")
|
||||
(o-assert-equal (get (. element attrs) "name") "name")
|
||||
(o-assert-equal (. element children) ["A name"])
|
||||
|
||||
(setv element (od.field-model "sample.model"))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element tag) "field")
|
||||
(o-assert-equal (get (. element attrs) "name") "model")
|
||||
(o-assert-equal (. element children) ["sample.model"])
|
||||
|
||||
(setv element (od.field-inherit "module.xml_view"))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element tag) "field")
|
||||
(o-assert-equal (get (. element attrs) "name") "inherit_id")
|
||||
(o-assert-equal (get (. element attrs) "ref") "module.xml_view")
|
||||
(o-assert-false (. element children))
|
||||
|
||||
(setv element (od.field-arch))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element tag) "field")
|
||||
(o-assert-equal (get (. element attrs) "name") "arch")
|
||||
(o-assert-equal (get (. element attrs) "type") "xml")
|
||||
(o-assert-false (. element children)))
|
||||
|
||||
(defn test-view [self]
|
||||
" Test view function "
|
||||
(setv element (od.view "view_xmlid" []))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element tag) "record")
|
||||
(o-assert-equal (get (. element attrs) "id") "view_xmlid")
|
||||
(o-assert-equal (get (. element attrs) "model") "ir.ui.view")
|
||||
(o-assert-false (. element children)))
|
||||
|
||||
(defn test-view-def [self]
|
||||
" Test view def function "
|
||||
(setv element (od.view_def "view_xmlid" "View" "sample.model" []))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element tag) "record")
|
||||
(o-assert-equal (get (. element attrs) "id") "view_xmlid")
|
||||
(o-assert-equal (get (. element attrs) "model") "ir.ui.view")
|
||||
(o-assert-equal (len (. element children)) 3)
|
||||
|
||||
(setv child (get (. element children) 0))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "name")
|
||||
(o-assert-equal (. child children) ["View"])
|
||||
|
||||
(setv child (get (. element children) 1))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "model")
|
||||
(o-assert-equal (. child children) ["sample.model"])
|
||||
|
||||
(setv child (get (. element children) 2))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "arch")
|
||||
(o-assert-equal (get (. child attrs) "type") "xml")
|
||||
(o-assert-false (. child children)))
|
||||
|
||||
(defn test-view-new [self]
|
||||
" Test view-new function "
|
||||
(setv element (od.view-new "tree" "sample.model" []))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element.tag) "record")
|
||||
(o-assert-equal (get (. element attrs) "id") "sample_model_view_tree")
|
||||
(o-assert-equal (get (. element attrs) "model") "ir.ui.view")
|
||||
(o-assert-equal (len (. element children)) 3)
|
||||
|
||||
(setv child (get (. element children) 0))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "name")
|
||||
(o-assert-equal (. child children) ["Sample Model Tree"])
|
||||
|
||||
(setv child (get (. element children) 1))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "model")
|
||||
(o-assert-equal (. child children) ["sample.model"])
|
||||
|
||||
(setv child (get (. element children) 2))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "arch")
|
||||
(o-assert-equal (get (. child attrs) "type") "xml")
|
||||
(o-assert-false (. child children)))
|
||||
|
||||
|
||||
(defn test_view_inherit [self]
|
||||
" Test view_inherit function "
|
||||
(setv element (od.view-inherit "odoo.addons.module" "sample.model" "parent.view" []))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element tag) "record")
|
||||
(o-assert-equal (get (. element attrs) "id") "view_inherit_module")
|
||||
(o-assert-equal (get (. element attrs) "model") "ir.ui.view")
|
||||
(o-assert-equal (len (. element children)) 4)
|
||||
|
||||
(setv child (get (. element children) 0))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "name")
|
||||
(o-assert-equal (. child children) ["Sample Model Adaptations"])
|
||||
|
||||
(setv child (get (. element children) 1))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "model")
|
||||
(o-assert-equal (. child children) ["sample.model"])
|
||||
|
||||
(setv child (get (. element children) 2))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "inherit_id")
|
||||
(o-assert-false (. child children))
|
||||
|
||||
(setv child (get (. element children) 3))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "arch")
|
||||
(o-assert-equal (get (. child attrs) "type") "xml")
|
||||
(o-assert-false (. child children)))
|
||||
|
||||
(defn test-actions-server-code [self]
|
||||
" Test actions-server-code function "
|
||||
(setv element (od.actions-server-code "sample.xmlid" "Code" "sample.model"
|
||||
"record.do_something()"))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element tag) "record")
|
||||
(o-assert-equal (get (. element attrs) "id") "sample.xmlid")
|
||||
(o-assert-equal (get (. element attrs) "model") "ir.actions.server")
|
||||
(o-assert-equal (len (. element children)) 4)
|
||||
|
||||
(setv child (get (. element children) 0))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "name")
|
||||
(o-assert-equal (. child children) ["Code"])
|
||||
|
||||
(setv child (get (. element children) 1))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "model_id")
|
||||
(o-assert-equal (get (. child attrs) "ref") "sample.model")
|
||||
(o-assert-false (. child children))
|
||||
|
||||
(setv child (get (. element children) 2))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "state")
|
||||
(o-assert-equal (. child children) ["code"])
|
||||
|
||||
(setv child (get (. element children) 3))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "code")
|
||||
(o-assert-equal (. child children) ["record.do_something()"]))
|
||||
|
||||
(defn test-client-action-multi [self]
|
||||
" Test client_action_multi function "
|
||||
(setv element (od.client-action-multi "sample.xmlid" "Multi" "sample.model" "sample.action"))
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
(o-assert-equal (. element tag) "record")
|
||||
(o-assert-equal (get (. element attrs) "id") "sample.xmlid")
|
||||
(o-assert-equal (get (. element attrs) "model") "ir.values")
|
||||
(o-assert-equal (len (. element children)) 4)
|
||||
|
||||
(setv child (get (. element children) 0))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "name")
|
||||
(o-assert-equal (. child children) ["Multi"])
|
||||
|
||||
(setv child (get (. element children) 1))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "key2")
|
||||
(o-assert-equal (get (. child attrs) "eval") "'client_action_multi'")
|
||||
(o-assert-false (. child children))
|
||||
|
||||
(setv child (get (. element children) 2))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "model")
|
||||
(o-assert-equal (get (. child attrs) "eval") "'sample.model'")
|
||||
|
||||
(setv child (get (. element children) 3))
|
||||
(o-assert-is-instance child XMLDictElement)
|
||||
(o-assert-equal (. child tag) "field")
|
||||
(o-assert-equal (get (. child attrs) "name") "value")
|
||||
(o-assert-equal (get (. child attrs) "eval") "'ir.actions.server,%d'%sample.action")))
|
||||
|
||||
|
||||
(defmain [] (.main unittest))
|
|
@ -0,0 +1,120 @@
|
|||
;; Copyright 2019-2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
;;
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
" XML Helpers tests "
|
||||
|
||||
(require
|
||||
hyrule.control [defmain]
|
||||
odoo.addons.hy-odoo.mtest *)
|
||||
|
||||
(import unittest
|
||||
functools [partial]
|
||||
xml.etree.ElementTree :as ET
|
||||
os [unlink]
|
||||
odoo.tests [tagged]
|
||||
odoo.tests.common [TransactionCase]
|
||||
odoo.addons.hy-odoo.xml [XMLDictElement xmln xmlroot xmlchild xml-write])
|
||||
|
||||
(defclass [(tagged "hyodoo" "hyodoo.xml")] TextXMLBase [TransactionCase]
|
||||
"XML Helpers tests"
|
||||
|
||||
(defn test-xmln [self]
|
||||
"Text xmln"
|
||||
; XMLDictElement
|
||||
(o-assert-is-instance (xmln) XMLDictElement)
|
||||
; Tags
|
||||
(o-assert-dict-equal (._asdict (xmln)) {"tag" "" "attrs" {} "children" []})
|
||||
(o-assert-equal (. (xmln "a tag") tag) "a tag")
|
||||
; Attrs
|
||||
(o-assert-dict-equal (. (xmln :attrs {"a good" "one"}) attrs) {"a good" "one"})
|
||||
; Childrens
|
||||
(o-assert-list-equal (. (xmln :children [1 2 3]) children) [1 2 3])
|
||||
(o-assert-list-equal (. (xmln :children "Some text") children) ["Some text"])
|
||||
|
||||
(with [(o-assert-raises-regex TypeError "Invalid arguments")] (xmln :children False))
|
||||
|
||||
; Ensure that only children after tags is managed
|
||||
(setv element (xmln "tag" {"something" "inside"}))
|
||||
(o-assert-is-instance (. element attrs) dict)
|
||||
(o-assert-is-instance (. element children) list)
|
||||
(o-assert-is-instance element XMLDictElement)
|
||||
|
||||
(setv element (xmln "tag" ["something" "inside"]))
|
||||
(o-assert-is-instance (. element attrs) dict)
|
||||
(o-assert-is-instance (. element children) list)
|
||||
(o-assert-is-instance element XMLDictElement))
|
||||
|
||||
(defn test-xmlchild [self]
|
||||
"Test xmlchild"
|
||||
(setv parent (xmlroot {"tag" "root" "attrs" {} "children" []})
|
||||
xmlc-par (partial xmlchild parent))
|
||||
|
||||
; Bad arguments
|
||||
(with [(o-assert-raises-regex TypeError "Invalid arguments for xmlchild")] (xmlc-par False))
|
||||
; Need XMLDictElement, not dict
|
||||
(with [(o-assert-raises-regex TypeError "Invalid arguments for xmlchild")]
|
||||
(xmlc-par [{"tag" "t" "attrs" {"a" "b"} "children" []}]))
|
||||
|
||||
(xmlc-par ["some text"])
|
||||
(o-assert-equal (. parent text) "some text")
|
||||
|
||||
(xmlc-par[(xmln "t" {"a" "b"} [])])
|
||||
(setv child (next (.iter parent "t")))
|
||||
(o-assert-equal (. child tag) "t")
|
||||
(o-assert-dict-equal (. child attrib) {"a" "b"})
|
||||
(o-assert-list-equal (list child) [])
|
||||
|
||||
(xmlc-par [(xmln "t2" {1 2} [])])
|
||||
(setv child (next (.iter parent "t2")))
|
||||
(o-assert-dict-equal (. child attrib) {"1" "2"})
|
||||
|
||||
(xmlc-par [(xmln "tchildren" {} [(xmln "subchild" {} [])])])
|
||||
(setv child (next (.iter parent "tchildren"))
|
||||
subchildren (list child))
|
||||
(o-assert-equal (len subchildren) 1)
|
||||
(o-assert-equal (. (get subchildren 0) tag) "subchild"))
|
||||
|
||||
(defn test-xmlroot [self]
|
||||
"Test xmlroot"
|
||||
|
||||
(setv root (xmlroot {"tag" "root" "attrs" {} "children" []}))
|
||||
(o-assert-is-instance root (. ET Element))
|
||||
|
||||
(with [(o-assert-raises-regex TypeError "is not subscriptable")] (xmlroot False))
|
||||
(with [(o-assert-raises-regex KeyError "tag")] (xmlroot {}))
|
||||
(with [(o-assert-raises-regex KeyError "attrs")] (xmlroot {"tag" "root"})))
|
||||
|
||||
(defn test-xml-write [self]
|
||||
"Test xml-write"
|
||||
|
||||
(setv children [(xmln "child1" {"attr" "value"} [])
|
||||
(xmln "child2" {} "Some text")]
|
||||
tree (xmlroot {"tag" "root" "attrs" {} "children" children})
|
||||
xmlw (fn [p] (xml_write p tree)))
|
||||
(o-assert-is-none (xmlw "/badpath"))
|
||||
(o-assert-is-none (xmlw "/bad.ext"))
|
||||
|
||||
(xmlw __file__)
|
||||
(setv filepath (.replace __file__ ".hy" "_views.xml"))
|
||||
(with [output-file (open filepath "r")]
|
||||
(setv output-xml (.read output-file))
|
||||
(o-assert-in "<?xml version" output-xml)
|
||||
(o-assert-in "<root>" output-xml)
|
||||
(o-assert-in "<child1 attr=\"value\"/>" output-xml)
|
||||
(o-assert-in "<child2>Some text</child2>" output-xml)
|
||||
(unlink filepath))
|
||||
|
||||
(xml-write __file__ tree :pretty True :suffix "_data")
|
||||
(setv filepath (.replace __file__ ".hy" "_data.xml"))
|
||||
(with [output-file (open filepath "r")]
|
||||
(setv output-xml (.read output-file))
|
||||
(o-assert-in "<?xml version" output-xml)
|
||||
(o-assert-in "<root>" output-xml)
|
||||
(o-assert-in "<child1 attr=\"value\"/>" output-xml)
|
||||
(o-assert-in "<child2>Some text</child2>" output-xml)
|
||||
(unlink filepath))))
|
||||
|
||||
(defmain [] (.main unittest))
|
|
@ -0,0 +1,24 @@
|
|||
;; Copyright 2021-2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
;;
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
" Hy General Utils "
|
||||
|
||||
(require hyrule.collections [assoc])
|
||||
|
||||
(defn pick [keys record]
|
||||
(reduce
|
||||
(fn [acc key]
|
||||
(do
|
||||
(assoc acc key (get record key))
|
||||
(return acc)))
|
||||
keys
|
||||
{}))
|
||||
|
||||
(defn pick-values [keys record]
|
||||
(list
|
||||
(map
|
||||
(fn [key] (get record key))
|
||||
keys)))
|
|
@ -0,0 +1,64 @@
|
|||
;; Copyright 2019-2022 Fabien Bourgeois <fabien@yaltik.com>
|
||||
;;
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
" XML helpers and macros "
|
||||
|
||||
(require odoo.addons.hy-odoo.mgeneral [instance?]
|
||||
odoo.addons.hy-odoo.modoo [pdb])
|
||||
(import collections [namedtuple]
|
||||
functools [partial]
|
||||
os [path]
|
||||
xml.dom [minidom]
|
||||
xml.etree.ElementTree :as ET)
|
||||
|
||||
;; Types
|
||||
|
||||
(setv XMLDictElement (namedtuple "XMLDictElement" ["tag" "attrs" "children"]))
|
||||
|
||||
;; Helpers
|
||||
|
||||
(defn xmlroot [tree]
|
||||
"Special process for root XML Node"
|
||||
(setv rootel (.Element ET (get tree "tag") (get tree "attrs")))
|
||||
(when (in "children" tree)
|
||||
(xmlchild rootel (get tree "children")))
|
||||
(return rootel))
|
||||
|
||||
(defn xmlchild [parent children]
|
||||
"Handling of children (ie non root) XML Nodes with/o text and subchildren
|
||||
(recursive)"
|
||||
(cond (instance? str children) (setv (. parent text) children)
|
||||
(instance? XMLDictElement children)
|
||||
(do
|
||||
(setv attrs (dfor [k v] (.items (. children attrs)) (str k) (str v)))
|
||||
(setv new-parent (.SubElement ET parent (. children tag) attrs)
|
||||
subchildren (. children children))
|
||||
(when subchildren) (xmlchild new-parent subchildren))
|
||||
(instance? list children) (list( map (partial xmlchild parent) children))
|
||||
True (raise (TypeError "Invalid arguments for xmlchild"))))
|
||||
|
||||
(defn xmln [[tag ""] [attrs {}] [children []]]
|
||||
"XMLDictElement building from dict object, with defaults"
|
||||
(when (instance? list attrs) (setv children attrs attrs {}))
|
||||
(setv xmldictel (partial XMLDictElement tag attrs)
|
||||
inst-str? (instance? str children))
|
||||
(when inst-str? (return (xmldictel [children])))
|
||||
(when (instance? list children) (return (xmldictel children)))
|
||||
(raise (TypeError "Invalid arguments for xmln")))
|
||||
|
||||
(defn xml-write [filepath tree [pretty True] [suffix "_views"]]
|
||||
"Write XML file according to filepath and given tree"
|
||||
(when (.endswith filepath ".hy")
|
||||
(setv output-xml (.decode (.tostring ET
|
||||
tree
|
||||
:xml_declaration True
|
||||
:encoding "utf-8") "utf-8")
|
||||
output-path (.split (.abspath path filepath) "/")
|
||||
(get output-path -1) (.replace (get output-path -1) ".hy" (% "%s.xml" suffix))
|
||||
output-path (.join "/" output-path))
|
||||
(when pretty
|
||||
(setv output-xml (.toprettyxml (.parseString minidom output-xml) :indent " ")))
|
||||
(with [output-file (open output-path "w")] (.write output-file output-xml))))
|
|
@ -24,7 +24,7 @@
|
|||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': ['note'],
|
||||
'data': ['views/note_view.xml']
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': ['note_base'],
|
||||
'data': ['views/note_dates.xml']
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': ['note_base'],
|
||||
'data': ['views/note_priority.xml']
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
'category': 'Tools',
|
||||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': ['note'],
|
||||
'data': ['note_tags_view.xml']
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
'category': 'Tools',
|
||||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': ['note_base', 'project'],
|
||||
'data': ['note_task_view.xml']
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<t t-esc="o.name">
|
||||
</t>
|
||||
""",
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'application': True,
|
||||
'data':[
|
||||
'view/templates.xml',
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
{
|
||||
'name': 'Email widget check syntax',
|
||||
'summary': 'Extends Web Email widget to ensure valid syntax',
|
||||
'version': '10.0.1.0.1',
|
||||
'version': '12.0.1.0.1',
|
||||
'category': 'Usability',
|
||||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': [ 'web' ],
|
||||
'data': [ 'view.xml' ]
|
||||
}
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
{
|
||||
'name': 'French format phone widget check',
|
||||
'summary': 'Extends Web Char widget to handle french format phone',
|
||||
'version': '10.0.1.0.2',
|
||||
'version': '12.0.1.0.2',
|
||||
'category': 'Usability',
|
||||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': [ 'web' ],
|
||||
'data': [ 'view.xml' ],
|
||||
'qweb': ['static/src/xml/qweb.xml']
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
{
|
||||
'name': 'URL widget check syntax',
|
||||
'summary': 'Extends Web URL widget to ensure valid syntax',
|
||||
'version': '10.0.1.0.1',
|
||||
'version': '12.0.1.0.1',
|
||||
'category': 'Usability',
|
||||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': [ 'web' ],
|
||||
'data': [ 'view.xml' ]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2019 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from . import odoo
|
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2019 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
{
|
||||
'name': 'Odoo XML DSL base module and fns',
|
||||
'summary': 'Odoo XML Domain Specific Language base module and functions',
|
||||
'description': """ Odoo XML Domain Specific Language base module and functions """,
|
||||
'version': '12.0.0.1.0',
|
||||
'category': 'Yaltik',
|
||||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'depends': ['base']
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2019 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" XML helpers and macros """
|
||||
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
def xmlroot(tree):
|
||||
""" Special process for root XML Node """
|
||||
rootel = ET.Element(tree['tag'], tree['attrs'])
|
||||
children = tree['children']
|
||||
if children:
|
||||
xmlchild(rootel, children)
|
||||
return rootel
|
||||
|
||||
def xmlchild(parent, children):
|
||||
""" Handling of children (ie non root) XML Nodes with/o text and
|
||||
subchildren (recursive) """
|
||||
for child in children:
|
||||
if isinstance(child, str):
|
||||
parent.text = child
|
||||
else:
|
||||
attrs = {unicode(k): unicode(v) for k, v in child['attrs'].items()}
|
||||
new_parent = ET.SubElement(parent, child['tag'], attrs)
|
||||
subchildren = child['children']
|
||||
if subchildren:
|
||||
xmlchild(new_parent, subchildren)
|
||||
|
||||
def xmln(tag='', attrs=None, children=None, text=False):
|
||||
""" XMLNode with default children, not attributes """
|
||||
children = ([text] if text else children) or []
|
||||
return {'tag': tag, 'attrs': attrs or {}, 'children': children}
|
|
@ -0,0 +1,81 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2019 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" Odoo XML DSL """
|
||||
|
||||
from . import base as x
|
||||
|
||||
def odoo(*args):
|
||||
""" odoo tag shortcut """
|
||||
return x.xmlroot(x.xmln('odoo', {}, *args))
|
||||
def data(*args):
|
||||
""" data tag shortcut """
|
||||
return x.xmln('data', {}, *args)
|
||||
def record(*args):
|
||||
""" record tag shortcut """
|
||||
return x.xmln('record', *args)
|
||||
|
||||
def view(xmlid, children):
|
||||
""" view tag shortcut """
|
||||
return record({'id': xmlid, 'model': 'ir.ui.view'}, children)
|
||||
|
||||
def view_def(xmlid, name, model, arch):
|
||||
""" View and first fields simplification with record xmlid, name, targeted model """
|
||||
return view(xmlid, [field_name(name), field_model(model), field_arch(arch)])
|
||||
|
||||
def view_inherit(name, model, inherit, arch):
|
||||
""" Inherited View simplification with name of the record, xmlid for model
|
||||
and inherited view """
|
||||
module = __name__.split('.')[2]
|
||||
inherited = inherit.split('.')[1]
|
||||
xmlid = '%s_inherit_%s' % (inherited, module)
|
||||
return view(xmlid, [field_name(name), field_model(model),
|
||||
field_inherit(inherit), field_arch(arch)])
|
||||
|
||||
def actions_server_code(xmlid, name, modelref, code):
|
||||
""" Server actions of type code """
|
||||
return record({'id': xmlid, 'model': 'ir.actions.server'},
|
||||
[field_name(name),
|
||||
field({'name': 'model_id', 'ref': modelref}),
|
||||
field({'name': 'state'}, ['code']),
|
||||
field({'name': 'code'}, [code])])
|
||||
|
||||
def field(*args):
|
||||
""" field tag shortcut """
|
||||
return x.xmln('field', *args)
|
||||
def field_name(name):
|
||||
""" field name tag shortcut """
|
||||
return field({'name': 'name'}, [name])
|
||||
def field_model(model):
|
||||
""" field model tag shortcut """
|
||||
return field({'name': 'model'}, [model])
|
||||
def field_inherit(xmlid):
|
||||
""" field inherit tag shortcut """
|
||||
return field({'name': 'inherit_id', 'ref': xmlid})
|
||||
def field_arch(*args):
|
||||
""" field arch tag shortcut """
|
||||
return field({'name': 'arch', 'type': 'xml'}, *args)
|
||||
|
||||
def xml_write(mpath, filename, tree):
|
||||
""" Write XML file according to filename and given tree """
|
||||
import os.path
|
||||
from xml.etree import ElementTree as ET
|
||||
output_xml = ET.tostring(tree).decode('utf-8')
|
||||
output_path = os.path.dirname(os.path.abspath(mpath))
|
||||
fpath = u'%s/%s' % (output_path, filename)
|
||||
with open(fpath, 'w') as xml_file:
|
||||
xml_file.write(output_xml)
|
|
@ -25,7 +25,7 @@
|
|||
'description': """ Yaltik Backend Green theme, based upon great
|
||||
DodgerBlue Backend Theme from OpenWorx :
|
||||
https://github.com/Openworx/themes/tree/8.0/dodgerblue_backend_theme """,
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': ['base'],
|
||||
'data': ['views.xml']
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2018 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2018-2023 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
{
|
||||
'name': 'Yaltik bootstrap',
|
||||
'summary': 'Yaltik bootstrap : common dependencies for all projects',
|
||||
'description': """ Yaltik bootstrap : common dependencies for all projects :
|
||||
* addons from OCA : FR localization, debranding ;
|
||||
* new backend and responsive theme """,
|
||||
'version': '16.0.0.1.1',
|
||||
'category': 'Yaltik',
|
||||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'depends': ['l10n_fr', 'l10n_fr_siret', 'l10n_fr_state', 'l10n_fr_department',
|
||||
'web_theme_classic', 'web_responsive']
|
||||
}
|
|
@ -18,11 +18,11 @@
|
|||
{
|
||||
'name': 'CRM Actions',
|
||||
'summary': 'Action management, instead of new activity, in CRM',
|
||||
'version': '10.0.1.3.5',
|
||||
'version': '12.0.1.3.5',
|
||||
'category': 'Sales',
|
||||
'author': 'Fabien BOURGEOIS - Yaltik',
|
||||
'license': 'AGPL-3',
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'application': True,
|
||||
'depends': ['crm', 'calendar'],
|
||||
'data': ['views/crm_action_views.xml',
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
.PHONY: setup
|
||||
setup:
|
||||
pip install --user -U coconut[watch,jobs,backports,mypy]
|
||||
|
||||
.PHONY: testdev
|
||||
testdev: clean build test
|
||||
|
||||
.PHONY: testprod
|
||||
testprod: clean prod test
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
find . -name '*.pyc' -delete
|
||||
find . -name '__pycache__' -delete
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
python3 tests/test_xml_base.py
|
||||
python3 tests/test_odoo.py
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
coconut -t sys -j sys .
|
||||
|
||||
.PHONY: prod
|
||||
prod:
|
||||
coconut --notco -t 3.9 -j sys -f .
|
||||
|
||||
.PHONY: watch
|
||||
watch:
|
||||
coconut --strict -t sys -j sys -w .
|
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2019-2020 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from . import src
|
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2019-2024 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
{
|
||||
'name': 'Yaltik Odoo DSL base module and fns',
|
||||
'summary': 'Yaltik Odoo Domain Specific Language base module and functions',
|
||||
'description': """ Yaltik Odoo Domain Specific Language base module and functions """,
|
||||
'version': '16.0.0.5.13',
|
||||
'category': 'Yaltik',
|
||||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'depends': ['base']
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
|||
# Copyright 2020 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from . import odoo_dsl
|
|
@ -0,0 +1,167 @@
|
|||
# Copyright 2019-2024 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" Odoo XML DSL """
|
||||
|
||||
from .xml_base import xmlroot, xmln
|
||||
|
||||
# XML helpers functions and macros
|
||||
|
||||
# Odoo root XML Node
|
||||
odoo = lambda children: xmlroot({'tag': 'odoo', 'attrs': {}, 'children': children})
|
||||
|
||||
# Special data Node
|
||||
def data(*args):
|
||||
""" Allow optional args on data tag """
|
||||
if len(args) == 1:
|
||||
args = list(args)
|
||||
args.insert(0, {})
|
||||
return xmln('data', *args)
|
||||
|
||||
# Aliases
|
||||
function = lambda *args: xmln('function', *args)
|
||||
record = lambda *args: xmln('record', *args)
|
||||
delete = lambda *args: xmln('delete', *args)
|
||||
form = lambda *args: xmln('form', *args)
|
||||
tree = lambda *args: xmln('tree', *args)
|
||||
search = lambda *args: xmln('search', *args)
|
||||
template = lambda *args: xmln('template', *args)
|
||||
|
||||
# Actions
|
||||
def act_window_model(model, attrs):
|
||||
""" Build new act_window from model and args """
|
||||
xmlid = '%s_view_action' % (model.replace('.', '_'))
|
||||
name = '%s Action' % ' '.join(map(lambda w: w.capitalize(), model.split('.')))
|
||||
attrs_clone = attrs.copy() # Avoid side-effect
|
||||
attrs_clone.update(
|
||||
{'name': name, 'res_model': model, 'type': 'ir.actions.act_window'}
|
||||
)
|
||||
children = []
|
||||
for key, val in attrs_clone.items():
|
||||
if key.endswith('_id'):
|
||||
children.append(field({'name': key, 'ref': val}))
|
||||
elif key == 'sequence':
|
||||
children.append(field({'name': key, 'eval': val}))
|
||||
else:
|
||||
children.append(field({'name': key}, [val]))
|
||||
return record({'id': xmlid, 'model': 'ir.actions.act_window'}, children)
|
||||
|
||||
def action_server_code(xmlid, name, modelref, code):
|
||||
""" Server actions of type code """
|
||||
children = [field_name(name),
|
||||
field({'name': 'model_id', 'ref': modelref}, []),
|
||||
field({'name': 'state'}, ['code']),
|
||||
field({'name': 'code'}, [code])]
|
||||
return record({'id': xmlid, 'model': 'ir.actions.server'}, children)
|
||||
|
||||
def client_action_multi(xmlid, name, model, action):
|
||||
""" Client action multi (ir.values), with own xmlid, name, targeted model
|
||||
and action """
|
||||
action = "'ir.actions.server,%d'%{}".format(action)
|
||||
children = [field_name(name),
|
||||
field({'name': 'key2', 'eval': "'client_action_multi'"}),
|
||||
field({'name': 'model', 'eval': "'%s'" % model}),
|
||||
field({'name': 'value', 'eval': action})]
|
||||
return record({'id': xmlid, 'model': 'ir.values'}, children)
|
||||
|
||||
# Menus
|
||||
menuitem = lambda *args: xmln('menuitem', *args)
|
||||
def menuitem_model(model, attrs):
|
||||
""" Build new menuitem from model and attrs """
|
||||
model_und = model.replace('.', '_')
|
||||
xmlid = '%s_menu' % model_und
|
||||
actionid = '%s_view_action' % model_und
|
||||
attrs_clone = attrs.copy() # Avoid side-effect
|
||||
attrs_clone.update({'id': xmlid, 'action': actionid})
|
||||
return menuitem(attrs_clone)
|
||||
|
||||
# Form aliases
|
||||
group = lambda *args: xmln('group', *args)
|
||||
header = lambda *args: xmln('header', *args)
|
||||
footer = lambda *args: xmln('footer', *args)
|
||||
sheet = lambda *args: xmln('sheet', *args)
|
||||
button = lambda *args: xmln('button', *args)
|
||||
p = lambda *args: xmln('p', *args)
|
||||
i = lambda *args: xmln('i', *args)
|
||||
t = lambda *args: xmln('t', *args)
|
||||
div = lambda *args: xmln('div', *args)
|
||||
span = lambda *args: xmln('span', *args)
|
||||
br = lambda *args: xmln('br', *args)
|
||||
h1 = lambda *args: xmln('h1', *args)
|
||||
h2 = lambda *args: xmln('h1', *args)
|
||||
h3 = lambda *args: xmln('h1', *args)
|
||||
h4 = lambda *args: xmln('h1', *args)
|
||||
h5 = lambda *args: xmln('h1', *args)
|
||||
ul = lambda *args: xmln('ul', *args)
|
||||
li = lambda *args: xmln('li', *args)
|
||||
label = lambda *args: xmln('label', *args)
|
||||
strong = lambda *args: xmln('strong', *args)
|
||||
small = lambda *args: xmln('small', *args)
|
||||
table = lambda *args: xmln('table', *args)
|
||||
tr = lambda *args: xmln('tr', *args)
|
||||
td = lambda *args: xmln('td', *args)
|
||||
notebook = lambda *args: xmln('notebook', *args)
|
||||
page = lambda *args: xmln('page', *args)
|
||||
xpath = lambda *args: xmln('xpath', *args)
|
||||
attribute = lambda name, value: xmln('attribute', {'name': name}, [value])
|
||||
|
||||
# Fields
|
||||
field = lambda *args: xmln('field', *args)
|
||||
field_name = lambda name: field({'name': 'name'}, [name])
|
||||
field_nval = lambda name, value: field({'name': name}, [value])
|
||||
field_model = lambda model: field({'name': 'model'}, [model])
|
||||
field_inherit = lambda xmlid: field({'name': 'inherit_id', 'ref': xmlid}, [])
|
||||
field_arch = lambda *args: field({'name': 'arch', 'type': 'xml'}, *args)
|
||||
|
||||
# Search
|
||||
filter = lambda *args: xmln('filter', *args)
|
||||
separator = lambda *args: xmln('separator', *args)
|
||||
|
||||
def filter_yes_no(field, str_yes=False, str_no=False):
|
||||
""" Double filter for boolean : True and False """
|
||||
res = []
|
||||
if str_yes:
|
||||
res.append(filter({'name': f'{field}_yes', 'string': str_yes,
|
||||
'domain': f"[('{field}', '=', True)]"}))
|
||||
if str_no:
|
||||
res.append(filter({'name': f'{field}_no', 'string': str_no,
|
||||
'domain': f"[('{field}', '=', False)]"}))
|
||||
return res
|
||||
|
||||
# Views
|
||||
view = lambda xmlid, children: record({'id': xmlid, 'model': 'ir.ui.view'}, children)
|
||||
|
||||
def view_def(xmlid, name, model, arch):
|
||||
""" Shortcut for new view """
|
||||
return view(xmlid, [field_name(name), field_model(model), field_arch(arch)])
|
||||
|
||||
def view_new(view_type, model, arch):
|
||||
""" View : new view definition, based on type (form, tree, ...) and model ID """
|
||||
model_und = model.replace('.', '_')
|
||||
model_cap = ' '.join(map(lambda w: w.capitalize(), model.split('.')))
|
||||
xmlid = "%s_view_%s" % (model_und, view_type)
|
||||
name = ' '.join([model_cap, view_type.capitalize()])
|
||||
return view_def(xmlid, name, model, arch)
|
||||
|
||||
def view_inherit(filename, model, inherit, arch):
|
||||
""" Inherited View simplification with name of the record, xmlid for model
|
||||
and inherited view """
|
||||
module = filename.split('.')[2]
|
||||
inherited = inherit.split('.')[1]
|
||||
xmlid = '%s_inherit_%s' % (inherited, module)
|
||||
model_cap = ' '.join(map(lambda w: w.capitalize(), model.split('.')))
|
||||
name = '%s Adaptations' % model_cap
|
||||
return view(xmlid, [field_name(name), field_model(model),
|
||||
field_inherit(inherit), field_arch(arch)])
|
|
@ -0,0 +1,73 @@
|
|||
# Copyright 2019-2020 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" XML helpers and macros """
|
||||
|
||||
from os import path
|
||||
import xml.etree.ElementTree as ET
|
||||
from xml.dom import minidom
|
||||
from collections import namedtuple
|
||||
from functools import partial
|
||||
|
||||
XMLDictElement = namedtuple('XMLDictElement', ['tag', 'attrs', 'children'])
|
||||
|
||||
|
||||
def xmlroot(tree):
|
||||
""" Special process for root XML Node """
|
||||
rootel = ET.Element(tree['tag'], tree['attrs'])
|
||||
if 'children' in tree:
|
||||
xmlchild(rootel, tree['children'])
|
||||
return rootel
|
||||
|
||||
def xmlchild(parent, children):
|
||||
""" Handling of children (ie non root) XML Nodes with/o text and
|
||||
subchildren (recursive) """
|
||||
if isinstance(children, str):
|
||||
parent.text = children
|
||||
elif isinstance(children, XMLDictElement):
|
||||
attrs = {str(k): str(v) for [k, v] in children.attrs.items()}
|
||||
new_parent = ET.SubElement(parent, children.tag, attrs)
|
||||
subchildren = children.children
|
||||
if subchildren:
|
||||
xmlchild(new_parent, subchildren)
|
||||
elif isinstance(children, list):
|
||||
list(map(partial(xmlchild, parent), children))
|
||||
else:
|
||||
raise TypeError('Invalid arguments for xmlchild')
|
||||
|
||||
def xmln(tag='', attrs={}, children=[]):
|
||||
""" XMLDictElement building from dict object, with defaults """
|
||||
if isinstance(attrs, list):
|
||||
children = attrs
|
||||
attrs = {}
|
||||
xmldictel = partial(XMLDictElement, tag, attrs)
|
||||
if isinstance(children, str):
|
||||
return xmldictel([children,])
|
||||
if isinstance(children, list):
|
||||
return xmldictel(children)
|
||||
raise TypeError('Invalid arguments for xmln')
|
||||
|
||||
|
||||
def xml_write(filepath, tree, pretty=True, suffix='_views'):
|
||||
""" Write XML file according to filename and given tree """
|
||||
if filepath.endswith('.py'): # if .pyc, no need to generate XML
|
||||
output_xml = ET.tostring(tree)
|
||||
if pretty:
|
||||
output_xml = minidom.parseString(output_xml).toprettyxml(indent=' ')
|
||||
output_path = path.abspath(filepath).split('/')
|
||||
output_path[-1] = output_path[-1].replace('.py', '%s.xml' % suffix)
|
||||
output_path = '/'.join(output_path)
|
||||
with open(output_path, 'w') as output_file:
|
||||
output_file.write(output_xml)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,271 @@
|
|||
# Copyright 2020 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" Odoo Helpers tests """
|
||||
|
||||
import unittest
|
||||
import xml.etree.ElementTree as ET
|
||||
from src.xml_base import XMLDictElement
|
||||
import src.odoo_dsl as od
|
||||
|
||||
class TestOdooBase(unittest.TestCase):
|
||||
""" Odoo Helpers tests """
|
||||
|
||||
def test_odoo(self):
|
||||
""" Test odoo function """
|
||||
element = od.odoo([])
|
||||
self.assertIsInstance(element, ET.Element)
|
||||
self.assertEqual(element.tag, 'odoo')
|
||||
|
||||
def test_data(self):
|
||||
""" Test data function """
|
||||
element = od.data([])
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'data')
|
||||
self.assertEqual(element.attrs, {})
|
||||
|
||||
element = od.data({"one": "attribute"}, [])
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'data')
|
||||
self.assertEqual(element.attrs, {"one": "attribute"})
|
||||
|
||||
def test_aliases(self):
|
||||
""" Test simple aliases to xmln """
|
||||
element = od.record({"one": "attribute"}, 'A child')
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'record')
|
||||
self.assertEqual(element.attrs, {"one": "attribute"})
|
||||
self.assertEqual(element.children, ['A child'])
|
||||
|
||||
element = od.tree()
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'tree')
|
||||
|
||||
def test_act_window_model(self):
|
||||
""" Test act_window function """
|
||||
element = od.act_window_model('sample.model', {'view_type': 'form'})
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'act_window')
|
||||
self.assertEqual(element.attrs['view_type'], 'form')
|
||||
self.assertEqual(element.attrs['id'], 'sample_model_view_action')
|
||||
self.assertEqual(element.attrs['res_model'], 'sample.model')
|
||||
self.assertEqual(element.attrs['name'], 'Sample Model Action')
|
||||
|
||||
def test_menunitem_model(self):
|
||||
""" Test menuitem function """
|
||||
element = od.menuitem_model('sample.model', {'groups': 'base.user_employee'})
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'menuitem')
|
||||
self.assertEqual(element.attrs['groups'], 'base.user_employee')
|
||||
self.assertEqual(element.attrs['id'], 'sample_model_menu')
|
||||
self.assertEqual(element.attrs['action'], 'sample_model_view_action')
|
||||
|
||||
def test_attribute(self):
|
||||
""" Test attribute function """
|
||||
element = od.attribute('invisible', "1")
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'attribute')
|
||||
self.assertEqual(element.attrs['name'], 'invisible')
|
||||
self.assertEqual(element.children, ["1"])
|
||||
|
||||
def test_fields(self):
|
||||
""" Test fields function """
|
||||
element = od.field({"one": "attribute"}, 'A child')
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'field')
|
||||
|
||||
element = od.field_name('A name')
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'field')
|
||||
self.assertEqual(element.attrs['name'], 'name')
|
||||
self.assertEqual(element.children, ['A name'])
|
||||
|
||||
element = od.field_model('sample.model')
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'field')
|
||||
self.assertEqual(element.attrs['name'], 'model')
|
||||
self.assertEqual(element.children, ['sample.model'])
|
||||
|
||||
element = od.field_inherit('module.xml_view')
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'field')
|
||||
self.assertEqual(element.attrs['name'], 'inherit_id')
|
||||
self.assertEqual(element.attrs['ref'], 'module.xml_view')
|
||||
self.assertFalse(element.children)
|
||||
|
||||
element = od.field_arch()
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'field')
|
||||
self.assertEqual(element.attrs['name'], 'arch')
|
||||
self.assertEqual(element.attrs['type'], 'xml')
|
||||
self.assertFalse(element.children)
|
||||
|
||||
def test_filter_yes_no(self):
|
||||
""" Test Filter Yes No """
|
||||
elements = od.filter_yes_no('some_field')
|
||||
self.assertIsInstance(elements, list)
|
||||
self.assertFalse(elements)
|
||||
|
||||
elements = od.filter_yes_no('some_field', 'Some field')
|
||||
self.assertEqual(len(elements), 1)
|
||||
self.assertIsInstance(elements[0], XMLDictElement)
|
||||
self.assertEqual(elements[0].tag, 'filter')
|
||||
self.assertEqual(elements[0].attrs['name'], 'some_field_yes')
|
||||
self.assertEqual(elements[0].attrs['string'], 'Some field')
|
||||
self.assertEqual(elements[0].attrs['domain'], "[('some_field', '=', True)]")
|
||||
|
||||
elements = od.filter_yes_no('some_field', 'Some field', 'Not some field')
|
||||
self.assertEqual(len(elements), 2)
|
||||
self.assertIsInstance(elements[0], XMLDictElement)
|
||||
self.assertEqual(elements[0].tag, 'filter')
|
||||
self.assertEqual(elements[0].attrs['name'], 'some_field_yes')
|
||||
self.assertEqual(elements[0].attrs['string'], 'Some field')
|
||||
self.assertEqual(elements[0].attrs['domain'], "[('some_field', '=', True)]")
|
||||
self.assertIsInstance(elements[1], XMLDictElement)
|
||||
self.assertEqual(elements[1].tag, 'filter')
|
||||
self.assertEqual(elements[1].attrs['name'], 'some_field_no')
|
||||
self.assertEqual(elements[1].attrs['string'], 'Not some field')
|
||||
self.assertEqual(elements[1].attrs['domain'], "[('some_field', '=', False)]")
|
||||
|
||||
def test_view(self):
|
||||
""" Test view function """
|
||||
element = od.view('view_xmlid', [])
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'record')
|
||||
self.assertEqual(element.attrs['id'], 'view_xmlid')
|
||||
self.assertEqual(element.attrs['model'], 'ir.ui.view')
|
||||
self.assertFalse(element.children)
|
||||
|
||||
def test_view_def(self):
|
||||
""" Test view_def function """
|
||||
element = od.view_def('view_xmlid', 'View', 'sample.model', [])
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'record')
|
||||
self.assertEqual(element.attrs['id'], 'view_xmlid')
|
||||
self.assertEqual(element.attrs['model'], 'ir.ui.view')
|
||||
self.assertEqual((len(element.children)), 3)
|
||||
self.assertIsInstance(element.children[0], XMLDictElement)
|
||||
self.assertEqual(element.children[0].tag, 'field')
|
||||
self.assertEqual(element.children[0].attrs['name'], 'name')
|
||||
self.assertEqual(element.children[0].children, ['View'])
|
||||
self.assertIsInstance(element.children[1], XMLDictElement)
|
||||
self.assertEqual(element.children[1].tag, 'field')
|
||||
self.assertEqual(element.children[1].attrs['name'], 'model')
|
||||
self.assertEqual(element.children[1].children, ['sample.model'])
|
||||
self.assertIsInstance(element.children[2], XMLDictElement)
|
||||
self.assertEqual(element.children[2].tag, 'field')
|
||||
self.assertEqual(element.children[2].attrs['name'], 'arch')
|
||||
self.assertEqual(element.children[2].attrs['type'], 'xml')
|
||||
self.assertFalse(element.children[2].children)
|
||||
|
||||
def test_view_new(self):
|
||||
""" Test view_new function """
|
||||
element = od.view_new('tree', 'sample.model', [])
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'record')
|
||||
self.assertEqual(element.attrs['id'], 'sample_model_view_tree')
|
||||
self.assertEqual(element.attrs['model'], 'ir.ui.view')
|
||||
self.assertEqual((len(element.children)), 3)
|
||||
self.assertIsInstance(element.children[0], XMLDictElement)
|
||||
self.assertEqual(element.children[0].tag, 'field')
|
||||
self.assertEqual(element.children[0].attrs['name'], 'name')
|
||||
self.assertEqual(element.children[0].children, ['Sample Model Tree'])
|
||||
self.assertIsInstance(element.children[1], XMLDictElement)
|
||||
self.assertEqual(element.children[1].tag, 'field')
|
||||
self.assertEqual(element.children[1].attrs['name'], 'model')
|
||||
self.assertEqual(element.children[1].children, ['sample.model'])
|
||||
self.assertIsInstance(element.children[2], XMLDictElement)
|
||||
self.assertEqual(element.children[2].tag, 'field')
|
||||
self.assertEqual(element.children[2].attrs['name'], 'arch')
|
||||
self.assertEqual(element.children[2].attrs['type'], 'xml')
|
||||
self.assertFalse(element.children[2].children)
|
||||
|
||||
def test_view_inherit(self):
|
||||
""" Test view_inherit function """
|
||||
element = od.view_inherit('odoo.addons.module', 'sample.model', 'parent.view', [])
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'record')
|
||||
self.assertEqual(element.attrs['id'], 'view_inherit_module')
|
||||
self.assertEqual(element.attrs['model'], 'ir.ui.view')
|
||||
self.assertEqual((len(element.children)), 4)
|
||||
self.assertIsInstance(element.children[0], XMLDictElement)
|
||||
self.assertEqual(element.children[0].tag, 'field')
|
||||
self.assertEqual(element.children[0].attrs['name'], 'name')
|
||||
self.assertEqual(element.children[0].children, ['Sample Model Adaptations'])
|
||||
self.assertIsInstance(element.children[1], XMLDictElement)
|
||||
self.assertEqual(element.children[1].tag, 'field')
|
||||
self.assertEqual(element.children[1].attrs['name'], 'model')
|
||||
self.assertEqual(element.children[1].children, ['sample.model'])
|
||||
self.assertIsInstance(element.children[2], XMLDictElement)
|
||||
self.assertEqual(element.children[2].tag, 'field')
|
||||
self.assertEqual(element.children[2].attrs['name'], 'inherit_id')
|
||||
self.assertFalse(element.children[2].children)
|
||||
self.assertIsInstance(element.children[3], XMLDictElement)
|
||||
self.assertEqual(element.children[3].tag, 'field')
|
||||
self.assertEqual(element.children[3].attrs['name'], 'arch')
|
||||
self.assertEqual(element.children[3].attrs['type'], 'xml')
|
||||
self.assertFalse(element.children[3].children)
|
||||
|
||||
def test_action_server_code(self):
|
||||
""" Test action_server_code function """
|
||||
element = od.action_server_code('sample.xmlid', 'Code', 'sample.model',
|
||||
'''record.do_something()''')
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'record')
|
||||
self.assertEqual(element.attrs['id'], 'sample.xmlid')
|
||||
self.assertEqual(element.attrs['model'], 'ir.actions.server')
|
||||
self.assertEqual((len(element.children)), 4)
|
||||
self.assertIsInstance(element.children[0], XMLDictElement)
|
||||
self.assertEqual(element.children[0].tag, 'field')
|
||||
self.assertEqual(element.children[0].attrs['name'], 'name')
|
||||
self.assertEqual(element.children[0].children, ['Code'])
|
||||
self.assertIsInstance(element.children[1], XMLDictElement)
|
||||
self.assertEqual(element.children[1].tag, 'field')
|
||||
self.assertEqual(element.children[1].attrs['name'], 'model_id')
|
||||
self.assertEqual(element.children[1].attrs['ref'], 'sample.model')
|
||||
self.assertFalse(element.children[1].children)
|
||||
self.assertEqual(element.children[2].tag, 'field')
|
||||
self.assertEqual(element.children[2].attrs['name'], 'state')
|
||||
self.assertEqual(element.children[2].children, ['code'])
|
||||
self.assertEqual(element.children[3].tag, 'field')
|
||||
self.assertEqual(element.children[3].attrs['name'], 'code')
|
||||
self.assertEqual(element.children[3].children, ['record.do_something()'])
|
||||
|
||||
def test_client_action_multi(self):
|
||||
""" Test client_action_multi function """
|
||||
element = od.client_action_multi('sample.xmlid', 'Multi', 'sample.model', 'sample.action')
|
||||
self.assertIsInstance(element, XMLDictElement)
|
||||
self.assertEqual(element.tag, 'record')
|
||||
self.assertEqual(element.attrs['id'], 'sample.xmlid')
|
||||
self.assertEqual(element.attrs['model'], 'ir.values')
|
||||
self.assertEqual((len(element.children)), 4)
|
||||
self.assertIsInstance(element.children[0], XMLDictElement)
|
||||
self.assertEqual(element.children[0].tag, 'field')
|
||||
self.assertEqual(element.children[0].attrs['name'], 'name')
|
||||
self.assertEqual(element.children[0].children, ['Multi'])
|
||||
self.assertIsInstance(element.children[1], XMLDictElement)
|
||||
self.assertEqual(element.children[1].tag, 'field')
|
||||
self.assertEqual(element.children[1].attrs['name'], 'key2')
|
||||
self.assertEqual(element.children[1].attrs['eval'], "'client_action_multi'")
|
||||
self.assertFalse(element.children[1].children)
|
||||
self.assertEqual(element.children[2].tag, 'field')
|
||||
self.assertEqual(element.children[2].attrs['name'], 'model')
|
||||
self.assertEqual(element.children[2].attrs['eval'], "'sample.model'")
|
||||
self.assertEqual(element.children[3].tag, 'field')
|
||||
self.assertEqual(element.children[3].attrs['name'], 'value')
|
||||
self.assertEqual(element.children[3].attrs['eval'], "'ir.actions.server,%d'%sample.action")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -0,0 +1,127 @@
|
|||
# Copyright 2020 Fabien Bourgeois <fabien@yaltik.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" XML Helpers tests """
|
||||
|
||||
from functools import partial
|
||||
import unittest
|
||||
import xml.etree.ElementTree as ET
|
||||
from os import unlink
|
||||
from src.xml_base import xmln, xmlroot, xmlchild, xml_write
|
||||
|
||||
|
||||
class TestXMLBase(unittest.TestCase):
|
||||
""" XML Helpers tests """
|
||||
|
||||
def test_xmln(self):
|
||||
""" Text xmln """
|
||||
# Tags
|
||||
self.assertDictEqual(xmln()._asdict(), {'tag': '', 'attrs': {}, 'children': []})
|
||||
self.assertEqual(xmln('a tag').tag, 'a tag')
|
||||
# Attrs
|
||||
self.assertDictEqual(xmln(attrs={'a good': 'one'}).attrs, {'a good': 'one'})
|
||||
# Childrens
|
||||
self.assertListEqual(xmln(children=[1, 2, 3]).children, [1, 2, 3])
|
||||
self.assertListEqual(xmln(children='Some text').children, ['Some text'])
|
||||
|
||||
with self.assertRaisesRegex(TypeError, 'Invalid arguments'):
|
||||
xmln(children=False)
|
||||
|
||||
# Ensure that only children after tags is managed
|
||||
element = xmln('tag', {'something': 'inside'})
|
||||
self.assertIsInstance(element.attrs, dict)
|
||||
self.assertIsInstance(element.children, list)
|
||||
|
||||
element = xmln('tag', ['something', 'inside'])
|
||||
self.assertIsInstance(element.attrs, dict)
|
||||
self.assertIsInstance(element.children, list)
|
||||
|
||||
|
||||
def test_xmlchild(self):
|
||||
""" Test xmlchild """
|
||||
parent = xmlroot({'tag': 'root', 'attrs': {}, 'children': []})
|
||||
xmlc_par = partial(xmlchild, parent)
|
||||
|
||||
# Bad arguments
|
||||
with self.assertRaisesRegex(TypeError, 'Invalid arguments for xmlchild'):
|
||||
xmlc_par(False)
|
||||
# Need XMLDictElement, not dict
|
||||
with self.assertRaisesRegex(TypeError, 'Invalid arguments for xmlchild'):
|
||||
xmlc_par([{'tag': 't', 'attrs': {'a': 'b'}, 'children': []},])
|
||||
|
||||
xmlc_par(['some text'])
|
||||
self.assertEqual(parent.text, 'some text')
|
||||
|
||||
xmlc_par([xmln('t', {'a': 'b'}, []),])
|
||||
child = parent.iter('t').__next__()
|
||||
self.assertEqual(child.tag, 't')
|
||||
self.assertDictEqual(child.attrib, {'a': 'b'})
|
||||
self.assertListEqual(list(child), [])
|
||||
|
||||
xmlc_par([xmln('t2', {1: 2}, []),])
|
||||
child = parent.iter('t2').__next__()
|
||||
self.assertDictEqual(child.attrib, {'1': '2'})
|
||||
|
||||
xmlc_par([xmln('tchildren', {}, [xmln('subchild', {}, []),]),])
|
||||
child = parent.iter('tchildren').__next__()
|
||||
subchildren = list(child)
|
||||
self.assertEqual(len(subchildren), 1)
|
||||
self.assertEqual(subchildren[0].tag, 'subchild')
|
||||
|
||||
|
||||
def test_xmlroot(self):
|
||||
""" Test xmlroot """
|
||||
root = xmlroot({'tag': 'root', 'attrs': {}, 'children': []})
|
||||
self.assertIsInstance(root, ET.Element)
|
||||
|
||||
with self.assertRaisesRegex(TypeError, 'not subscriptable'):
|
||||
xmlroot(False)
|
||||
with self.assertRaisesRegex(KeyError, 'tag'):
|
||||
xmlroot({})
|
||||
with self.assertRaisesRegex(KeyError, 'attrs'):
|
||||
xmlroot({'tag': 'root'})
|
||||
|
||||
|
||||
def test_xml_write(self):
|
||||
""" test xml_write """
|
||||
children = [xmln('child1', {'attr': 'value'}, []),
|
||||
xmln('child2', {}, "Some text")]
|
||||
tree = xmlroot({'tag': 'root', 'attrs': {}, 'children': children})
|
||||
xmlw = lambda p: xml_write(p, tree)
|
||||
|
||||
self.assertIsNone(xmlw('/badpath'))
|
||||
self.assertIsNone(xmlw('/bad.ext'))
|
||||
|
||||
xmlw(__file__)
|
||||
filepath = __file__.replace('.py', '_views.xml')
|
||||
with open(filepath, 'r') as output_file:
|
||||
output_xml = output_file.read()
|
||||
self.assertIn('<?xml version', output_xml)
|
||||
self.assertIn('<root>', output_xml)
|
||||
self.assertIn('<child1 attr="value"/>', output_xml)
|
||||
self.assertIn('<child2>Some text</child2>', output_xml)
|
||||
unlink(filepath)
|
||||
xml_write(__file__, tree, suffix='_data')
|
||||
filepath = __file__.replace('.py', '_data.xml')
|
||||
with open(filepath, 'r') as output_file:
|
||||
output_xml = output_file.read()
|
||||
self.assertIn('<?xml version', output_xml)
|
||||
self.assertIn('<root>', output_xml)
|
||||
self.assertIn('<child1 attr="value"/>', output_xml)
|
||||
self.assertIn('<child2>Some text</child2>', output_xml)
|
||||
unlink(filepath)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -25,7 +25,7 @@
|
|||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': ['portal_project'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
'author': 'Fabien Bourgeois',
|
||||
'license': 'AGPL-3',
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'installable': False,
|
||||
'depends': ['portal_project_issue'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
|
|
Loading…
Reference in New Issue