int app-odoo 17, paid

This commit is contained in:
Ivan Office
2023-09-30 10:58:00 +08:00
parent e346c360a2
commit 72c740dd76
355 changed files with 14784 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Part of odooai.cn. See LICENSE file for full copyright and licensing details.
# Created on 2023-02-02
# 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 ir_ui_view
from . import ir_cron
from . import res_users
from . import ir_mail_server
from . import mail_mail
from . import ir_http
from . import app_import
from . import res_partner

View File

@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
import base64
import io
import csv
import os.path
from odoo import api, fields, models, modules, tools, SUPERUSER_ID, _
from odoo.tools import pycompat
from odoo.tests import common
ADMIN_USER_ID = common.ADMIN_USER_ID
def app_quick_import(cr, content_path, sep=None):
if not sep:
sep = '/'
dir_split = content_path.split(sep)
module_name = dir_split[0]
file_name = dir_split[2]
file_path, file_type = os.path.splitext(content_path)
model_name = file_name.replace(file_type, '')
file_path = modules.get_module_resource(module_name, dir_split[1], file_name)
content = open(file_path, 'rb').read()
uid = SUPERUSER_ID
if model_name == 'mail.channel':
# todo: 创建mail.channel时如果用root用户会报错
uid = 2
env = api.Environment(cr, uid, {})
if file_type == '.csv':
file_type = 'text/csv'
elif file_type in ['.xls', '.xlsx']:
file_type = 'application/vnd.ms-excel'
import_wizard = env['base_import.import'].create({
'res_model': model_name,
'file_name': file_name,
'file_type': file_type,
'file': content,
})
if file_type == 'text/csv':
preview = import_wizard.parse_preview({
'separator': ',',
'has_headers': True,
'quoting': '"',
})
elif file_type == 'application/vnd.ms-excel':
preview = import_wizard.parse_preview({
'has_headers': True,
})
result = import_wizard.execute_import(
preview["headers"],
preview["headers"],
preview["options"]
)

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

