EnCoach Odoo 19 custom modules
Full backend implementation with custom Odoo modules: - encoach_api: Core API, user management, JWT auth - encoach_exam: Exam generation (reading, writing, listening, speaking) - encoach_evaluate: AI-powered evaluation (writing, speaking) - encoach_training: Training tips and walkthrough - encoach_storage: File storage management - encoach_payment: Stripe, PayPal, Paymob integration - encoach_mail: Email notifications Made-with: Cursor
This commit is contained in:
1
encoach_stats/__init__.py
Normal file
1
encoach_stats/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
15
encoach_stats/__manifest__.py
Normal file
15
encoach_stats/__manifest__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "EnCoach Stats",
|
||||
"version": "19.0.1.0.0",
|
||||
"category": "Education",
|
||||
"summary": "Exam sessions and statistics tracking",
|
||||
"depends": ["encoach_core", "encoach_exam"],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"security/rules.xml",
|
||||
"views/session_views.xml",
|
||||
"views/stat_views.xml",
|
||||
],
|
||||
"installable": True,
|
||||
"license": "LGPL-3",
|
||||
}
|
||||
2
encoach_stats/models/__init__.py
Normal file
2
encoach_stats/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import session
|
||||
from . import stat
|
||||
21
encoach_stats/models/session.py
Normal file
21
encoach_stats/models/session.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class EncoachSession(models.Model):
|
||||
_name = "encoach.session"
|
||||
_description = "EnCoach Exam Session"
|
||||
_order = "date desc"
|
||||
|
||||
user_id = fields.Many2one("res.users", required=True, index=True)
|
||||
exam_data = fields.Json(help="Full exam state snapshot (ExamState)")
|
||||
date = fields.Datetime(default=fields.Datetime.now)
|
||||
legacy_id = fields.Char(index=True)
|
||||
|
||||
def to_encoach_dict(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"id": self.id,
|
||||
"user": self.user_id.id,
|
||||
"date": self.date.isoformat() if self.date else None,
|
||||
**(self.exam_data or {}),
|
||||
}
|
||||
67
encoach_stats/models/stat.py
Normal file
67
encoach_stats/models/stat.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from odoo import models, fields
|
||||
|
||||
MODULE_SELECTION = [
|
||||
("reading", "Reading"),
|
||||
("listening", "Listening"),
|
||||
("writing", "Writing"),
|
||||
("speaking", "Speaking"),
|
||||
("level", "Level"),
|
||||
]
|
||||
|
||||
|
||||
class EncoachStat(models.Model):
|
||||
_name = "encoach.stat"
|
||||
_description = "EnCoach Exam Statistic"
|
||||
_order = "date desc"
|
||||
|
||||
user_id = fields.Many2one("res.users", required=True, index=True)
|
||||
exam_id = fields.Many2one("encoach.exam", index=True)
|
||||
exercise = fields.Char()
|
||||
session_id = fields.Many2one("encoach.session", index=True)
|
||||
date = fields.Datetime(default=fields.Datetime.now)
|
||||
module = fields.Selection(MODULE_SELECTION, index=True)
|
||||
solutions = fields.Json(help="User solution objects for this stat")
|
||||
stat_type = fields.Char(string="Type")
|
||||
time_spent = fields.Integer(help="Seconds spent on this exercise")
|
||||
inactivity = fields.Integer(help="Seconds of inactivity")
|
||||
assignment_id = fields.Many2one("encoach.assignment", index=True)
|
||||
score_correct = fields.Integer(default=0)
|
||||
score_total = fields.Integer(default=0)
|
||||
score_missing = fields.Integer(default=0)
|
||||
is_disabled = fields.Boolean(default=False)
|
||||
shuffle_maps = fields.Json()
|
||||
is_practice = fields.Boolean(default=False)
|
||||
pdf_url = fields.Char()
|
||||
legacy_id = fields.Char(index=True)
|
||||
|
||||
def to_encoach_dict(self):
|
||||
self.ensure_one()
|
||||
result = {
|
||||
"id": self.id,
|
||||
"user": self.user_id.id,
|
||||
"exam": self.exam_id.id if self.exam_id else None,
|
||||
"exercise": self.exercise or "",
|
||||
"session": self.session_id.id if self.session_id else None,
|
||||
"date": (
|
||||
int(self.date.timestamp() * 1000) if self.date else None
|
||||
),
|
||||
"module": self.module,
|
||||
"solutions": self.solutions or [],
|
||||
"type": self.stat_type or "",
|
||||
"timeSpent": self.time_spent,
|
||||
"inactivity": self.inactivity,
|
||||
"assignment": (
|
||||
self.assignment_id.id if self.assignment_id else None
|
||||
),
|
||||
"score": {
|
||||
"correct": self.score_correct,
|
||||
"total": self.score_total,
|
||||
"missing": self.score_missing,
|
||||
},
|
||||
"isDisabled": self.is_disabled,
|
||||
"shuffleMaps": self.shuffle_maps or [],
|
||||
"isPractice": self.is_practice,
|
||||
}
|
||||
if self.pdf_url:
|
||||
result["pdf"] = {"path": self.pdf_url, "version": "1"}
|
||||
return result
|
||||
9
encoach_stats/security/ir.model.access.csv
Normal file
9
encoach_stats/security/ir.model.access.csv
Normal file
@@ -0,0 +1,9 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_encoach_session_student,encoach.session.student,model_encoach_session,encoach_core.group_encoach_student,1,1,1,0
|
||||
access_encoach_session_teacher,encoach.session.teacher,model_encoach_session,encoach_core.group_encoach_teacher,1,1,1,0
|
||||
access_encoach_session_admin,encoach.session.admin,model_encoach_session,encoach_core.group_encoach_admin,1,1,1,1
|
||||
access_encoach_stat_student,encoach.stat.student,model_encoach_stat,encoach_core.group_encoach_student,1,1,1,0
|
||||
access_encoach_stat_teacher,encoach.stat.teacher,model_encoach_stat,encoach_core.group_encoach_teacher,1,1,1,0
|
||||
access_encoach_stat_admin,encoach.stat.admin,model_encoach_stat,encoach_core.group_encoach_admin,1,1,1,1
|
||||
access_encoach_session_sysadmin,encoach.session.sysadmin,model_encoach_session,base.group_system,1,1,1,1
|
||||
access_encoach_stat_sysadmin,encoach.stat.sysadmin,model_encoach_stat,base.group_system,1,1,1,1
|
||||
|
29
encoach_stats/security/rules.xml
Normal file
29
encoach_stats/security/rules.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="rule_session_student_own" model="ir.rule">
|
||||
<field name="name">Students see own sessions</field>
|
||||
<field name="model_id" ref="model_encoach_session"/>
|
||||
<field name="groups" eval="[(4, ref('encoach_core.group_encoach_student'))]"/>
|
||||
<field name="domain_force">[('user_id', '=', user.id)]</field>
|
||||
</record>
|
||||
<record id="rule_session_teacher" model="ir.rule">
|
||||
<field name="name">Teachers see all sessions</field>
|
||||
<field name="model_id" ref="model_encoach_session"/>
|
||||
<field name="groups" eval="[(4, ref('encoach_core.group_encoach_teacher'))]"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
</record>
|
||||
<record id="rule_stat_student_own" model="ir.rule">
|
||||
<field name="name">Students see own stats</field>
|
||||
<field name="model_id" ref="model_encoach_stat"/>
|
||||
<field name="groups" eval="[(4, ref('encoach_core.group_encoach_student'))]"/>
|
||||
<field name="domain_force">[('user_id', '=', user.id)]</field>
|
||||
</record>
|
||||
<record id="rule_stat_teacher" model="ir.rule">
|
||||
<field name="name">Teachers see all stats</field>
|
||||
<field name="model_id" ref="model_encoach_stat"/>
|
||||
<field name="groups" eval="[(4, ref('encoach_core.group_encoach_teacher'))]"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
43
encoach_stats/views/session_views.xml
Normal file
43
encoach_stats/views/session_views.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id="view_encoach_session_list" model="ir.ui.view">
|
||||
<field name="name">encoach.session.list</field>
|
||||
<field name="model">encoach.session</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Sessions">
|
||||
<field name="user_id"/>
|
||||
<field name="date"/>
|
||||
<field name="legacy_id"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_encoach_session_form" model="ir.ui.view">
|
||||
<field name="name">encoach.session.form</field>
|
||||
<field name="model">encoach.session</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Session">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="user_id"/>
|
||||
<field name="date"/>
|
||||
<field name="legacy_id"/>
|
||||
<field name="exam_data" widget="json"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_encoach_session" model="ir.actions.act_window">
|
||||
<field name="name">Sessions</field>
|
||||
<field name="res_model">encoach.session</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_encoach_session"
|
||||
name="Sessions"
|
||||
parent="encoach_core.menu_encoach_classroom"
|
||||
action="action_encoach_session"
|
||||
sequence="10"/>
|
||||
</odoo>
|
||||
67
encoach_stats/views/stat_views.xml
Normal file
67
encoach_stats/views/stat_views.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id="view_encoach_stat_list" model="ir.ui.view">
|
||||
<field name="name">encoach.stat.list</field>
|
||||
<field name="model">encoach.stat</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Statistics">
|
||||
<field name="user_id"/>
|
||||
<field name="module"/>
|
||||
<field name="exercise"/>
|
||||
<field name="exam_id"/>
|
||||
<field name="score_correct"/>
|
||||
<field name="score_total"/>
|
||||
<field name="date"/>
|
||||
<field name="time_spent"/>
|
||||
<field name="is_practice"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_encoach_stat_form" model="ir.ui.view">
|
||||
<field name="name">encoach.stat.form</field>
|
||||
<field name="model">encoach.stat</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Statistic">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="user_id"/>
|
||||
<field name="module"/>
|
||||
<field name="exercise"/>
|
||||
<field name="exam_id"/>
|
||||
<field name="session_id"/>
|
||||
<field name="assignment_id"/>
|
||||
<field name="date"/>
|
||||
<field name="stat_type"/>
|
||||
<field name="time_spent"/>
|
||||
<field name="inactivity"/>
|
||||
<field name="is_disabled"/>
|
||||
<field name="is_practice"/>
|
||||
<field name="pdf_url"/>
|
||||
</group>
|
||||
<group string="Score">
|
||||
<field name="score_correct"/>
|
||||
<field name="score_total"/>
|
||||
<field name="score_missing"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="solutions" widget="json"/>
|
||||
<field name="shuffle_maps" widget="json"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_encoach_stat" model="ir.actions.act_window">
|
||||
<field name="name">Statistics</field>
|
||||
<field name="res_model">encoach.stat</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_encoach_stat"
|
||||
name="Statistics"
|
||||
parent="encoach_core.menu_encoach_classroom"
|
||||
action="action_encoach_stat"
|
||||
sequence="20"/>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user