fix: resolve 9 critical bugs from full local testing

- Migrate 5 OWL components from deprecated useService("rpc") to useService("orm") for Odoo 19
- Fix _paginate() signature mismatch across 6 controllers (dual calling convention)
- Fix taxonomy API serializers referencing non-existent fields (icon, difficulty)
- Fix branding controller validate_token() called with wrong arguments
- Add .sudo() to exam template/question model access under auth='none'
- Restore missing encoach_core files (security groups, permissions seed, core models)
- Create encoach_api shared controller utilities module
- Add Entity list/form/search views and menu items
- Add Taxonomy (Subjects/Domains/Topics) views and menu items
- Fix deprecated XML group expand="0" patterns across 15 view files
- Remove stale model references from adaptive __init__.py and access CSV
- Update .gitignore to exclude local dev artifacts

Tested: 97% pass rate (64/66 tests) across UI navigation, CRUD, and API endpoints.
Made-with: Cursor
This commit is contained in:
Yamen Ahmad
2026-04-07 03:43:48 +04:00
parent 6c93c5d600
commit d98cd55b99
58 changed files with 1401 additions and 213 deletions

View File

@@ -7,6 +7,7 @@
'depends': ['encoach_core'],
'data': [
'security/ir.model.access.csv',
'views/taxonomy_views.xml',
'data/default_subjects.xml',
'data/math_it_taxonomy.xml',
],

View File

@@ -40,8 +40,8 @@ class EncoachTaxonomyController(http.Controller):
items.append({
'id': s.id,
'name': s.name,
'code': s.code,
'icon': s.icon or '',
'code': s.code or '',
'description': s.description or '',
'domain_count': len(s.domain_ids),
})
@@ -79,7 +79,6 @@ class EncoachTaxonomyController(http.Controller):
topics.append({
'id': t.id,
'name': t.name,
'difficulty': t.difficulty or '',
'description': t.description or '',
'learning_objectives': objectives,
})
@@ -92,8 +91,8 @@ class EncoachTaxonomyController(http.Controller):
tree.append({
'id': s.id,
'name': s.name,
'code': s.code,
'icon': s.icon or '',
'code': s.code or '',
'description': s.description or '',
'domains': domains,
})
@@ -134,8 +133,6 @@ class EncoachTaxonomyController(http.Controller):
if not code:
return _error_response('code is required for subjects', 400)
vals['code'] = code
if body.get('icon'):
vals['icon'] = body['icon']
else:
parent_id = body.get('parent_id')
if not parent_id:
@@ -145,8 +142,6 @@ class EncoachTaxonomyController(http.Controller):
)
vals[PARENT_FIELD_MAP[node_type]] = int(parent_id)
if node_type == 'topic' and body.get('difficulty'):
vals['difficulty'] = body['difficulty']
if node_type == 'learning_objective' and body.get('bloom_level'):
vals['bloom_level'] = body['bloom_level']
@@ -192,12 +187,8 @@ class EncoachTaxonomyController(http.Controller):
if node_type == 'subject':
if body.get('code'):
vals['code'] = body['code']
if 'icon' in body:
vals['icon'] = body.get('icon') or ''
if 'active' in body:
vals['active'] = bool(body['active'])
elif node_type == 'topic' and body.get('difficulty'):
vals['difficulty'] = body['difficulty']
elif node_type == 'learning_objective' and body.get('bloom_level'):
vals['bloom_level'] = body['bloom_level']

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="subject_english" model="encoach.subject">
<field name="name">English</field>
<field name="code">ENG</field>
<field name="description">English Language</field>
<field name="sequence">1</field>
</record>
<record id="subject_math" model="encoach.subject">
<field name="name">Mathematics</field>
<field name="code">MATH</field>
<field name="description">Mathematics</field>
<field name="sequence">2</field>
</record>
<record id="subject_it" model="encoach.subject">
<field name="name">Information Technology</field>
<field name="code">IT</field>
<field name="description">Information Technology and Computer Science</field>
<field name="sequence">3</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,4 @@
from . import subject
from . import domain
from . import topic
from . import learning_objective

View File

@@ -0,0 +1,25 @@
from odoo import models, fields
class EncoachDomain(models.Model):
_name = 'encoach.domain'
_description = 'Taxonomy Domain'
_order = 'sequence, name'
name = fields.Char(required=True, index=True)
subject_id = fields.Many2one('encoach.subject', required=True, ondelete='cascade', index=True)
description = fields.Text()
sequence = fields.Integer(default=10)
active = fields.Boolean(default=True)
topic_ids = fields.One2many('encoach.topic', 'domain_id', string='Topics')
def to_api_dict(self):
self.ensure_one()
return {
'id': self.id,
'name': self.name,
'subject_id': self.subject_id.id,
'subject_name': self.subject_id.name,
'description': self.description or '',
'active': self.active,
}

View File

@@ -0,0 +1,29 @@
from odoo import models, fields
class EncoachLearningObjective(models.Model):
_name = 'encoach.learning.objective'
_description = 'Taxonomy Learning Objective'
_order = 'sequence, name'
name = fields.Char(required=True, index=True)
topic_id = fields.Many2one('encoach.topic', required=True, ondelete='cascade', index=True)
description = fields.Text()
cefr_level = fields.Selection([
('pre_a1', 'Pre-A1'), ('a1', 'A1'), ('a2', 'A2'),
('b1', 'B1'), ('b2', 'B2'), ('c1', 'C1'), ('c2', 'C2'),
])
sequence = fields.Integer(default=10)
active = fields.Boolean(default=True)
def to_api_dict(self):
self.ensure_one()
return {
'id': self.id,
'name': self.name,
'topic_id': self.topic_id.id,
'topic_name': self.topic_id.name,
'description': self.description or '',
'cefr_level': self.cefr_level or '',
'active': self.active,
}

