feat(v3): restructure project + add complete frontend
- 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
This commit is contained in:
130
custom_addons/encoach_exam_template/controllers/templates.py
Normal file
130
custom_addons/encoach_exam_template/controllers/templates.py
Normal file
@@ -0,0 +1,130 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user