@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.http import request
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:
if not domain:
domain = self._fields[fieldname].domain or []
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中时间按格式转为用户本地时间.注意只处理in str为字符串类型,如果是时间类型直接用 datetime.now(tz)
"""
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')
_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(return_format)
dt = datetime.strptime(value, return_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):
# 返回这个图片的base64编码
return get_image_from_url(url)
def get_ua_type(self):
return get_ua_type()
def get_image_from_url(url):
if not url:
return None
try:
response = requests.get(url, timeout=5)
except Exception as e:
return None
# 返回这个图片的base64编码
return base64.b64encode(BytesIO(response.content).read())
def get_ua_type():
ua = request.httprequest.headers.get('User-Agent')
# 临时用 agent 处理,后续要前端中正确处理或者都从后台来
# 微信浏览器
# MicroMessenger: Mozilla/5.0 (Linux; Android 10; ELE-AL00 Build/HUAWEIELE-AL00; wv)
# AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120
# MQQBrowser/6.2 TBS/045525 Mobile Safari/537.36 MMWEBID/3135 MicroMessenger/8.0.2.1860(0x2800023B) Process/tools WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64
# 微信浏览器,开发工具,网页 iphone
# ,Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
# wechatdevtools/1.03.2011120 MicroMessenger/7.0.4 Language/zh_CN webview/16178807094901773
# webdebugger port/27772 token/b91f4a234b918f4e2a5d1a835a09c31e
# 微信小程序
# MicroMessenger: Mozilla/5.0 (Linux; Android 10; ELE-AL00 Build/HUAWEIELE-AL00; wv)
# AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.62 XWEB/2767 MMWEBSDK/20210302 Mobile Safari/537.36 MMWEBID/6689 MicroMessenger/8.0.2.1860(0x2800023B) Process/appbrand2 WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64
# MiniProgramEnv/android
# 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_7_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.37(0x18002529) NetType/WIFI Language/zh_CN'
# 微信浏览器开发工具小程序iphone
# Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
# wechatdevtools/1.03.2011120 MicroMessenger/7.0.4 Language/zh_CN webview/
# 微信内iphone web
# Mozilla/5.0 (iPhone; CPU iPhone OS 14_4_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
# MicroMessenger/8.0.3(0x1800032a) NetType/WIFI Language/zh_CN
# 安卓app,h5
# ELE-AL00(Android/10) (cn.erpapp.o20sticks.App/13.20.12.09) Weex/0.26.0 1080x2265
# web 表示普通浏览器,后续更深入处理
utype = 'web'
# todo: 引入现成 py lib处理企业微信
if 'MicroMessenger' in ua and 'webdebugger' not in ua \
and ('miniProgram' in ua or 'MiniProgram' in ua or 'MiniProgramEnv' in ua or 'wechatdevtools' in ua):
# 微信小程序及开发者工具
utype = 'wxapp'
elif 'MicroMessenger' in ua:
# 微信浏览器
utype = 'wxweb'
elif 'cn.erpapp.o20sticks.App' in ua:
# 安卓app
utype = 'native_android'
# _logger.warning('=========get ua %s,%s' % (utype, ua))
return utype

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,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo.http import request
class IrHttp(models.AbstractModel):
_inherit = 'ir.http'
def session_info(self):
result = super(IrHttp, self).session_info()
result['ua_type'] = self.get_ua_type()
return result

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
import logging
_logger = logging.getLogger(__name__)
class IrMailServer(models.Model):
_inherit = "ir.mail_server"
_order = "sequence"
# 改默认发邮件逻辑
@api.model
def send_email(self, message, mail_server_id=None, smtp_server=None, smtp_port=None,
smtp_user=None, smtp_password=None, smtp_encryption=None, smtp_debug=False,
smtp_session=None):
email_to = message['To']
# 忽略掉无效email避免被ban
if email_to.find('no-reply@odooai.cn') != -1 or email_to.find('postmaster-odoo@odooai.cn') != -1:
pass
elif email_to.find('example.com') != -1 or email_to.find('@sunpop.cn') != -1 or email_to.find('@odooapp.cn') != -1:
_logger.error(_("=================Email to ignore: %s") % email_to)
raise AssertionError(_("Email to ignore: %s") % email_to)
return super(IrMailServer, self).send_email(message, mail_server_id, smtp_server, smtp_port,
smtp_user, smtp_password, smtp_encryption, smtp_debug,
smtp_session)

View File

@@ -0,0 +1,42 @@
# -*- 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__)
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.error('Failed to load RelaxNG XML schema for views validation')
_relaxng_cache[view_type] = None
return _relaxng_cache[view_type]
view_validation.relaxng = app_relaxng
class View(models.Model):
_inherit = 'ir.ui.view'
def __init__(self, env, ids, prefetch_ids):
# 这里应该是无必要,但为了更安全
super(View, self).__init__(env, ids, prefetch_ids)
view_validation.relaxng = app_relaxng
# todo: 有可能需要处理增加的 header等标签
# 直接重写原生方法
# def transfer_node_to_modifiers(node, modifiers, context=None, in_tree_view=False):

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
import logging
_logger = logging.getLogger(__name__)
class MailMail(models.Model):
_inherit = "mail.mail"
# 猴子补丁模式,改默认发邮件逻辑
def _send(self, auto_commit=False, raise_exception=False, smtp_session=None):
for m in self:
if m.email_to and (m.email_to.find('example.com') != -1 or m.email_to.find('@odooai.cn') != -1 or m.email_to.find('@odooapp.cn') != -1):
self = self - m
return super(MailMail, self)._send(auto_commit, raise_exception, smtp_session)

View File

@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools, _
class ResPartner(models.Model):
_inherit = 'res.partner'
def get_related_user_id(self):
self.ensure_one()
user = self.env['res.users'].sudo().with_context(active_test=False).search([('partner_id', '=', self.id)], limit=1)
return user

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)