This commit is contained in:
ivan deng
2023-07-21 18:56:03 +08:00
parent 35b7dbb4b7
commit 82a6525b55
44 changed files with 10368 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Part of odooai.cn. See LICENSE file for full copyright and licensing details.
# Created on 2019-04-20
# author: 欧度智能http://www.odooai.cn
# email: 300883@qq.com
# resource of odooai
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
# Odoo12在线用户手册长期更新
# http://www.odooai.cn/documentation/user/12.0/en/index.html
# Odoo12在线开发者手册长期更新
# http://www.odooai.cn/documentation/12.0/index.html
# Odoo10在线中文用户手册长期更新
# http://www.odooai.cn/documentation/user/10.0/zh_CN/index.html
# Odoo10离线中文用户手册下载
# http://www.odooai.cn/odoo10_user_manual_document_offline/
# Odoo10离线开发手册下载-含python教程jquery参考Jinja2模板PostgresSQL参考odoo开发必备
# http://www.odooai.cn/odoo10_developer_document_offline/
# description:
from . import base
from . import fields
from . import view_validation
from . import ir_ui_view
from . import ir_cron
from . import res_users

110
app_common/models/base.py Normal file
View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
import requests
import base64
from io import BytesIO
from datetime import date, datetime, time
import pytz
import logging
_logger = logging.getLogger(__name__)
# 常规的排除的fields
EXCLU_FIELDS = [
'__last_update',
'access_token',
'access_url',
'access_warning',
'activity_date_deadline',
'activity_exception_decoration',
'activity_exception_icon',
'activity_ids',
'activity_state',
'activity_summary',
'activity_type_id',
'activity_user_id',
'display_name',
'message_attachment_count',
'message_channel_ids',
'message_follower_ids',
'message_has_error',
'message_has_error_counter',
'message_has_sms_error',
'message_ids',
'message_is_follower',
'message_main_attachment_id',
'message_needaction',
'message_needaction_counter',
'message_partner_ids',
'message_unread',
'message_unread_counter',
'website_message_ids',
'write_date',
'write_uid',
]
class Base(models.AbstractModel):
_inherit = 'base'
@api.model
def _get_normal_fields(self):
f_list = []
for k, v in self._fields.items():
if k not in EXCLU_FIELDS:
f_list.append(k)
return f_list
@api.model
def _app_get_m2o_default(self, fieldname, domain=[]):
if hasattr(self, fieldname) and self._fields[fieldname].type == 'many2one':
if self._context.get(fieldname) or self._context.get('default_%s' % fieldname):
return self._context.get(fieldname) or self._context.get('default_%s' % fieldname)
else:
rec = self.env[self._fields[fieldname].comodel_name].sudo().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中时间按格式转为用户本地时间.如果处理当天,是时间类型直接用 datetime.now(tz)
输入str或日期时间类型
输出: str
"""
if not value:
return value
if isinstance(value, datetime):
value = value.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
dt = datetime.strptime(value, DEFAULT_SERVER_DATETIME_FORMAT)
user_tz = pytz.timezone(self.env.user.tz or 'Etc/GMT-8')
# _logger.warning('============= user2 tz: %s' % user_tz)
dt = dt.replace(tzinfo=pytz.timezone('UTC'))
return dt.astimezone(user_tz).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(DEFAULT_SERVER_DATETIME_FORMAT)
dt = datetime.strptime(value, DEFAULT_SERVER_DATETIME_FORMAT)
pytz_timezone = pytz.timezone('Etc/GMT+8')
dt = dt.replace(tzinfo=pytz.timezone('UTC'))
return dt.astimezone(pytz_timezone).strftime(return_format)
@api.model
def get_image_from_url(self, url):
if not url:
return None
try:
response = requests.get(url) # 将这个图片保存在内存
except Exception as e:
return None
# 返回这个图片的base64编码
return base64.b64encode(BytesIO(response.content).read())

View File

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

View File

@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import api, fields, models, modules, tools, _
_logger = logging.getLogger(__name__)
class IrCron(models.Model):
_inherit = "ir.cron"
trigger_user_id = fields.Many2one('res.users', string='Last Trigger User')
def method_direct_trigger(self):
self.write({'trigger_user_id': self.env.user.id})
return super(IrCron, self).method_direct_trigger()

View File

@@ -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', 'pivot']:
_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.error('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):

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools, _
class ResUsers(models.Model):
_inherit = 'res.users'
login = fields.Char(index=True)

View File

@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
import ast
from odoo.tools import view_validation
from odoo.tools.view_validation import get_attrs_field_names as old_gafn
from odoo.tools.view_validation import _get_attrs_symbols
import logging
_logger = logging.getLogger(__name__)
ATTRS_WITH_FIELD_NAMES2 = {
'context',
'domain',
'decoration-bf',
'decoration-it',
'decoration-danger',
'decoration-info',
'decoration-muted',
'decoration-primary',
'decoration-success',
'decoration-warning',
'decoration-black',
'decoration-white',
'bg-danger',
'bg-info',
'bg-muted',
'bg-primary',
'bg-success',
'bg-warning',
'bg-black',
'bg-white',
}
def app_get_attrs_field_names(env, arch, model, editable):
symbols = _get_attrs_symbols() | {None}
result = []
def get_name(node):
""" return the name from an AST node, or None """
if isinstance(node, ast.Name):
return node.id
def process_expr(expr, get, key, val):
""" parse `expr` and collect triples """
for node in ast.walk(ast.parse(expr.strip(), mode='eval')):
name = get(node)
if name not in symbols:
result.append((name, key, val))
def add_bg(node, model, editable, get=get_name):
for key, val in node.items():
if not val:
continue
if key in ATTRS_WITH_FIELD_NAMES2:
process_expr(val, get, key, val)
res = old_gafn(env, arch, model, editable)
add_bg(arch, model, editable)
res += result
return res
# 使用猴子补丁方式更新
view_validation.get_attrs_field_names = app_get_attrs_field_names