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

Odoo17中Controller、JS、XML及OWL的通信连接与数据存储问题

考勤表单开发疑问与代码解析

需求背景

正在开发一个基础考勤表单,核心需求:

  • 切换班级时自动加载对应学生信息到表格
  • 可标记学生出勤/缺勤状态并提交数据

已完成Controller、main.js、XML模板及Manifest.py的编写,但未使用OWL组件,希望了解:

  1. Odoo17中OWL、JavaScript、XML模板三者的通信机制
  2. 前端数据如何存储到数据库的完整流程

已编写代码

控制器(Controller)

class StudentAttendanceController(http.Controller):
    @http.route('/attendance_form', type='http', auth="public", website=True)
    def attendance_form(self, **kw):
        return request.render("aspire_student_attendance.attendance_form_template", {'today': fields.Date.today()})

    @http.route('/submit_attendance', type='json', auth="user")
    def submit_attendance(self, **kw):
        class_division_id = kw.get('class_division_id')
        date = kw.get('date')
        student_attendances = kw.get(
            'student_attendances')  # 预期是包含student_id和attendance_status的字典列表

        Attendance = request.env['student.attendance']
        AttendanceLine = request.env['student.attendance.line']
        faculty_id = request.env.user.id  # 当前用户ID(教师)

        # 创建考勤主记录
        attendance_record = Attendance.create({
            'date': date,
            'class_division_id': class_division_id,
            'faculty_id': faculty_id,
            'attendance_status': 'draft',  # 初始状态为草稿
        })
        # 批量创建学生考勤明细
        for student_attendance in student_attendances:
            AttendanceLine.create({
                'attendance_id': attendance_record.id,
                'student_id': student_attendance['student_id'],
                'attendance_status': student_attendance['attendance_status'],
            })

        return {'success': True, 'message': '考勤记录提交成功'}

    @http.route('/get_students', type='json', auth='user')
    def get_students(self, **kwargs):
        # 注意:此处硬编码了班级ID=2,应改为使用传入的class_division_id参数
        students = request.env['school.class.division'].browse([2]).student_ids
        student_data = [{'student_id': student.id, 'student_name': student.name} for student in students]
        print('student data', student_data)
        return student_data

前端逻辑(main.js)

odoo.define('aspire_student_attendance.attendance_form', ['web.ajax'], function (require) {
        "use strict";

        var ajax = require('web.ajax');
        const core = require('web.core');
        var _t = core._t;
        $(document).ready(function () {
            // 班级选择变化时加载学生
            $('select[name="class_division_id"]').on('change', function () {
                const classId = $(this).val();
                if (classId) {
                    ajax.rpc({route: "/get_students",
                              params: {class_division_id: classId}}).then(function (response) {
                            const studentList = $('#students_list');
                            studentList.empty();
                            $.each(response, function (index, student) {
                                const studentEntry = $('<div>')
                                    .addClass('student_attendance_input')
                                    .text(student.student_name)
                                    .data('student-id', student.student_id);
                                $('<input>', {
                                    type: 'checkbox',
                                    name: `attendance[${student.student_id}]`,
                                    value: 'present'
                                }).appendTo(studentEntry);

                                studentList.append(`<tr><td>${studentEntry.text()}</td><td>${studentEntry.find('input')[0].outerHTML}</td></tr>`);
                            });
                        }).catch(function (error) {
                            console.error("加载学生失败:", error);
                        });
                } else {
                    $('#students_list').empty();
                }
            });
            // 提交考勤
            $('button[name="submit_button"]').on('click', function (e) {
                e.preventDefault();
                const classId = $('select[name="class_division_id"]').val();
                const date = $('input[name="date"]').val();
                const studentAttendances = [];

                $('.student_attendance_input input[type="checkbox"]').each(function () {
                    const studentId = $(this).closest('.student_attendance_input').data('student-id');
                    const attendanceStatus = $(this).is(':checked') ? 'present' : 'absent';
                    studentAttendances.push({
                        student_id: studentId,
                        attendance_status: attendanceStatus,
                    });
                });

                if (!classId || !date || studentAttendances.length === 0) {
                    alert("请选择班级、确认日期,并标记学生出勤状态。");
                    return;
                }
                ajax.rpc('/submit_attendance', {
                    class_division_id: classId,
                    date: date,
                    student_attendances: studentAttendances,
                }).then(function (result) {
                    if (result.success) {
                        alert(result.message);
                        $('#students_list').empty();
                        $('select[name="class_division_id"]').val('');
                        $('input[name="date"]').val('');
                    }
                }).catch(function (error) {
                    console.error("提交考勤失败:", error);
                });
            });
        });
    });