View File

@@ -0,0 +1,24 @@
from odoo import models, fields
class EncoachSubject(models.Model):
_name = 'encoach.subject'
_description = 'Taxonomy Subject'
_order = 'sequence, name'
name = fields.Char(required=True, index=True)
code = fields.Char(size=20, index=True)
description = fields.Text()
sequence = fields.Integer(default=10)
active = fields.Boolean(default=True)
domain_ids = fields.One2many('encoach.domain', 'subject_id', string='Domains')
def to_api_dict(self):
self.ensure_one()
return {
'id': self.id,
'name': self.name,
'code': self.code or '',
'description': self.description or '',
'active': self.active,
}

View File

@@ -0,0 +1,25 @@
from odoo import models, fields
class EncoachTopic(models.Model):
_name = 'encoach.topic'
_description = 'Taxonomy Topic'
_order = 'sequence, name'
name = fields.Char(required=True, index=True)
domain_id = fields.Many2one('encoach.domain', required=True, ondelete='cascade', index=True)
description = fields.Text()
sequence = fields.Integer(default=10)
active = fields.Boolean(default=True)
learning_objective_ids = fields.One2many('encoach.learning.objective', 'topic_id', string='Learning Objectives')
def to_api_dict(self):
self.ensure_one()
return {
'id': self.id,
'name': self.name,
'domain_id': self.domain_id.id,
'domain_name': self.domain_id.name,
'description': self.description or '',
'active': self.active,
}

View File

@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_encoach_subject_all,encoach.subject.all,model_encoach_subject,base.group_user,1,1,1,1
access_encoach_domain_all,encoach.domain.all,model_encoach_domain,base.group_user,1,1,1,1
access_encoach_topic_all,encoach.topic.all,model_encoach_topic,base.group_user,1,1,1,1
access_encoach_learning_objective_all,encoach.learning.objective.all,model_encoach_learning_objective,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_encoach_subject_all encoach.subject.all model_encoach_subject base.group_user 1 1 1 1
3 access_encoach_domain_all encoach.domain.all model_encoach_domain base.group_user 1 1 1 1
4 access_encoach_topic_all encoach.topic.all model_encoach_topic base.group_user 1 1 1 1
5 access_encoach_learning_objective_all encoach.learning.objective.all model_encoach_learning_objective base.group_user 1 1 1 1

View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Subject Views -->
<record id="view_encoach_subject_list" model="ir.ui.view">
<field name="name">encoach.subject.list</field>
<field name="model">encoach.subject</field>
<field name="arch" type="xml">
<list string="Subjects" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="code"/>
<field name="active" column_invisible="True"/>
</list>
</field>
</record>
<record id="view_encoach_subject_form" model="ir.ui.view">
<field name="name">encoach.subject.form</field>
<field name="model">encoach.subject</field>
<field name="arch" type="xml">
<form string="Subject">
<sheet>
<div class="oe_title">
<h1><field name="name" placeholder="Subject Name"/></h1>
</div>
<group>
<group>
<field name="code"/>
<field name="sequence"/>
<field name="active"/>
</group>
<group>
<field name="description"/>
</group>
</group>
<notebook>
<page string="Domains">
<field name="domain_ids">
<list>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="description"/>
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_encoach_subject" model="ir.actions.act_window">
<field name="name">Subjects</field>
<field name="res_model">encoach.subject</field>
<field name="view_mode">list,form</field>
</record>
<!-- Domain Views -->
<record id="view_encoach_domain_list" model="ir.ui.view">
<field name="name">encoach.domain.list</field>
<field name="model">encoach.domain</field>
<field name="arch" type="xml">
<list string="Domains">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="subject_id"/>
<field name="description"/>
</list>
</field>
</record>
<record id="view_encoach_domain_form" model="ir.ui.view">
<field name="name">encoach.domain.form</field>
<field name="model">encoach.domain</field>
<field name="arch" type="xml">
<form string="Domain">
<sheet>
<div class="oe_title">
<h1><field name="name" placeholder="Domain Name"/></h1>
</div>
<group>
<group>
<field name="subject_id"/>
<field name="sequence"/>
</group>
<group>
<field name="description"/>
</group>
</group>
<notebook>
<page string="Topics">
<field name="topic_ids">
<list>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="description"/>
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_encoach_domain" model="ir.actions.act_window">
<field name="name">Domains</field>
<field name="res_model">encoach.domain</field>
<field name="view_mode">list,form</field>
</record>
<!-- Topic Views -->
<record id="view_encoach_topic_list" model="ir.ui.view">
<field name="name">encoach.topic.list</field>
<field name="model">encoach.topic</field>
<field name="arch" type="xml">
<list string="Topics">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="domain_id"/>
<field name="description"/>
</list>
</field>
</record>
<record id="action_encoach_topic" model="ir.actions.act_window">
<field name="name">Topics</field>
<field name="res_model">encoach.topic</field>
<field name="view_mode">list,form</field>
</record>
<!-- Menu Items under Content -->
<menuitem id="menu_taxonomy_subjects"
name="Subjects"
parent="encoach_core.menu_encoach_content"
action="action_encoach_subject"
sequence="10"/>
<menuitem id="menu_taxonomy_domains"
name="Domains"
parent="encoach_core.menu_encoach_content"
action="action_encoach_domain"
sequence="11"/>
<menuitem id="menu_taxonomy_topics"
name="Topics"
parent="encoach_core.menu_encoach_content"
action="action_encoach_topic"
sequence="12"/>
</odoo>