Odoo17中Controller、JS、XML及OWL的通信连接与数据存储问题
考勤表单开发疑问与代码解析
需求背景
正在开发一个基础考勤表单,核心需求:
- 切换班级时自动加载对应学生信息到表格
- 可标记学生出勤/缺勤状态并提交数据
已完成Controller、main.js、XML模板及Manifest.py的编写,但未使用OWL组件,希望了解:
- Odoo17中OWL、JavaScript、XML模板三者的通信机制
- 前端数据如何存储到数据库的完整流程
已编写代码
控制器(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维护内部状态,模板直接绑定这些数据实现双向同步。 - 通信逻辑:
- 组件初始化时,可通过
setup方法发起RPC请求获取初始数据,存入state。 - 用户交互触发组件方法(如点击、选择),方法修改
state或发起RPC,模板自动更新。 - 模板中的指令(如
t-on-click)直接绑定组件方法,无需手动绑定事件监听。
- 组件初始化时,可通过
对比你的当前实现:jQuery是命令式操作DOM,而OWL是声明式绑定,代码更简洁、易维护,且原生支持Odoo的前端生态(如国际化、权限控制)。
2. 数据存储到数据库的完整流程
以你的考勤提交功能为例,流程如下:
前端数据收集:
- JS监听提交按钮点击,收集班级ID、日期、学生出勤状态,整理成结构化数据(
studentAttendances数组)。 - 通过
ajax.rpc调用后端路由/submit_attendance,将数据以JSON格式发送。
- JS监听提交按钮点击,收集班级ID、日期、学生出勤状态,整理成结构化数据(
后端路由处理:
- Controller的
submit_attendance方法接收前端参数,通过Odoo ORM操作数据库:- 调用
Attendance.create()创建考勤主记录,ORM自动生成INSERT语句插入student_attendance表。 - 循环调用
AttendanceLine.create()创建每条学生的考勤明细,插入student_attendance_line表,通过attendance_id关联主记录。
- 调用
- Controller的
ORM与数据库交互:
- Odoo ORM会处理字段验证、权限检查、自动填充(如创建时间、创建者),最终将转换后的SQL语句发送给PostgreSQL数据库执行。
响应返回与前端处理:
- 后端返回
success: True的JSON响应,前端收到后弹出提示,清空表单内容。
- 后端返回
内容的提问来源于stack exchange,提问作者Raihan
相关产品推荐
相关产品推荐

