diff --git a/custom_addons/encoach_exam_template/controllers/__init__.py b/custom_addons/encoach_exam_template/controllers/__init__.py index 673af85e..a1995d3b 100644 --- a/custom_addons/encoach_exam_template/controllers/__init__.py +++ b/custom_addons/encoach_exam_template/controllers/__init__.py @@ -2,3 +2,5 @@ from . import templates from . import ielts_exam from . import custom_exam from . import exam_structures +from . import assignments +from . import rubrics diff --git a/custom_addons/encoach_exam_template/controllers/__pycache__/__init__.cpython-312.pyc b/custom_addons/encoach_exam_template/controllers/__pycache__/__init__.cpython-312.pyc index a8186e1b..d13f86e7 100644 Binary files a/custom_addons/encoach_exam_template/controllers/__pycache__/__init__.cpython-312.pyc and b/custom_addons/encoach_exam_template/controllers/__pycache__/__init__.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/controllers/__pycache__/assignments.cpython-312.pyc b/custom_addons/encoach_exam_template/controllers/__pycache__/assignments.cpython-312.pyc new file mode 100644 index 00000000..01c6afe1 Binary files /dev/null and b/custom_addons/encoach_exam_template/controllers/__pycache__/assignments.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/controllers/__pycache__/rubrics.cpython-312.pyc b/custom_addons/encoach_exam_template/controllers/__pycache__/rubrics.cpython-312.pyc new file mode 100644 index 00000000..b4f87a2d Binary files /dev/null and b/custom_addons/encoach_exam_template/controllers/__pycache__/rubrics.cpython-312.pyc differ diff --git a/custom_addons/encoach_exam_template/controllers/assignments.py b/custom_addons/encoach_exam_template/controllers/assignments.py new file mode 100644 index 00000000..4c356bca --- /dev/null +++ b/custom_addons/encoach_exam_template/controllers/assignments.py @@ -0,0 +1,110 @@ +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 _assignment_to_dict(rec): + return { + 'id': rec.id, + 'title': rec.exam_id.title if rec.exam_id else f'Assignment #{rec.id}', + 'exam_id': rec.exam_id.id if rec.exam_id else None, + 'student_id': rec.student_id.id if rec.student_id else None, + 'student_name': rec.student_id.name if rec.student_id else None, + 'batch_id': rec.batch_id.id if rec.batch_id else None, + 'batch_name': rec.batch_id.name if rec.batch_id else None, + 'entity_name': rec.exam_id.entity_id.name if rec.exam_id and rec.exam_id.entity_id else None, + 'start_date': rec.access_start.isoformat() if rec.access_start else None, + 'end_date': rec.access_end.isoformat() if rec.access_end else None, + 'state': rec.status, + 'assignee_count': 1, + 'completed_count': 1 if rec.status == 'completed' else 0, + } + + +class EncoachAssignmentController(http.Controller): + + @http.route('/api/assignments', type='http', auth='none', + methods=['GET'], csrf=False) + @jwt_required + def list_assignments(self, **kw): + try: + Assignment = request.env['encoach.exam.assignment'].sudo() + search = kw.get('search', '').strip() + domain = [] + if search: + domain = [('exam_id.title', 'ilike', search)] + + total = Assignment.search_count(domain) + page, per_page, offset = _paginate(kw) + records = Assignment.search(domain, limit=per_page, offset=offset, + order='id desc') + return _json_response({ + 'items': [_assignment_to_dict(r) for r in records], + 'total': total, + 'page': page, + 'per_page': per_page, + }) + except Exception as e: + _logger.exception('assignments list failed') + return _error_response(str(e), 500) + + @http.route('/api/assignments', type='http', auth='none', + methods=['POST'], csrf=False) + @jwt_required + def create_assignment(self, **kw): + try: + body = _get_json_body() + title = (body.get('title') or '').strip() + if not title: + return _error_response('title is required', 400) + + exam_id = body.get('exam_id') + if not exam_id: + Exam = request.env['encoach.exam.custom'].sudo() + exam = Exam.create({ + 'title': title, + 'teacher_id': request.env.user.id, + 'status': 'draft', + 'total_time_min': 0, + }) + exam_id = exam.id + + vals = { + 'exam_id': exam_id, + 'student_id': body.get('student_id') or False, + 'batch_id': body.get('batch_id') or False, + 'status': 'assigned', + } + + start_date = body.get('start_date') + end_date = body.get('end_date') + if start_date: + vals['access_start'] = start_date + if end_date: + vals['access_end'] = end_date + + rec = request.env['encoach.exam.assignment'].sudo().create(vals) + return _json_response(_assignment_to_dict(rec), 201) + except Exception as e: + _logger.exception('assignment create failed') + return _error_response(str(e), 500) + + @http.route('/api/assignments/', type='http', + auth='none', methods=['GET'], csrf=False) + @jwt_required + def get_assignment(self, assignment_id, **kw): + try: + rec = request.env['encoach.exam.assignment'].sudo().browse(assignment_id) + if not rec.exists(): + return _error_response('Assignment not found', 404) + return _json_response(_assignment_to_dict(rec)) + except Exception as e: + _logger.exception('assignment get failed') + return _error_response(str(e), 500) diff --git a/custom_addons/encoach_exam_template/controllers/rubrics.py b/custom_addons/encoach_exam_template/controllers/rubrics.py new file mode 100644 index 00000000..ae9ba783 --- /dev/null +++ b/custom_addons/encoach_exam_template/controllers/rubrics.py @@ -0,0 +1,80 @@ +import json +import logging + +from odoo import http +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +def _json_response(data, status=200): + return request.make_json_response(data, status=status) + + +def _rubric_to_dict(rec): + criteria_text = rec.criteria or '' + criteria_count = 0 + if criteria_text: + try: + parsed = json.loads(criteria_text) + if isinstance(parsed, list): + criteria_count = len(parsed) + elif isinstance(parsed, dict): + criteria_count = len(parsed) + else: + criteria_count = len([l for l in criteria_text.split('\n') if l.strip()]) + except (json.JSONDecodeError, ValueError): + criteria_count = len([l for l in criteria_text.split('\n') if l.strip()]) + + return { + 'id': rec.id, + 'name': rec.name, + 'skill': rec.skill or '', + 'exam_type': rec.exam_type or '', + 'criteria': criteria_count or 1, + 'criteria_text': criteria_text, + 'levels': ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'], + 'created': rec.create_date.strftime('%Y-%m-%d') if rec.create_date else '', + } + + +class EncoachRubricController(http.Controller): + + @http.route('/api/rubrics', type='http', auth='user', + methods=['GET'], csrf=False) + def list_rubrics(self, **kw): + try: + Rubric = request.env['encoach.rubric'].sudo() + limit = int(kw.get('limit', 50)) + offset = int(kw.get('offset', 0)) + records = Rubric.search([], limit=limit, offset=offset, + order='create_date desc') + total = Rubric.search_count([]) + return _json_response({ + 'items': [_rubric_to_dict(r) for r in records], + 'total': total, + }) + except Exception as e: + _logger.exception('rubrics list failed') + return _json_response({'error': str(e)}, 500) + + @http.route('/api/rubrics', type='http', auth='user', + methods=['POST'], csrf=False) + def create_rubric(self, **kw): + try: + body = json.loads(request.httprequest.data or '{}') + name = body.get('name', '').strip() + if not name: + return _json_response({'error': 'name is required'}, 400) + + vals = { + 'name': name, + 'skill': body.get('skill', 'writing'), + 'criteria': body.get('criteria', ''), + 'exam_type': body.get('exam_type', 'academic'), + } + rec = Rubric = request.env['encoach.rubric'].sudo().create(vals) + return _json_response(_rubric_to_dict(rec), 201) + except Exception as e: + _logger.exception('rubric create failed') + return _json_response({'error': str(e)}, 500)