You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题原因分析

  1. _check_credentials上下文错误:调用该方法时,self是空的res.users模型记录集,self.browse(self._uid)无法正确加载用户的时间字段值,导致条件判断失效。
  2. 控制器逻辑缺陷:直接返回request.redirect_with_hash('/web')跳过了原登录页面的渲染流程,且未处理authenticate返回False的情况,导致时间限制逻辑未被触发。
  3. 浮点时间精度问题:使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 04:15:54