- Restructure: move backend from new_project/ to backend/ - Add full React/TypeScript frontend (37 pages, 17 services, 16 type defs, 11 query hooks) - Add docs/ with SRS specs, user stories, and workflow documentation - Update .gitignore for new directory layout Workflows implemented: WF1 User Signup, WF2 Placement Test, WF3 Exam Configuration, WF4 General English Exam, WF5 Course Generation, WF6 Entity Student Onboarding, AI Course Generation, Adaptive Learning Engine UI, White-Label Branding, Score Release Made-with: Cursor
131 lines
4.8 KiB
Python
131 lines
4.8 KiB
Python
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__)
|
|
|
|
|
|
def _template_to_dict(rec):
|
|
structure = None
|
|
if rec.structure:
|
|
try:
|
|
structure = json.loads(rec.structure)
|
|
except (json.JSONDecodeError, TypeError):
|
|
structure = rec.structure
|
|
return {
|
|
'id': rec.id,
|
|
'name': rec.name,
|
|
'code': rec.code or '',
|
|
'type': rec.type,
|
|
'editable': rec.editable,
|
|
'active': rec.active,
|
|
'subject_id': rec.subject_id.id if rec.subject_id else None,
|
|
'subject_name': rec.subject_id.name if rec.subject_id else None,
|
|
'entity_id': rec.entity_id.id if rec.entity_id else None,
|
|
'teacher_id': rec.teacher_id.id if rec.teacher_id else None,
|
|
'structure': structure,
|
|
'total_time_min': rec.total_time_min or 0,
|
|
'pass_threshold': rec.pass_threshold or 0.0,
|
|
'results_release_mode': rec.results_release_mode or 'auto',
|
|
'randomize_questions': rec.randomize_questions,
|
|
'description': rec.description or '',
|
|
}
|
|
|
|
|
|
class EncoachTemplatesController(http.Controller):
|
|
|
|
# ------------------------------------------------------------------
|
|
# GET /api/exam/templates
|
|
# ------------------------------------------------------------------
|
|
@http.route('/api/exam/templates', type='http', auth='none',
|
|
methods=['GET'], csrf=False)
|
|
@jwt_required
|
|
def list_templates(self, **kw):
|
|
try:
|
|
domain = [('active', '=', True)]
|
|
|
|
tpl_type = kw.get('type')
|
|
if tpl_type in ('international', 'custom'):
|
|
domain.append(('type', '=', tpl_type))
|
|
|
|
subject_id = kw.get('subject_id')
|
|
if subject_id:
|
|
domain.append(('subject_id', '=', int(subject_id)))
|
|
|
|
page = int(kw.get('page', 1))
|
|
size = int(kw.get('size', 20))
|
|
|
|
Template = request.env['encoach.exam.template'].sudo()
|
|
records, total = _paginate(Template, domain, page=page, size=size)
|
|
|
|
return _json_response({
|
|
'items': [_template_to_dict(r) for r in records],
|
|
'total': total,
|
|
'page': page,
|
|
'size': size,
|
|
})
|
|
|
|
except Exception as e:
|
|
_logger.exception('list_templates failed')
|
|
return _error_response(str(e), 500)
|
|
|
|
# ------------------------------------------------------------------
|
|
# GET /api/exam/templates/<int:template_id>
|
|
# ------------------------------------------------------------------
|
|
@http.route('/api/exam/templates/<int:template_id>', type='http',
|
|
auth='none', methods=['GET'], csrf=False)
|
|
@jwt_required
|
|
def get_template(self, template_id, **kw):
|
|
try:
|
|
rec = request.env['encoach.exam.template'].sudo().browse(template_id)
|
|
if not rec.exists():
|
|
return _error_response('Template not found', 404)
|
|
|
|
return _json_response(_template_to_dict(rec))
|
|
|
|
except Exception as e:
|
|
_logger.exception('get_template failed')
|
|
return _error_response(str(e), 500)
|
|
|
|
# ------------------------------------------------------------------
|
|
# POST /api/exam/templates/custom
|
|
# ------------------------------------------------------------------
|
|
@http.route('/api/exam/templates/custom', type='http', auth='none',
|
|
methods=['POST'], csrf=False)
|
|
@jwt_required
|
|
def create_custom_template(self, **kw):
|
|
try:
|
|
body = _get_json_body()
|
|
name = body.get('name', '').strip()
|
|
if not name:
|
|
return _error_response('name is required', 400)
|
|
|
|
structure = body.get('structure')
|
|
if structure and not isinstance(structure, str):
|
|
structure = json.dumps(structure)
|
|
|
|
vals = {
|
|
'name': name,
|
|
'type': 'custom',
|
|
'teacher_id': request.env.user.id,
|
|
'subject_id': body.get('subject_id'),
|
|
'structure': structure,
|
|
'total_time_min': body.get('total_time_min', 0),
|
|
'pass_threshold': body.get('pass_threshold', 0.0),
|
|
'results_release_mode': body.get('results_release_mode', 'auto'),
|
|
'randomize_questions': body.get('randomize_questions', False),
|
|
'description': body.get('description', ''),
|
|
}
|
|
|
|
rec = request.env['encoach.exam.template'].sudo().create(vals)
|
|
return _json_response(_template_to_dict(rec), 201)
|
|
|
|
except Exception as e:
|
|
_logger.exception('create_custom_template failed')
|
|
return _error_response(str(e), 500)
|