feat: initial backend codebase — EnCoach v3
Complete Odoo 19 backend with 25 custom addons: - encoach_core: user/entity/role management - encoach_api: REST API + JWT auth - encoach_ai: OpenAI integration, AI settings, generation - encoach_ai_course: AI-powered English & IELTS course generation - encoach_exam_template/session: exam creation, structures, sessions - encoach_scoring: AI auto-grading + manual approval - encoach_vector: pgvector RAG integration - encoach_adaptive: adaptive learning engine - encoach_placement: placement testing - encoach_taxonomy/resources: content taxonomy & resource management - Plus 14 more modules for courses, branding, portal, etc. Includes docs: user guide, generation report, developer workflow. Made-with: Cursor
This commit is contained in:
2
custom_addons/encoach_taxonomy/__init__.py
Normal file
2
custom_addons/encoach_taxonomy/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import controllers
|
||||
16
custom_addons/encoach_taxonomy/__manifest__.py
Normal file
16
custom_addons/encoach_taxonomy/__manifest__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
'name': 'EnCoach Taxonomy',
|
||||
'version': '19.0.1.0',
|
||||
'category': 'Education',
|
||||
'summary': 'Subject taxonomy: subjects, domains, topics, learning objectives',
|
||||
'author': 'EnCoach',
|
||||
'depends': ['encoach_core'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/taxonomy_views.xml',
|
||||
'data/default_subjects.xml',
|
||||
'data/math_it_taxonomy.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
1
custom_addons/encoach_taxonomy/controllers/__init__.py
Normal file
1
custom_addons/encoach_taxonomy/controllers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import taxonomy
|
||||
230
custom_addons/encoach_taxonomy/controllers/taxonomy.py
Normal file
230
custom_addons/encoach_taxonomy/controllers/taxonomy.py
Normal file
@@ -0,0 +1,230 @@
|
||||
import json
|
||||
import logging
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.addons.encoach_api.controllers.base import (
|
||||
jwt_required, _json_response, _error_response, _get_json_body, _paginate
|
||||
)
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
TAXONOMY_MODELS = {
|
||||
'subject': 'encoach.subject',
|
||||
'domain': 'encoach.domain',
|
||||
'topic': 'encoach.topic',
|
||||
'learning_objective': 'encoach.learning.objective',
|
||||
}
|
||||
|
||||
PARENT_FIELD_MAP = {
|
||||
'domain': 'subject_id',
|
||||
'topic': 'domain_id',
|
||||
'learning_objective': 'topic_id',
|
||||
}
|
||||
|
||||
|
||||
class EncoachTaxonomyController(http.Controller):
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/taxonomy/subjects
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/api/taxonomy/subjects', type='http', auth='none',
|
||||
methods=['GET'], csrf=False)
|
||||
@jwt_required
|
||||
def get_subjects(self, **kw):
|
||||
try:
|
||||
Subject = request.env['encoach.subject'].sudo()
|
||||
subjects = Subject.search([('active', '=', True)])
|
||||
|
||||
items = []
|
||||
for s in subjects:
|
||||
items.append({
|
||||
'id': s.id,
|
||||
'name': s.name,
|
||||
'code': s.code or '',
|
||||
'description': s.description or '',
|
||||
'domain_count': len(s.domain_ids),
|
||||
})
|
||||
|
||||
return _json_response({'subjects': items})
|
||||
|
||||
except Exception as e:
|
||||
_logger.exception('get_subjects failed')
|
||||
return _error_response(str(e), 500)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/taxonomy/tree
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/api/taxonomy/tree', type='http', auth='none',
|
||||
methods=['GET'], csrf=False)
|
||||
@jwt_required
|
||||
def get_tree(self, **kw):
|
||||
try:
|
||||
Subject = request.env['encoach.subject'].sudo()
|
||||
subjects = Subject.search([('active', '=', True)])
|
||||
|
||||
tree = []
|
||||
for s in subjects:
|
||||
domains = []
|
||||
for d in s.domain_ids:
|
||||
topics = []
|
||||
for t in d.topic_ids:
|
||||
objectives = []
|
||||
for o in t.learning_objective_ids:
|
||||
objectives.append({
|
||||
'id': o.id,
|
||||
'name': o.name,
|
||||
'bloom_level': o.bloom_level or '',
|
||||
'description': o.description or '',
|
||||
})
|
||||
topics.append({
|
||||
'id': t.id,
|
||||
'name': t.name,
|
||||
'description': t.description or '',
|
||||
'learning_objectives': objectives,
|
||||
})
|
||||
domains.append({
|
||||
'id': d.id,
|
||||
'name': d.name,
|
||||
'description': d.description or '',
|
||||
'topics': topics,
|
||||
})
|
||||
tree.append({
|
||||
'id': s.id,
|
||||
'name': s.name,
|
||||
'code': s.code or '',
|
||||
'description': s.description or '',
|
||||
'domains': domains,
|
||||
})
|
||||
|
||||
return _json_response({'tree': tree})
|
||||
|
||||
except Exception as e:
|
||||
_logger.exception('get_tree failed')
|
||||
return _error_response(str(e), 500)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /api/taxonomy/node
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/api/taxonomy/node', type='http', auth='none',
|
||||
methods=['POST'], csrf=False)
|
||||
@jwt_required
|
||||
def create_node(self, **kw):
|
||||
try:
|
||||
body = _get_json_body()
|
||||
node_type = body.get('type')
|
||||
if not node_type or node_type not in TAXONOMY_MODELS:
|
||||
return _error_response(
|
||||
f'type must be one of: {", ".join(TAXONOMY_MODELS.keys())}', 400,
|
||||
)
|
||||
|
||||
name = body.get('name')
|
||||
if not name:
|
||||
return _error_response('name is required', 400)
|
||||
|
||||
model_name = TAXONOMY_MODELS[node_type]
|
||||
Model = request.env[model_name].sudo()
|
||||
|
||||
vals = {'name': name}
|
||||
if body.get('description'):
|
||||
vals['description'] = body['description']
|
||||
|
||||
if node_type == 'subject':
|
||||
code = body.get('code')
|
||||
if not code:
|
||||
return _error_response('code is required for subjects', 400)
|
||||
vals['code'] = code
|
||||
else:
|
||||
parent_id = body.get('parent_id')
|
||||
if not parent_id:
|
||||
parent_field = PARENT_FIELD_MAP[node_type]
|
||||
return _error_response(
|
||||
f'parent_id ({parent_field}) is required for {node_type}', 400,
|
||||
)
|
||||
vals[PARENT_FIELD_MAP[node_type]] = int(parent_id)
|
||||
|
||||
if node_type == 'learning_objective' and body.get('bloom_level'):
|
||||
vals['bloom_level'] = body['bloom_level']
|
||||
|
||||
record = Model.create(vals)
|
||||
|
||||
return _json_response({
|
||||
'id': record.id,
|
||||
'type': node_type,
|
||||
'name': record.name,
|
||||
}, 201)
|
||||
|
||||
except Exception as e:
|
||||
_logger.exception('create_node failed')
|
||||
return _error_response(str(e), 500)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# PUT /api/taxonomy/node/<int:node_id>
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/api/taxonomy/node/<int:node_id>', type='http', auth='none',
|
||||
methods=['PUT'], csrf=False)
|
||||
@jwt_required
|
||||
def update_node(self, node_id, **kw):
|
||||
try:
|
||||
body = _get_json_body()
|
||||
node_type = body.get('type')
|
||||
if not node_type or node_type not in TAXONOMY_MODELS:
|
||||
return _error_response(
|
||||
f'type must be one of: {", ".join(TAXONOMY_MODELS.keys())}', 400,
|
||||
)
|
||||
|
||||
model_name = TAXONOMY_MODELS[node_type]
|
||||
Model = request.env[model_name].sudo()
|
||||
record = Model.browse(node_id)
|
||||
if not record.exists():
|
||||
return _error_response(f'{node_type} not found', 404)
|
||||
|
||||
vals = {}
|
||||
if body.get('name'):
|
||||
vals['name'] = body['name']
|
||||
if 'description' in body:
|
||||
vals['description'] = body.get('description') or ''
|
||||
|
||||
if node_type == 'subject':
|
||||
if body.get('code'):
|
||||
vals['code'] = body['code']
|
||||
if 'active' in body:
|
||||
vals['active'] = bool(body['active'])
|
||||
elif node_type == 'learning_objective' and body.get('bloom_level'):
|
||||
vals['bloom_level'] = body['bloom_level']
|
||||
|
||||
if vals:
|
||||
record.write(vals)
|
||||
|
||||
return _json_response(record.to_api_dict())
|
||||
|
||||
except Exception as e:
|
||||
_logger.exception('update_node failed')
|
||||
return _error_response(str(e), 500)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# DELETE /api/taxonomy/node/<int:node_id>
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/api/taxonomy/node/<int:node_id>', type='http', auth='none',
|
||||
methods=['DELETE'], csrf=False)
|
||||
@jwt_required
|
||||
def delete_node(self, node_id, **kw):
|
||||
try:
|
||||
body = _get_json_body()
|
||||
node_type = body.get('type') or kw.get('type')
|
||||
if not node_type or node_type not in TAXONOMY_MODELS:
|
||||
return _error_response(
|
||||
f'type must be one of: {", ".join(TAXONOMY_MODELS.keys())}', 400,
|
||||
)
|
||||
|
||||
model_name = TAXONOMY_MODELS[node_type]
|
||||
Model = request.env[model_name].sudo()
|
||||
record = Model.browse(node_id)
|
||||
if not record.exists():
|
||||
return _error_response(f'{node_type} not found', 404)
|
||||
|
||||
record.unlink()
|
||||
return _json_response({}, 204)
|
||||
|
||||
except Exception as e:
|
||||
_logger.exception('delete_node failed')
|
||||
return _error_response(str(e), 500)
|
||||
23
custom_addons/encoach_taxonomy/data/default_subjects.xml
Normal file
23
custom_addons/encoach_taxonomy/data/default_subjects.xml
Normal 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>
|
||||
304
custom_addons/encoach_taxonomy/data/math_it_taxonomy.xml
Normal file
304
custom_addons/encoach_taxonomy/data/math_it_taxonomy.xml
Normal file
@@ -0,0 +1,304 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- ===================== ENGLISH DOMAINS & TOPICS ===================== -->
|
||||
<record id="domain_eng_listening" model="encoach.domain">
|
||||
<field name="name">Listening</field>
|
||||
<field name="subject_id" ref="subject_english"/>
|
||||
<field name="description">Listening comprehension skills</field>
|
||||
</record>
|
||||
<record id="domain_eng_reading" model="encoach.domain">
|
||||
<field name="name">Reading</field>
|
||||
<field name="subject_id" ref="subject_english"/>
|
||||
<field name="description">Reading comprehension and analysis</field>
|
||||
</record>
|
||||
<record id="domain_eng_writing" model="encoach.domain">
|
||||
<field name="name">Writing</field>
|
||||
<field name="subject_id" ref="subject_english"/>
|
||||
<field name="description">Written communication skills</field>
|
||||
</record>
|
||||
<record id="domain_eng_speaking" model="encoach.domain">
|
||||
<field name="name">Speaking</field>
|
||||
<field name="subject_id" ref="subject_english"/>
|
||||
<field name="description">Spoken communication and fluency</field>
|
||||
</record>
|
||||
<record id="domain_eng_grammar" model="encoach.domain">
|
||||
<field name="name">Grammar</field>
|
||||
<field name="subject_id" ref="subject_english"/>
|
||||
<field name="description">English grammar rules and usage</field>
|
||||
</record>
|
||||
<record id="domain_eng_vocabulary" model="encoach.domain">
|
||||
<field name="name">Vocabulary</field>
|
||||
<field name="subject_id" ref="subject_english"/>
|
||||
<field name="description">Word knowledge and usage</field>
|
||||
</record>
|
||||
|
||||
<!-- English Topics -->
|
||||
<record id="topic_eng_conversation" model="encoach.topic">
|
||||
<field name="name">Conversation Comprehension</field>
|
||||
<field name="domain_id" ref="domain_eng_listening"/>
|
||||
</record>
|
||||
<record id="topic_eng_monologue" model="encoach.topic">
|
||||
<field name="name">Monologue Comprehension</field>
|
||||
<field name="domain_id" ref="domain_eng_listening"/>
|
||||
</record>
|
||||
<record id="topic_eng_note_taking" model="encoach.topic">
|
||||
<field name="name">Note Taking & Form Completion</field>
|
||||
<field name="domain_id" ref="domain_eng_listening"/>
|
||||
</record>
|
||||
<record id="topic_eng_skimming" model="encoach.topic">
|
||||
<field name="name">Skimming & Scanning</field>
|
||||
<field name="domain_id" ref="domain_eng_reading"/>
|
||||
</record>
|
||||
<record id="topic_eng_detailed_reading" model="encoach.topic">
|
||||
<field name="name">Detailed Reading</field>
|
||||
<field name="domain_id" ref="domain_eng_reading"/>
|
||||
</record>
|
||||
<record id="topic_eng_inference" model="encoach.topic">
|
||||
<field name="name">Inference & Implication</field>
|
||||
<field name="domain_id" ref="domain_eng_reading"/>
|
||||
</record>
|
||||
<record id="topic_eng_task1_writing" model="encoach.topic">
|
||||
<field name="name">Task 1 — Report/Letter Writing</field>
|
||||
<field name="domain_id" ref="domain_eng_writing"/>
|
||||
</record>
|
||||
<record id="topic_eng_task2_essay" model="encoach.topic">
|
||||
<field name="name">Task 2 — Essay Writing</field>
|
||||
<field name="domain_id" ref="domain_eng_writing"/>
|
||||
</record>
|
||||
<record id="topic_eng_part1_intro" model="encoach.topic">
|
||||
<field name="name">Part 1 — Introduction & Interview</field>
|
||||
<field name="domain_id" ref="domain_eng_speaking"/>
|
||||
</record>
|
||||
<record id="topic_eng_part2_cue" model="encoach.topic">
|
||||
<field name="name">Part 2 — Cue Card</field>
|
||||
<field name="domain_id" ref="domain_eng_speaking"/>
|
||||
</record>
|
||||
<record id="topic_eng_part3_discussion" model="encoach.topic">
|
||||
<field name="name">Part 3 — Discussion</field>
|
||||
<field name="domain_id" ref="domain_eng_speaking"/>
|
||||
</record>
|
||||
<record id="topic_eng_tenses" model="encoach.topic">
|
||||
<field name="name">Tenses</field>
|
||||
<field name="domain_id" ref="domain_eng_grammar"/>
|
||||
</record>
|
||||
<record id="topic_eng_conditionals" model="encoach.topic">
|
||||
<field name="name">Conditionals</field>
|
||||
<field name="domain_id" ref="domain_eng_grammar"/>
|
||||
</record>
|
||||
<record id="topic_eng_passive_voice" model="encoach.topic">
|
||||
<field name="name">Passive Voice</field>
|
||||
<field name="domain_id" ref="domain_eng_grammar"/>
|
||||
</record>
|
||||
<record id="topic_eng_articles_determiners" model="encoach.topic">
|
||||
<field name="name">Articles & Determiners</field>
|
||||
<field name="domain_id" ref="domain_eng_grammar"/>
|
||||
</record>
|
||||
<record id="topic_eng_academic_vocab" model="encoach.topic">
|
||||
<field name="name">Academic Vocabulary</field>
|
||||
<field name="domain_id" ref="domain_eng_vocabulary"/>
|
||||
</record>
|
||||
<record id="topic_eng_collocations" model="encoach.topic">
|
||||
<field name="name">Collocations & Phrasal Verbs</field>
|
||||
<field name="domain_id" ref="domain_eng_vocabulary"/>
|
||||
</record>
|
||||
|
||||
<!-- ===================== MATHEMATICS DOMAINS & TOPICS ===================== -->
|
||||
<record id="domain_math_algebra" model="encoach.domain">
|
||||
<field name="name">Algebra</field>
|
||||
<field name="subject_id" ref="subject_math"/>
|
||||
<field name="description">Algebraic expressions, equations, and functions</field>
|
||||
</record>
|
||||
<record id="domain_math_geometry" model="encoach.domain">
|
||||
<field name="name">Geometry</field>
|
||||
<field name="subject_id" ref="subject_math"/>
|
||||
<field name="description">Shapes, areas, volumes, and geometric proofs</field>
|
||||
</record>
|
||||
<record id="domain_math_calculus" model="encoach.domain">
|
||||
<field name="name">Calculus</field>
|
||||
<field name="subject_id" ref="subject_math"/>
|
||||
<field name="description">Limits, derivatives, integrals</field>
|
||||
</record>
|
||||
<record id="domain_math_statistics" model="encoach.domain">
|
||||
<field name="name">Statistics & Probability</field>
|
||||
<field name="subject_id" ref="subject_math"/>
|
||||
<field name="description">Data analysis, probability, distributions</field>
|
||||
</record>
|
||||
<record id="domain_math_linear_algebra" model="encoach.domain">
|
||||
<field name="name">Linear Algebra</field>
|
||||
<field name="subject_id" ref="subject_math"/>
|
||||
<field name="description">Matrices, vectors, linear transformations</field>
|
||||
</record>
|
||||
<record id="domain_math_number_theory" model="encoach.domain">
|
||||
<field name="name">Number Theory</field>
|
||||
<field name="subject_id" ref="subject_math"/>
|
||||
<field name="description">Integers, primes, divisibility</field>
|
||||
</record>
|
||||
|
||||
<!-- Math Topics -->
|
||||
<record id="topic_math_linear_eq" model="encoach.topic">
|
||||
<field name="name">Linear Equations</field>
|
||||
<field name="domain_id" ref="domain_math_algebra"/>
|
||||
</record>
|
||||
<record id="topic_math_quadratic_eq" model="encoach.topic">
|
||||
<field name="name">Quadratic Equations</field>
|
||||
<field name="domain_id" ref="domain_math_algebra"/>
|
||||
</record>
|
||||
<record id="topic_math_polynomials" model="encoach.topic">
|
||||
<field name="name">Polynomials</field>
|
||||
<field name="domain_id" ref="domain_math_algebra"/>
|
||||
</record>
|
||||
<record id="topic_math_functions" model="encoach.topic">
|
||||
<field name="name">Functions & Graphs</field>
|
||||
<field name="domain_id" ref="domain_math_algebra"/>
|
||||
</record>
|
||||
<record id="topic_math_triangles" model="encoach.topic">
|
||||
<field name="name">Triangles & Trigonometry</field>
|
||||
<field name="domain_id" ref="domain_math_geometry"/>
|
||||
</record>
|
||||
<record id="topic_math_circles" model="encoach.topic">
|
||||
<field name="name">Circles</field>
|
||||
<field name="domain_id" ref="domain_math_geometry"/>
|
||||
</record>
|
||||
<record id="topic_math_coordinate" model="encoach.topic">
|
||||
<field name="name">Coordinate Geometry</field>
|
||||
<field name="domain_id" ref="domain_math_geometry"/>
|
||||
</record>
|
||||
<record id="topic_math_limits" model="encoach.topic">
|
||||
<field name="name">Limits & Continuity</field>
|
||||
<field name="domain_id" ref="domain_math_calculus"/>
|
||||
</record>
|
||||
<record id="topic_math_derivatives" model="encoach.topic">
|
||||
<field name="name">Derivatives</field>
|
||||
<field name="domain_id" ref="domain_math_calculus"/>
|
||||
</record>
|
||||
<record id="topic_math_integrals" model="encoach.topic">
|
||||
<field name="name">Integrals</field>
|
||||
<field name="domain_id" ref="domain_math_calculus"/>
|
||||
</record>
|
||||
<record id="topic_math_descriptive" model="encoach.topic">
|
||||
<field name="name">Descriptive Statistics</field>
|
||||
<field name="domain_id" ref="domain_math_statistics"/>
|
||||
</record>
|
||||
<record id="topic_math_probability" model="encoach.topic">
|
||||
<field name="name">Probability</field>
|
||||
<field name="domain_id" ref="domain_math_statistics"/>
|
||||
</record>
|
||||
<record id="topic_math_matrices" model="encoach.topic">
|
||||
<field name="name">Matrix Operations</field>
|
||||
<field name="domain_id" ref="domain_math_linear_algebra"/>
|
||||
</record>
|
||||
<record id="topic_math_vectors" model="encoach.topic">
|
||||
<field name="name">Vectors</field>
|
||||
<field name="domain_id" ref="domain_math_linear_algebra"/>
|
||||
</record>
|
||||
<record id="topic_math_primes" model="encoach.topic">
|
||||
<field name="name">Prime Numbers & Factorization</field>
|
||||
<field name="domain_id" ref="domain_math_number_theory"/>
|
||||
</record>
|
||||
|
||||
<!-- ===================== IT DOMAINS & TOPICS ===================== -->
|
||||
<record id="domain_it_programming" model="encoach.domain">
|
||||
<field name="name">Programming Fundamentals</field>
|
||||
<field name="subject_id" ref="subject_it"/>
|
||||
<field name="description">Core programming concepts and languages</field>
|
||||
</record>
|
||||
<record id="domain_it_databases" model="encoach.domain">
|
||||
<field name="name">Databases</field>
|
||||
<field name="subject_id" ref="subject_it"/>
|
||||
<field name="description">Database design, SQL, and data management</field>
|
||||
</record>
|
||||
<record id="domain_it_networking" model="encoach.domain">
|
||||
<field name="name">Networking</field>
|
||||
<field name="subject_id" ref="subject_it"/>
|
||||
<field name="description">Computer networks, protocols, and security</field>
|
||||
</record>
|
||||
<record id="domain_it_web_dev" model="encoach.domain">
|
||||
<field name="name">Web Development</field>
|
||||
<field name="subject_id" ref="subject_it"/>
|
||||
<field name="description">Frontend, backend, and full-stack web development</field>
|
||||
</record>
|
||||
<record id="domain_it_os" model="encoach.domain">
|
||||
<field name="name">Operating Systems</field>
|
||||
<field name="subject_id" ref="subject_it"/>
|
||||
<field name="description">OS concepts, Linux, process management</field>
|
||||
</record>
|
||||
<record id="domain_it_security" model="encoach.domain">
|
||||
<field name="name">Cybersecurity</field>
|
||||
<field name="subject_id" ref="subject_it"/>
|
||||
<field name="description">Information security principles and practices</field>
|
||||
</record>
|
||||
|
||||
<!-- IT Topics -->
|
||||
<record id="topic_it_variables" model="encoach.topic">
|
||||
<field name="name">Variables & Data Types</field>
|
||||
<field name="domain_id" ref="domain_it_programming"/>
|
||||
</record>
|
||||
<record id="topic_it_control_flow" model="encoach.topic">
|
||||
<field name="name">Control Flow</field>
|
||||
<field name="domain_id" ref="domain_it_programming"/>
|
||||
</record>
|
||||
<record id="topic_it_functions" model="encoach.topic">
|
||||
<field name="name">Functions & Modules</field>
|
||||
<field name="domain_id" ref="domain_it_programming"/>
|
||||
</record>
|
||||
<record id="topic_it_oop" model="encoach.topic">
|
||||
<field name="name">Object-Oriented Programming</field>
|
||||
<field name="domain_id" ref="domain_it_programming"/>
|
||||
</record>
|
||||
<record id="topic_it_algorithms" model="encoach.topic">
|
||||
<field name="name">Algorithms & Data Structures</field>
|
||||
<field name="domain_id" ref="domain_it_programming"/>
|
||||
</record>
|
||||
<record id="topic_it_sql_basics" model="encoach.topic">
|
||||
<field name="name">SQL Basics</field>
|
||||
<field name="domain_id" ref="domain_it_databases"/>
|
||||
</record>
|
||||
<record id="topic_it_joins" model="encoach.topic">
|
||||
<field name="name">Joins & Subqueries</field>
|
||||
<field name="domain_id" ref="domain_it_databases"/>
|
||||
</record>
|
||||
<record id="topic_it_db_design" model="encoach.topic">
|
||||
<field name="name">Database Design & Normalization</field>
|
||||
<field name="domain_id" ref="domain_it_databases"/>
|
||||
</record>
|
||||
<record id="topic_it_tcp_ip" model="encoach.topic">
|
||||
<field name="name">TCP/IP & OSI Model</field>
|
||||
<field name="domain_id" ref="domain_it_networking"/>
|
||||
</record>
|
||||
<record id="topic_it_routing" model="encoach.topic">
|
||||
<field name="name">Routing & Switching</field>
|
||||
<field name="domain_id" ref="domain_it_networking"/>
|
||||
</record>
|
||||
<record id="topic_it_html_css" model="encoach.topic">
|
||||
<field name="name">HTML & CSS</field>
|
||||
<field name="domain_id" ref="domain_it_web_dev"/>
|
||||
</record>
|
||||
<record id="topic_it_javascript" model="encoach.topic">
|
||||
<field name="name">JavaScript</field>
|
||||
<field name="domain_id" ref="domain_it_web_dev"/>
|
||||
</record>
|
||||
<record id="topic_it_backend" model="encoach.topic">
|
||||
<field name="name">Backend Development</field>
|
||||
<field name="domain_id" ref="domain_it_web_dev"/>
|
||||
</record>
|
||||
<record id="topic_it_linux" model="encoach.topic">
|
||||
<field name="name">Linux Administration</field>
|
||||
<field name="domain_id" ref="domain_it_os"/>
|
||||
</record>
|
||||
<record id="topic_it_processes" model="encoach.topic">
|
||||
<field name="name">Process Management</field>
|
||||
<field name="domain_id" ref="domain_it_os"/>
|
||||
</record>
|
||||
<record id="topic_it_encryption" model="encoach.topic">
|
||||
<field name="name">Encryption & Cryptography</field>
|
||||
<field name="domain_id" ref="domain_it_security"/>
|
||||
</record>
|
||||
<record id="topic_it_threats" model="encoach.topic">
|
||||
<field name="name">Threats & Vulnerability Assessment</field>
|
||||
<field name="domain_id" ref="domain_it_security"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
4
custom_addons/encoach_taxonomy/models/__init__.py
Normal file
4
custom_addons/encoach_taxonomy/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import subject
|
||||
from . import domain
|
||||
from . import topic
|
||||
from . import learning_objective
|
||||
25
custom_addons/encoach_taxonomy/models/domain.py
Normal file
25
custom_addons/encoach_taxonomy/models/domain.py
Normal 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,
|
||||
}
|
||||
29
custom_addons/encoach_taxonomy/models/learning_objective.py
Normal file
29
custom_addons/encoach_taxonomy/models/learning_objective.py
Normal 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,
|
||||
}
|
||||
24
custom_addons/encoach_taxonomy/models/subject.py
Normal file
24
custom_addons/encoach_taxonomy/models/subject.py
Normal 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,
|
||||
}
|
||||
25
custom_addons/encoach_taxonomy/models/topic.py
Normal file
25
custom_addons/encoach_taxonomy/models/topic.py
Normal 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,
|
||||
}
|
||||
@@ -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
|
||||
|
150
custom_addons/encoach_taxonomy/views/taxonomy_views.xml
Normal file
150
custom_addons/encoach_taxonomy/views/taxonomy_views.xml
Normal 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>
|
||||
Reference in New Issue
Block a user