XML模板

<template id="attendance_form_template" name="学生考勤表单">
    <t t-call="website.layout">
        <div class="container">
            <h1>学生考勤</h1>
            <div class="attendance-header">
                <h2>状态: <span t-esc="attendance_status"/></h2>
            </div>
            <div class="faculty-info">
                <span>教师: <span t-esc="faculty_name"/></span>
                <span>日期: <input type="date" id="date" name="date" class="form-control" t-att-value="today" readonly="readonly"/></span>
                <span>班级: <select id="class_select" name="class_division_id" class="form-control" required="required">
                    <t t-foreach="request.env['school.class.division'].sudo().search([])" t-as="class">
                        <option t-att-value="class.id"><t t-esc="class.name"/></option>
                    </t>
                </select></span>
            </div>
            <table class="table">
                <thead>
                    <tr>
                        <th>学生姓名</th>
                        <th>出勤状态</th>
                    </tr>
                </thead>
                <tbody id="students_list">
                    <!-- 学生信息将通过AJAX加载到这里 -->
                </tbody>
            </table>
            <button name="submit_button" type="submit" class="btn btn-primary">提交考勤</button>
        </div>
    </t>
</template>

模块配置(Manifest.py)

{
    'name': '学校管理系统',
    'version': '17.0.1.0.0',
    'category': '学校管理',
    'summary': """扩展学校管理功能""",
    'author': 'AAAA',
    'company': 'AAAA',
    'website': "",
    'depends': ['base', 'web', 'website'],
    'data': [
        'security/ir.model.access.csv',
        'views/student_attendance_views.xml',
        'views/student_attendance_form.xml',
        'views/home.xml',
    ],
    'assets': {
        'web.assets_frontend': [
            'aspire_student_attendance/static/src/student_attendance_form/**/*',
        ],
    },
    'installable': True,
    'auto_install': False,
    'application': True,
    'license': 'AGPL-3',
}

核心问题解答

1. Odoo17中OWL、JavaScript、XML模板的通信机制

你的当前实现基于传统jQuery+AJAX模式,未使用Odoo17主推的OWL组件。OWL是Odoo的前端框架,三者的协作逻辑如下:

  • XML模板:作为OWL组件的视图层,使用t-*指令(如t-if、t-for、t-model)与组件的Props/State绑定,实现声明式渲染,无需手动操作DOM。模板会自动响应组件数据变化更新UI。
  • OWL组件(JavaScript):作为逻辑层,负责管理组件状态(state)、处理用户交互(事件方法)、与后端通信(rpc调用)。组件通过props接收外部数据,通过state维护内部状态,模板直接绑定这些数据实现双向同步。
  • 通信逻辑:
    1. 组件初始化时,可通过setup方法发起RPC请求获取初始数据,存入state。
    2. 用户交互触发组件方法(如点击、选择),方法修改state或发起RPC,模板自动更新。
    3. 模板中的指令(如t-on-click)直接绑定组件方法,无需手动绑定事件监听。

对比你的当前实现:jQuery是命令式操作DOM,而OWL是声明式绑定,代码更简洁、易维护,且原生支持Odoo的前端生态(如国际化、权限控制)。

2. 数据存储到数据库的完整流程

以你的考勤提交功能为例,流程如下:

  1. 前端数据收集:

    • JS监听提交按钮点击,收集班级ID、日期、学生出勤状态,整理成结构化数据(studentAttendances数组)。
    • 通过ajax.rpc调用后端路由/submit_attendance,将数据以JSON格式发送。
  2. 后端路由处理:

    • Controller的submit_attendance方法接收前端参数,通过Odoo ORM操作数据库:
      • 调用Attendance.create()创建考勤主记录,ORM自动生成INSERT语句插入student_attendance表。
      • 循环调用AttendanceLine.create()创建每条学生的考勤明细,插入student_attendance_line表,通过attendance_id关联主记录。
  3. ORM与数据库交互:

    • Odoo ORM会处理字段验证、权限检查、自动填充(如创建时间、创建者),最终将转换后的SQL语句发送给PostgreSQL数据库执行。
  4. 响应返回与前端处理:

    • 后端返回success: True的JSON响应,前端收到后弹出提示,清空表单内容。

内容的提问来源于stack exchange,提问作者Raihan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 16:54:52