diff --git a/attendance_iot/models/iot_message.py b/attendance_iot/models/iot_message.py index 09aeed617..9c1b55edd 100644 --- a/attendance_iot/models/iot_message.py +++ b/attendance_iot/models/iot_message.py @@ -5,6 +5,30 @@ from odoo import fields, models, api _logger = logging.getLogger(__name__) +def _strip_face_template(message_data_str): + """移除 message_data JSON 中的 face_template 字段(图片base64太长)""" + if not message_data_str: + return message_data_str + try: + data = json.loads(message_data_str) + if isinstance(data, dict) and 'face_template' in data: + data.pop('face_template') + _logger.info("已移除 face_template 字段") + return json.dumps(data, ensure_ascii=False) + if isinstance(data, list): + changed = False + for item in data: + if isinstance(item, dict) and 'face_template' in item: + item.pop('face_template') + changed = True + if changed: + _logger.info("已移除列表中的 face_template 字段") + return json.dumps(data, ensure_ascii=False) + return message_data_str + except (json.JSONDecodeError, TypeError): + return message_data_str + + class IotMessage(models.Model): _name = 'iot.message' _description = 'mqtt 消息,发送和接收' @@ -21,6 +45,52 @@ class IotMessage(models.Model): # 响应信息 res_data = fields.Text(string='发送后的响应信息') + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals.get('message_data'): + vals['message_data'] = _strip_face_template(vals['message_data']) + return super().create(vals_list) + + def write(self, vals): + if vals.get('message_data'): + vals['message_data'] = _strip_face_template(vals['message_data']) + return super().write(vals) + + def action_clear_face_template(self): + """批量清除已有记录的 face_template 参数""" + count = 0 + for record in self: + if not record.message_data: + continue + try: + data = json.loads(record.message_data) + changed = False + if isinstance(data, dict) and 'face_template' in data: + data.pop('face_template') + changed = True + elif isinstance(data, list): + for item in data: + if isinstance(item, dict) and 'face_template' in item: + item.pop('face_template') + changed = True + if changed: + record.write({'message_data': json.dumps(data, ensure_ascii=False)}) + count += 1 + except (json.JSONDecodeError, TypeError): + continue + _logger.info(f"已清理 {count} 条记录的 face_template") + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': '完成', + 'message': f'已清理 {count} 条记录的 face_template', + 'sticky': False, + 'type': 'success', + } + } + @api.model def to_reply(self, data): _logger.info(f'返回消息:{data}') diff --git a/attendance_iot/views/iot_message.xml b/attendance_iot/views/iot_message.xml index 849eb04bd..a61385825 100644 --- a/attendance_iot/views/iot_message.xml +++ b/attendance_iot/views/iot_message.xml @@ -8,6 +8,9 @@ iot.message +
+
diff --git a/yuthon_base/models/__init__.py b/yuthon_base/models/__init__.py index 19457e29a..2f7af145a 100644 --- a/yuthon_base/models/__init__.py +++ b/yuthon_base/models/__init__.py @@ -9,4 +9,5 @@ from . import res_partner from . import base_month_year from . import system_logs from . import res_users -from . import mail_thread \ No newline at end of file +from . import mail_thread +from . import ir_ui_menu diff --git a/yuthon_base/models/ir_ui_menu.py b/yuthon_base/models/ir_ui_menu.py new file mode 100644 index 000000000..eeaafe667 --- /dev/null +++ b/yuthon_base/models/ir_ui_menu.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models, _ +import pypinyin +import re + + +class IrUiMenu(models.Model): + _inherit = 'ir.ui.menu' + + name_pinyin = fields.Char( + string='菜单名拼音', + compute='_compute_name_pinyin', + store=True, + index=True, + ) + + @api.depends('name') + def _compute_name_pinyin(self): + for menu in self: + if menu.name: + # 全拼:如 "通知公告" → "tongzhigonggao" + full_pinyin = ''.join(pypinyin.lazy_pinyin(menu.name)).lower() + # 首字母:如 "通知公告" → "tzgg" + first_letters = ''.join([ + p[0] for p in pypinyin.pinyin(menu.name) + ]).lower() + menu.name_pinyin = f"{full_pinyin} {first_letters}" + else: + menu.name_pinyin = False + + def name_search(self, name='', args=None, operator='ilike', limit=100): + """ + 重写 name_search 支持全拼/首字母模糊搜索菜单 + """ + if args is None: + args = [] + if name and operator in ('ilike', 'like', '=', '=like'): + # 清理输入:去掉空格、转小写 + search_term = name.strip().lower().replace(' ', '') + if not search_term: + return super().name_search(name=name, args=args, operator=operator, limit=limit) + + # 生成搜索词的拼音和首字母 + search_pinyin = ''.join(pypinyin.lazy_pinyin(search_term)).lower() + search_first = ''.join([p[0] for p in pypinyin.pinyin(search_term)]).lower() + + # 构建拼音搜索域:匹配 name_pinyin 字段 + pinyin_domain = [ + '|', + ('name_pinyin', 'ilike', search_term), # 直接搜索词 + ('name_pinyin', 'ilike', search_pinyin), # 全拼搜索 + ] + # 如果首字母不同,也加上 + if search_first != search_pinyin: + pinyin_domain = ['|'] + pinyin_domain + [('name_pinyin', 'ilike', search_first)] + + # 组合:原名搜索 OR 拼音搜索 + full_domain = ['|'] + expression.AND([args, [('name', operator, name)]]) + expression.AND([args, pinyin_domain]) + + # 去重:用 browse 把结果合并(优先按原名匹配排序) + name_results = self.search(expression.AND([args, [('name', operator, name)]]), limit=limit) + pinyin_results = self.search(expression.AND([args, pinyin_domain]), limit=limit) + all_ids = list(dict.fromkeys(name_results.ids + pinyin_results.ids))[:limit] + return self.browse(all_ids).name_get() + return super().name_search(name=name, args=args, operator=operator, limit=limit)