import json import logging from odoo import http from odoo.http import request from .base import EncoachMixin _logger = logging.getLogger(__name__) VALID_MODULES = ('reading', 'listening', 'writing', 'speaking', 'level') class ExamsController(EncoachMixin, http.Controller): # --------------------------------------------------------------- list @http.route('/api/exam', type='http', auth='public', methods=['GET', 'OPTIONS'], csrf=False, cors='*') def list_exams(self, **kwargs): try: user = self._authenticate() if not user: return self._error_response('Unauthorized', 401) domain = [] module = kwargs.get('module') if module and module in VALID_MODULES: domain.append(('module', '=', module)) creator = kwargs.get('createdBy') if creator: domain.append(('create_uid', '=', int(creator))) is_published = kwargs.get('isPublished') if is_published is not None: domain.append(('is_published', '=', is_published in ('true', '1', True))) entity_id = kwargs.get('entityID') if entity_id: domain.append(('entity_id', '=', int(entity_id))) offset, limit, page = self._paginate_params(kwargs) Exam = request.env['encoach.exam'].sudo() total = Exam.search_count(domain) exams = Exam.search(domain, offset=offset, limit=limit, order='create_date desc') return self._json_response({ 'exams': [self._serialize_exam(e) for e in exams], 'total': total, 'page': page, 'limit': limit, }) except Exception: _logger.exception("List exams error") return self._error_response('Internal server error', 500) # --------------------------------------------------------------- create @http.route( '/api/exam/', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*', ) def create_exam(self, module, **kwargs): try: user = self._authenticate() if not user: return self._error_response('Unauthorized', 401) if module not in VALID_MODULES: return self._error_response(f'Invalid module: {module}', 400) body = self._get_json_body() vals = { 'name': body.get('name', ''), 'module': module, 'create_uid': user.id, } if 'parts' in body: vals['parts'] = json.dumps(body['parts']) if isinstance(body['parts'], (list, dict)) else body['parts'] if 'difficulty' in body: vals['difficulty'] = body['difficulty'] if 'tags' in body: vals['tags'] = json.dumps(body['tags']) if isinstance(body['tags'], list) else body['tags'] if 'isPublished' in body: vals['is_published'] = body['isPublished'] if 'entityID' in body and body['entityID']: vals['entity_id'] = int(body['entityID']) exam = request.env['encoach.exam'].sudo().with_user(user).create(vals) return self._json_response(self._serialize_exam(exam), status=201) except Exception: _logger.exception("Create exam error") return self._error_response('Internal server error', 500) # ----------------------------------------------------------- get by id @http.route( '/api/exam//', type='http', auth='public', methods=['GET', 'OPTIONS'], csrf=False, cors='*', ) def get_exam(self, module, exam_id, **kwargs): try: user = self._authenticate() if not user: return self._error_response('Unauthorized', 401) exam = request.env['encoach.exam'].sudo().browse(exam_id) if not exam.exists(): return self._error_response('Exam not found', 404) return self._json_response(self._serialize_exam(exam)) except Exception: _logger.exception("Get exam error") return self._error_response('Internal server error', 500) # -------------------------------------------------------------- update @http.route( '/api/exam//', type='http', auth='public', methods=['PATCH', 'OPTIONS'], csrf=False, cors='*', ) def update_exam(self, module, exam_id, **kwargs): try: user = self._authenticate() if not user: return self._error_response('Unauthorized', 401) exam = request.env['encoach.exam'].sudo().browse(exam_id) if not exam.exists(): return self._error_response('Exam not found', 404) body = self._get_json_body() vals = {} if 'name' in body: vals['name'] = body['name'] if 'parts' in body: vals['parts'] = json.dumps(body['parts']) if isinstance(body['parts'], (list, dict)) else body['parts'] if 'difficulty' in body: vals['difficulty'] = body['difficulty'] if 'tags' in body: vals['tags'] = json.dumps(body['tags']) if isinstance(body['tags'], list) else body['tags'] if 'isPublished' in body: vals['is_published'] = body['isPublished'] if vals: exam.write(vals) return self._json_response(self._serialize_exam(exam)) except Exception: _logger.exception("Update exam error") return self._error_response('Internal server error', 500) # -------------------------------------------------------------- delete @http.route( '/api/exam//', type='http', auth='public', methods=['DELETE', 'OPTIONS'], csrf=False, cors='*', ) def delete_exam(self, module, exam_id, **kwargs): try: user = self._authenticate() if not user: return self._error_response('Unauthorized', 401) exam = request.env['encoach.exam'].sudo().browse(exam_id) if not exam.exists(): return self._error_response('Exam not found', 404) exam.unlink() return self._json_response({'ok': True}) except Exception: _logger.exception("Delete exam error") return self._error_response('Internal server error', 500) # -------------------------------------------------------------- import @http.route( '/api/exam//import/', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*', ) def import_exam(self, module, **kwargs): """Import an exam from an uploaded file (Word/Excel).""" try: user = self._authenticate() if not user: return self._error_response('Unauthorized', 401) if module not in VALID_MODULES: return self._error_response(f'Invalid module: {module}', 400) exercises_file = request.httprequest.files.get('exercises') solutions_file = request.httprequest.files.get('solutions') if not exercises_file: return self._error_response('exercises file is required', 400) Exam = request.env['encoach.exam'].sudo() result = Exam.import_from_file( module=module, exercises_data=exercises_file.read(), exercises_filename=exercises_file.filename, solutions_data=solutions_file.read() if solutions_file else None, solutions_filename=solutions_file.filename if solutions_file else None, user=user, ) return self._json_response(result) except Exception: _logger.exception("Import exam error") return self._error_response('Internal server error', 500) # ------------------------------------------------------------ avatars @http.route('/api/exam/avatars', type='http', auth='public', methods=['GET', 'OPTIONS'], csrf=False, cors='*') def list_avatars(self, **kwargs): try: user = self._authenticate() if not user: return self._error_response('Unauthorized', 401) avatars = request.env['encoach.elai.avatar'].sudo().search([]) data = [{ 'name': a.name, 'avatar_code': a.avatar_code, 'avatar_url': a.avatar_url or '', 'gender': a.gender or '', } for a in avatars] return self._json_response(data) except Exception: _logger.exception("List avatars error") return self._error_response('Internal server error', 500)