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:
220
encoach_api/controllers/exams.py
Normal file
220
encoach_api/controllers/exams.py
Normal file
@@ -0,0 +1,220 @@
|
||||
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/<string:module>', 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/<string:module>/<int:exam_id>', 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/<string:module>/<int:exam_id>', 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/<string:module>/<int:exam_id>', 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/<string:module>/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)
|
||||
Reference in New Issue
Block a user