Odoo按时间段限制用户登录失效问题解决求助
Odoo 用户登录时间限制失效问题排查与修复
问题背景
开发自定义Odoo模块时,需限制用户仅在特定时间段(如08:00至18:00)登录。已在res.users中添加access_start_time和access_end_time两个Float字段,并重写_check_credentials()方法,同时尝试通过控制器重写/web/login路由实现限制,但用户始终能不受限制登录,条件判断未生效。
原代码实现
1. ResUsers 模型代码
from datetime import datetime from odoo import models, fields, api, _ from odoo.exceptions import ValidationError, AccessDenied class ResUsers(models.Model): _inherit = 'res.users' access_start_time = fields.Float( string="Heure de début d'accès", default=8.0, help="Heure au format 24h, exemple: 8.5 pour 08:30" ) access_end_time = fields.Float( string="Heure de fin d'accès", default=18.0, help="Heure au format 24h, exemple: 18.0 pour 18:00" ) @api.constrains('access_start_time', 'access_end_time') def _check_valid_time_range(self): for rec in self: if not (0 <= rec.access_start_time <= 23.99): raise ValidationError(_("L'heure de début doit être comprise entre 00:00 et 23:59.")) if not (0 <= rec.access_end_time <= 23.99): raise ValidationError(_("L'heure de fin doit être comprise entre 00:00 et 23:59.")) if rec.access_start_time >= rec.access_end_time: raise ValidationError(_("L'heure de début doit être inférieure à l'heure de fin.")) def _check_credentials(self, password): """Override to restrict login based on time.""" super()._check_credentials(password) now = datetime.now().time() current_hour = now.hour + now.minute / 60 if not self._uid: return user = self.browse(self._uid) if not user.exists() or user._is_admin(): return # Don't block admin or invalid users start = user.access_start_time or 0.0 end = user.access_end_time or 23.99 if not (start <= current_hour <= end): raise AccessDenied(_( "⛔ Accès refusé ! Vos horaires autorisés sont de %s à %s." ) % ( f"{int(start):02d}:{int((start % 1) * 60):02d}", f"{int(end):02d}:{int((end % 1) * 60):02d}", ))
2. 登录控制器代码
from odoo import http, _ from odoo.http import request from datetime import datetime from odoo.exceptions import AccessDenied class AuthControllerOverride(http.Controller): @http.route('/web/login', type='http', auth="none") def web_login(self, redirect=None, **kw): response = request.redirect_with_hash('/web') if request.httprequest.method == 'POST': uid = request.session.authenticate( request.session.db, kw['login'], kw['password'] ) user = request.env['res.users'].sudo().browse(uid) if not user._is_admin(): now = datetime.now().time() current_hour = now.hour + now.minute / 60 start = user.access_start_time or 0.0 end = user.access_end_time or 23.99 if not (start <= current_hour <= end): # Kill session and show error request.session.logout() return request.render('web.login', { 'error': _("⛔ Accès refusé : vous êtes hors de vos horaires autorisés (%s - %s).") % ( f"{int(start):02d}:{int((start % 1) * 60):02d}", f"{int(end):02d}:{int((end % 1) * 60):02d}", ) }) return response
问题原因分析
_check_credentials上下文错误:调用该方法时,self是空的res.users模型记录集,self.browse(self._uid)无法正确加载用户的时间字段值,导致条件判断失效。- 控制器逻辑缺陷:直接返回
request.redirect_with_hash('/web')跳过了原登录页面的渲染流程,且未处理authenticate返回False的情况,导致时间限制逻辑未被触发。 - 浮点时间精度问题:使用
current_hour = now.hour + now.minute / 60计算当前时间时,浮点运算可能导致精度误差(如23.99对应23:59:24),无法准确匹配到23:59。
修复方案
方案一:修正_check_credentials方法
利用super()调用成功后self.env.user已正确初始化的特性,改用分钟总数进行时间比较,避免浮点精度问题:
from datetime import datetime from odoo import models, fields, api, _ from odoo.exceptions import ValidationError, AccessDenied class ResUsers(models.Model): _inherit = 'res.users' access_start_time = fields.Float( string="Heure de début d'accès", default=8.0, help="Heure au format 24h, exemple: 8.5 pour 08:30" ) access_end_time = fields.Float( string="Heure de fin d'accès", default=18.0, help="Heure au format 24h, exemple: 18.0 pour 18:00" ) @api.constrains('access_start_time', 'access_end_time') def _check_valid_time_range(self): for rec in self: if not (0 <= rec.access_start_time <= 23.99): raise ValidationError(_("L'heure de début doit être comprise entre 00:00 et 23:59.")) if not (0 <= rec.access_end_time <= 23.99): raise ValidationError(_("L'heure de fin doit être comprise entre 00:00 et 23:59.")) if rec.access_start_time >= rec.access_end_time: raise ValidationError(_("L'heure de début doit être inférieure à l'heure de fin.")) def _check_credentials(self, password): """Override to restrict login based on time.""" # 先验证密码 super()._check_credentials(password) # 管理员豁免 if self.env.user._is_admin(): return now = datetime.now().time() current_total_minutes = now.hour * 60 + now.minute # 转换开始时间为分钟总数 start = self.env.user.access_start_time or 0.0 start_hour = int(start) start_minute = round((start - start_hour) * 60) start_total_minutes = start_hour * 60 + start_minute # 转换结束时间为分钟总数 end = self.env.user.access_end_time or 23.99 end_hour = int(end) end_minute = round((end - end_hour) * 60) end_total_minutes = end_hour * 60 + end_minute # 时间范围判断 if not (start_total_minutes <= current_total_minutes <= end_total_minutes): start_str = f"{start_hour:02d}:{start_minute:02d}" end_str = f"{end_hour:02d}:{end_minute:02d}" raise AccessDenied(_( "⛔ Accès refusé ! Vos horaires autorisés sont de %s à %s." ) % (start_str, end_str))
方案二:修正登录控制器
继承原Home控制器,保留原有登录逻辑,仅在用户验证通过后添加时间限制判断:
from odoo import http, _ from odoo.http import request from datetime import datetime from odoo.addons.web.controllers.auth import Home class AuthControllerOverride(Home): @http.route('/web/login', type='http', auth="none") def web_login(self, redirect=None, **kw): # 调用父类方法获取基础响应和上下文 response = super().web_login(redirect=redirect, **kw) if request.httprequest.method == 'POST' and request.session.uid: user = request.env['res.users'].sudo().browse(request.session.uid) # 管理员豁免 if user._is_admin(): return response now = datetime.now().time() current_total_minutes = now.hour * 60 + now.minute # 转换开始时间为分钟总数 start = user.access_start_time or 0.0 start_hour = int(start) start_minute = round((start - start_hour) * 60) start_total_minutes = start_hour * 60 + start_minute # 转换结束时间为分钟总数 end = user.access_end_time or 23.99 end_hour = int(end) end_minute = round((end - end_hour) * 60) end_total_minutes = end_hour * 60 + end_minute # 时间范围判断 if not (start_total_minutes <= current_total_minutes <= end_total_minutes): request.session.logout() # 重新渲染登录页面并显示错误 return request.render('web.login', { 'error': _("⛔ Accès refusé : vous êtes hors de vos horaires autorisés (%s - %s).") % ( f"{start_hour:02d}:{start_minute:02d}", f"{end_hour:02d}:{end_minute:02d}", ) }) return response
额外注意事项
- 模块加载顺序:确保自定义模块依赖
web模块,避免控制器重写不生效。 - 时间字段验证:保留原有的
_check_valid_time_range约束,确保用户输入的时间在合法范围内。 - 测试场景:测试边界时间点(如08:00、18:00)以及管理员登录情况,确保逻辑覆盖全面。
内容的提问来源于stack exchange,提问作者Rahma Begag
相关产品推荐
相关产品推荐

