From 6b8d15ce85e28b76922eaa9cb646c9ddbc07c5a0 Mon Sep 17 00:00:00 2001 From: ivan deng Date: Wed, 31 Mar 2021 23:21:26 +0800 Subject: [PATCH] opt m2o --- app_base_chinese/__manifest__.py | 2 +- app_base_chinese/data/crm_team_data.xml | 8 + app_base_chinese/data/ir_default_data.xml | 2 +- app_base_chinese/data/res_lang_data.xml | 2 +- app_base_chinese/hooks.py | 8 +- app_base_chinese/models/__init__.py | 2 - app_base_chinese/models/res_currency.py | 2 +- app_common/__manifest__.py | 20 +- app_common/models/__init__.py | 4 + app_common/models/base.py | 29 ++ app_common/models/fields.py | 49 ++ app_common/models/ir_ui_view.py | 66 +++ app_common/models/validator_old.py | 78 ++++ app_common/rng/activity_view.rng | 29 ++ app_common/rng/calendar_view.rng | 48 ++ app_common/rng/common.rng | 438 ++++++++++++++++++ app_common/rng/graph_view.rng | 39 ++ app_common/rng/pivot_view.rng | 28 ++ app_common/rng/search_view.rng | 57 +++ app_common/rng/search_view_new.rng | 54 +++ app_common/rng/tree_view.rng | 94 ++++ app_common/static/description/index.html | 8 +- .../models/res_config_settings.py | 2 + 23 files changed, 1045 insertions(+), 24 deletions(-) create mode 100644 app_base_chinese/data/crm_team_data.xml create mode 100644 app_common/models/fields.py create mode 100644 app_common/models/ir_ui_view.py create mode 100644 app_common/models/validator_old.py create mode 100644 app_common/rng/activity_view.rng create mode 100644 app_common/rng/calendar_view.rng create mode 100644 app_common/rng/common.rng create mode 100644 app_common/rng/graph_view.rng create mode 100644 app_common/rng/pivot_view.rng create mode 100644 app_common/rng/search_view.rng create mode 100644 app_common/rng/search_view_new.rng create mode 100644 app_common/rng/tree_view.rng diff --git a/app_base_chinese/__manifest__.py b/app_base_chinese/__manifest__.py index 434c6154..56876a72 100644 --- a/app_base_chinese/__manifest__.py +++ b/app_base_chinese/__manifest__.py @@ -18,7 +18,7 @@ { 'name': "App base chinese,中国化基本模块增强", - 'version': '14.20.10.05', + 'version': '14.21.03.22', 'author': 'Sunpop.cn', 'category': 'Base', 'website': 'https://www.sunpop.cn', diff --git a/app_base_chinese/data/crm_team_data.xml b/app_base_chinese/data/crm_team_data.xml new file mode 100644 index 00000000..19feb435 --- /dev/null +++ b/app_base_chinese/data/crm_team_data.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app_base_chinese/data/ir_default_data.xml b/app_base_chinese/data/ir_default_data.xml index df41c518..4f9ae9ef 100644 --- a/app_base_chinese/data/ir_default_data.xml +++ b/app_base_chinese/data/ir_default_data.xml @@ -10,7 +10,7 @@ - "Asia/Shanghai" + "Etc/GMT-8" diff --git a/app_base_chinese/data/res_lang_data.xml b/app_base_chinese/data/res_lang_data.xml index 2cbafeb0..a0e3efc0 100644 --- a/app_base_chinese/data/res_lang_data.xml +++ b/app_base_chinese/data/res_lang_data.xml @@ -1,6 +1,6 @@ - + %Y-%m-%d diff --git a/app_base_chinese/hooks.py b/app_base_chinese/hooks.py index 519e6004..de9af053 100644 --- a/app_base_chinese/hooks.py +++ b/app_base_chinese/hooks.py @@ -33,18 +33,18 @@ def post_init_hook(cr, registry): env = api.Environment(cr, SUPERUSER_ID, {}) ids = env['product.category'].sudo().with_context(lang='zh_CN').search([ ('parent_id', '!=', False) - ], order='complete_name') + ], order='parent_path') for rec in ids: rec._compute_complete_name() ids = env['stock.location'].sudo().with_context(lang='zh_CN').search([ ('location_id', '!=', False), ('usage', '!=', 'views'), - ], order='complete_name') + ], order='parent_path') for rec in ids: rec._compute_complete_name() # 超级用户改时区为中国 ids = env['res.users'].sudo().with_context(lang='zh_CN').browse([1, 2]) - ids.write({'tz': "Asia/Shanghai"}) + ids.write({'tz': "Etc/GMT-8"}) except Exception as e: raise Warning(e) @@ -52,4 +52,4 @@ def uninstall_hook(cr, registry): """ 数据初始化,卸载时执行 """ - pass \ No newline at end of file + pass diff --git a/app_base_chinese/models/__init__.py b/app_base_chinese/models/__init__.py index be3e94c5..423324f5 100644 --- a/app_base_chinese/models/__init__.py +++ b/app_base_chinese/models/__init__.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- from . import res_partner -from . import res_company from . import res_currency -from . import res_country from . import product_category from . import stock_location from . import account_tax_group diff --git a/app_base_chinese/models/res_currency.py b/app_base_chinese/models/res_currency.py index ad714fac..3f55e337 100644 --- a/app_base_chinese/models/res_currency.py +++ b/app_base_chinese/models/res_currency.py @@ -16,7 +16,7 @@ class ResCurrency(models.Model): :return 返回值是对应阿拉伯数字的绝对值的中文数字 """ if self.name != 'CNY': - return; + return rmbmap = [u"零", u"壹", u"贰", u"叁", u"肆", u"伍", u"陆", u"柒", u"捌", u"玖"] unit = [u"分", u"角", u"元", u"拾", u"佰", u"仟", u"万", u"拾", u"佰", u"仟", u"亿", u"拾", u"佰", u"仟", u"万", u"拾", u"佰", u"仟", u"兆"] diff --git a/app_common/__manifest__.py b/app_common/__manifest__.py index e493dacf..d6678341 100644 --- a/app_common/__manifest__.py +++ b/app_common/__manifest__.py @@ -32,8 +32,8 @@ ############################################################################## { - 'name': "Sunpop Odooapp Common Function", - 'version': '14.20.10.05', + 'name': "Sunpop Odooapp Common Func", + 'version': '14.21.01.27', 'author': 'Sunpop.cn', 'category': 'Base', 'website': 'https://www.sunpop.cn', @@ -43,20 +43,20 @@ 'currency': 'EUR', 'images': ['static/description/banner.png'], 'depends': [ + 'base', 'web', ], 'summary': ''' - Core for common use. - Add get m2o default method - 基础核心 + Core for common use sunpop apps. + 基础核心,必须没有要被依赖字段及视图等,实现auto_install ''', 'description': ''' - Support Odoo 14,13,12, Enterprise and Community Edition - 1. Easy get default 1st record of m2o list. + Support Odoo 13,12, Enterprise and Community Edition + 1. 2. 3. Multi-language Support. 4. Multi-Company Support. - 5. Support Odoo 14,13,12, Enterprise and Community Edition + 5. Support Odoo 13,12, Enterprise and Community Edition ========== 1. 2. @@ -68,7 +68,7 @@ # 'security/*.xml', # 'security/ir.model.access.csv', # 'data/.xml', - # 'views/.xml', + # 'views/ir_module_module_views.xml', # 'report/.xml', ], 'qweb': [ @@ -80,5 +80,5 @@ # 'uninstall_hook': 'uninstall_hook', 'installable': True, 'application': True, - 'auto_install': False, + 'auto_install': True, } diff --git a/app_common/models/__init__.py b/app_common/models/__init__.py index 736cc270..e98cf58e 100644 --- a/app_common/models/__init__.py +++ b/app_common/models/__init__.py @@ -23,3 +23,7 @@ # description: from . import base +# from . import fields +# from . import validator +# from . import ir_ui_view + diff --git a/app_common/models/base.py b/app_common/models/base.py index 1639cde7..5f27377e 100644 --- a/app_common/models/base.py +++ b/app_common/models/base.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- from odoo import models, fields, api, _ +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT +from datetime import date, datetime, time +import pytz class Base(models.AbstractModel): _inherit = 'base' @@ -14,3 +17,29 @@ class Base(models.AbstractModel): rec = self.env[self._fields[fieldname].comodel_name].search(domain, limit=1) return rec.id if rec else False return False + + def _app_dt2local(self, value, return_format=DEFAULT_SERVER_DATETIME_FORMAT): + """ + 将value中时间,按格式转为用户本地时间 + """ + if not value: + return value + if isinstance(value, datetime): + value = value.strftime(return_format) + dt = datetime.strptime(value, return_format) + pytz_timezone = pytz.timezone(self.env.user.tz or 'Etc/GMT-8') + dt = dt.replace(tzinfo=pytz.timezone('UTC')) + return dt.astimezone(pytz_timezone).strftime(return_format) + + def _app_dt2utc(self, value, return_format=DEFAULT_SERVER_DATETIME_FORMAT): + """ + 将value中用户本地时间,按格式转为UTC时间,输出 str + """ + if not value: + return value + if isinstance(value, datetime): + value = value.strftime(return_format) + dt = datetime.strptime(value, return_format) + user_tz = pytz.timezone(self.env.user.tz or 'Etc/GMT+8') + dt = dt.replace(tzinfo=pytz.timezone('UTC')) + return dt.astimezone(user_tz).strftime(return_format) diff --git a/app_common/models/fields.py b/app_common/models/fields.py new file mode 100644 index 00000000..d0149013 --- /dev/null +++ b/app_common/models/fields.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +from odoo.fields import Field, resolve_mro +from odoo.fields import Selection as oldSelection +from odoo.tools import merge_sequences +import logging + +_logger = logging.getLogger(__name__) + +# 此处用猴子补丁,热更新,不影响后续继承 +class Selection(Field): + def _setup_attrs_app(self, model, name): + Field._setup_attrs(self, model, name) + + # determine selection (applying 'selection_add' extensions) + values = None + labels = {} + + for field in reversed(resolve_mro(model, name, self._can_setup_from)): + # We cannot use field.selection or field.selection_add here + # because those attributes are overridden by ``_setup_attrs``. + if 'selection' in field.args: + selection = field.args['selection'] + if isinstance(selection, list): + if ( + values is not None + and values != [kv[0] for kv in selection] + ): + _logger.debug("%s: selection=%r overrides existing selection; use selection_add instead", self, selection) + values = [kv[0] for kv in selection] + labels = dict(selection) + else: + self.selection = selection + values = None + labels = {} + + if 'selection_add' in field.args: + selection_add = field.args['selection_add'] + assert isinstance(selection_add, list), \ + "%s: selection_add=%r must be a list" % (self, selection_add) + assert values is not None, \ + "%s: selection_add=%r on non-list selection %r" % (self, selection_add, self.selection) + values = merge_sequences(values, [kv[0] for kv in selection_add]) + labels.update(kv for kv in selection_add if len(kv) == 2) + + if values is not None: + self.selection = [(value, labels[value]) for value in values] + +oldSelection._setup_attrs = Selection._setup_attrs_app \ No newline at end of file diff --git a/app_common/models/ir_ui_view.py b/app_common/models/ir_ui_view.py new file mode 100644 index 00000000..166f8240 --- /dev/null +++ b/app_common/models/ir_ui_view.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +from odoo import api, models, tools, SUPERUSER_ID +from odoo.modules.module import get_resource_path +from odoo.tools import view_validation +from odoo.tools.view_validation import _relaxng_cache, validate, _validators +from odoo.tools.safe_eval import safe_eval + +from lxml import etree +import logging +_logger = logging.getLogger(__name__) + +@validate('tree') +def app_valid_field_in_tree(arch, **kwargs): + # 增加 header + return all( + child.tag in ('field', 'button', 'control', 'groupby', 'header') + for child in arch.xpath('/tree/*') + ) + +def app_relaxng(view_type): + """ Return a validator for the given view type, or None. """ + if view_type not in _relaxng_cache: + # tree, search 特殊 + if view_type in ['tree', 'search']: + _file = get_resource_path('app_common', 'rng', '%s_view.rng' % view_type) + else: + _file = get_resource_path('base', 'rng', '%s_view.rng' % view_type) + with tools.file_open(_file) as frng: + try: + relaxng_doc = etree.parse(frng) + _relaxng_cache[view_type] = etree.RelaxNG(relaxng_doc) + except Exception: + _logger.exception('Failed to load RelaxNG XML schema for views validation') + _relaxng_cache[view_type] = None + return _relaxng_cache[view_type] + +def app_reset_valid_view(view_type): + _relaxng_cache = view_validation._relaxng_cache + for pred in _validators[view_type]: + # 要pop掉函数 valid_field_in_tree + if pred.__name__ == 'valid_field_in_tree': + _validators[view_type].remove(pred) + try: + _relaxng_cache.pop(view_type, None) + _relaxng_cache[view_type] = None + except Exception: + pass + _relaxng_cache[view_type] = app_relaxng(view_type) + +app_reset_valid_view('tree') +view_validation.valid_field_in_tree = app_valid_field_in_tree +view_validation.relaxng = app_relaxng + +class View(models.Model): + _inherit = 'ir.ui.view' + + def __init__(self, *args, **kwargs): + super(View, self).__init__(*args, **kwargs) + view_validation.relaxng = app_relaxng + # 重置 tree + app_reset_valid_view('tree') + + # todo: 有可能需要处理增加的 header等标签 + # 直接重写原生方法 + # def transfer_node_to_modifiers(node, modifiers, context=None, in_tree_view=False): diff --git a/app_common/models/validator_old.py b/app_common/models/validator_old.py new file mode 100644 index 00000000..ed6ae118 --- /dev/null +++ b/app_common/models/validator_old.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +from odoo import tools, _ +from odoo.modules.module import get_resource_path +from odoo.tools import view_validation +from odoo.tools.view_validation import validate, _validators +from lxml import etree +import logging + + +_logger = logging.getLogger(__name__) + +_relaxng_cache = view_validation._relaxng_cache +_relaxng_cache['tree'] = None +with tools.file_open(get_resource_path('app_common', 'rng', 'tree_view.rng')) as frng: + try: + text = frng.read() + # with tools.file_open('addons/base/rng/common.rng') as common_rng: + # common_txt = common_rng.read() + # start_pos = common_txt.find('', start_pos) + # end_pos = common_txt.find('') + # common_content = common_txt[start_pos + 1: end_pos] + # + # # 从14中学习,最新 common + # # + # old_content = ''' + # + # ''' + # new_content = ''' + # + # + # + # + # + # + # + # + # + # + # + # + # + # ''' + # common_content = common_content.replace(old_content, new_content) + # # common 替代 + # text = text.replace('', common_content) + # # tree 替代 + # old_content = '''''' + # new_content = ''' + # + # + # + # + # ''' + # text = text.replace(old_content, new_content) + # # 增加 fx_tree_table 支持 + # text = text.replace('', + # '') + + tmp_doc = etree.fromstring(text.encode('utf-8')) + _relaxng_cache['tree'] = etree.RelaxNG(tmp_doc) + _logger.warning('=========new tree done: %s' % _relaxng_cache['tree']) + except Exception as error: + _logger.exception('Failed to load RelaxNG XML schema for views validation, {error}'.format( + error=error)) + _relaxng_cache['tree'] = None + + +@validate('tree') +def app_valid_field_in_tree(arch, **kwargs): + # 增加 header + return all( + child.tag in ('field', 'button', 'control', 'groupby', 'header') + for child in arch.xpath('/tree/*') + ) + +view_validation.valid_field_in_tree = app_valid_field_in_tree diff --git a/app_common/rng/activity_view.rng b/app_common/rng/activity_view.rng new file mode 100644 index 00000000..4560d035 --- /dev/null +++ b/app_common/rng/activity_view.rng @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_common/rng/calendar_view.rng b/app_common/rng/calendar_view.rng new file mode 100644 index 00000000..ee51acbd --- /dev/null +++ b/app_common/rng/calendar_view.rng @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + year + month + week + day + + + + + + + + + + + + + + diff --git a/app_common/rng/common.rng b/app_common/rng/common.rng new file mode 100644 index 00000000..a62f159a --- /dev/null +++ b/app_common/rng/common.rng @@ -0,0 +1,438 @@ + + + + + + + + + + + + before + + after + + inside + + replace + + + + + + attributes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_common/rng/graph_view.rng b/app_common/rng/graph_view.rng new file mode 100644 index 00000000..47f547f0 --- /dev/null +++ b/app_common/rng/graph_view.rng @@ -0,0 +1,39 @@ + + + + + + + + + + + bar + pie + line + pivot + + + + + + + + + + + + + + + + + + + + + diff --git a/app_common/rng/pivot_view.rng b/app_common/rng/pivot_view.rng new file mode 100644 index 00000000..23a77407 --- /dev/null +++ b/app_common/rng/pivot_view.rng @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_common/rng/search_view.rng b/app_common/rng/search_view.rng new file mode 100644 index 00000000..69a03bfc --- /dev/null +++ b/app_common/rng/search_view.rng @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_common/rng/search_view_new.rng b/app_common/rng/search_view_new.rng new file mode 100644 index 00000000..cb8ff88d --- /dev/null +++ b/app_common/rng/search_view_new.rng @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_common/rng/tree_view.rng b/app_common/rng/tree_view.rng new file mode 100644 index 00000000..a04eb2b3 --- /dev/null +++ b/app_common/rng/tree_view.rng @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top + bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_common/static/description/index.html b/app_common/static/description/index.html index f4277771..cc311fb7 100644 --- a/app_common/static/description/index.html +++ b/app_common/static/description/index.html @@ -1,10 +1,10 @@
-

Sunpop Odooapp Common Function

+

-

Lastest update: v14.20.10.05

+

Lastest update: v13.20.01.01

@@ -14,7 +14,7 @@
  • - Easy get default 1st record of m2o list. + Put key function here.
  • @@ -26,7 +26,7 @@
  • - 5. Support Odoo 14,13,12, 11, Enterprise and Community Edition. + 5. Support Odoo 13,12, 11, Enterprise and Community Edition.
diff --git a/app_odoo_customize/models/res_config_settings.py b/app_odoo_customize/models/res_config_settings.py index d24da809..6b7aab28 100644 --- a/app_odoo_customize/models/res_config_settings.py +++ b/app_odoo_customize/models/res_config_settings.py @@ -3,6 +3,7 @@ import logging from odoo import api, fields, models, _ +from odoo.exceptions import UserError, ValidationError _logger = logging.getLogger(__name__) @@ -337,6 +338,7 @@ class ResConfigSettings(models.TransientModel): return res def remove_account_chart(self): + # todo: 安装会计模块后,会有问题,后续处理 company_id = self.env.company.id self = self.with_context(force_company=company_id, company_id=company_id) to_removes = [