Fix model-controller mismatches and routing issues
- ticket: add user_id, priority, category fields; fix _serialize_ticket -> _serialize - package: fix is_active -> active (Odoo auto-filter), remove invalid entity_id filter - walkthrough: add sequence field; fix controller to use name/content instead of title/steps - users: fix encoach_user_type -> encoach_type, encoach_entity_id -> entity_rel_ids.entity_id - generation: use EncoachGenerationService class instead of nonexistent ORM model, fix /api/exam/level/ route conflict (separate GET and POST URLs) Made-with: Cursor
This commit is contained in:
@@ -9,6 +9,13 @@ from .base import EncoachMixin
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_gen_service():
|
||||
from odoo.addons.encoach_ai_generation.services.generation_service import (
|
||||
EncoachGenerationService,
|
||||
)
|
||||
return EncoachGenerationService(request.env)
|
||||
|
||||
|
||||
class GenerationController(EncoachMixin, http.Controller):
|
||||
|
||||
# ------------------------------------------------- reading passage
|
||||
@@ -28,9 +35,9 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
topic = kwargs.get('topic')
|
||||
word_count = int(kwargs.get('word_count', 500))
|
||||
|
||||
GenService = request.env['encoach.ai.generation'].sudo()
|
||||
result = GenService.generate_reading_passage(
|
||||
passage=passage,
|
||||
service = _get_gen_service()
|
||||
result = service.generate_reading_passage(
|
||||
passage_num=passage,
|
||||
topic=topic,
|
||||
word_count=word_count,
|
||||
)
|
||||
@@ -57,10 +64,10 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
if not text or not exercises:
|
||||
return self._error_response('text and exercises are required', 400)
|
||||
|
||||
GenService = request.env['encoach.ai.generation'].sudo()
|
||||
result = GenService.generate_reading_exercises(
|
||||
service = _get_gen_service()
|
||||
result = service.generate_reading_exercises(
|
||||
text=text,
|
||||
exercises=exercises,
|
||||
exercises_config=exercises,
|
||||
difficulty=difficulty,
|
||||
)
|
||||
|
||||
@@ -86,8 +93,8 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
topic = kwargs.get('topic')
|
||||
difficulty = kwargs.get('difficulty')
|
||||
|
||||
GenService = request.env['encoach.ai.generation'].sudo()
|
||||
result = GenService.generate_listening_dialog(
|
||||
service = _get_gen_service()
|
||||
result = service.generate_listening_dialog(
|
||||
section=section,
|
||||
topic=topic,
|
||||
difficulty=difficulty,
|
||||
@@ -115,10 +122,10 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
if not text or not exercises:
|
||||
return self._error_response('text and exercises are required', 400)
|
||||
|
||||
GenService = request.env['encoach.ai.generation'].sudo()
|
||||
result = GenService.generate_listening_exercises(
|
||||
service = _get_gen_service()
|
||||
result = service.generate_listening_exercises(
|
||||
text=text,
|
||||
exercises=exercises,
|
||||
exercises_config=exercises,
|
||||
difficulty=difficulty,
|
||||
)
|
||||
|
||||
@@ -144,8 +151,8 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
topic = kwargs.get('topic')
|
||||
difficulty = kwargs.get('difficulty')
|
||||
|
||||
GenService = request.env['encoach.ai.generation'].sudo()
|
||||
result = GenService.generate_writing_task(
|
||||
service = _get_gen_service()
|
||||
result = service.generate_writing_task(
|
||||
task=task,
|
||||
topic=topic,
|
||||
difficulty=difficulty,
|
||||
@@ -175,9 +182,9 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
second_topic = kwargs.get('second_topic')
|
||||
difficulty = kwargs.get('difficulty')
|
||||
|
||||
GenService = request.env['encoach.ai.generation'].sudo()
|
||||
result = GenService.generate_speaking_task(
|
||||
task=task,
|
||||
service = _get_gen_service()
|
||||
result = service.generate_speaking_task(
|
||||
part=task,
|
||||
topic=topic,
|
||||
first_topic=first_topic,
|
||||
second_topic=second_topic,
|
||||
@@ -189,8 +196,8 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
_logger.exception("Generate speaking task error")
|
||||
return self._error_response('Internal server error', 500)
|
||||
|
||||
# --------------------------------------------- level exercises
|
||||
@http.route('/api/exam/level/', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*')
|
||||
# --------------------------------------------- level exercises (POST)
|
||||
@http.route('/api/exam/level/generate', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*')
|
||||
def generate_level_exercises(self, **kwargs):
|
||||
"""Generate level-test exercises using GPT-4o."""
|
||||
try:
|
||||
@@ -205,9 +212,9 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
if not exercises:
|
||||
return self._error_response('exercises specification is required', 400)
|
||||
|
||||
GenService = request.env['encoach.ai.generation'].sudo()
|
||||
result = GenService.generate_level_exercises(
|
||||
exercises=exercises,
|
||||
service = _get_gen_service()
|
||||
result = service.generate_level_exercises(
|
||||
exercises_config=exercises,
|
||||
difficulty=difficulty,
|
||||
)
|
||||
|
||||
@@ -242,11 +249,9 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
image_data = base64.b64encode(image_file.read()).decode('utf-8')
|
||||
mime_type = image_file.content_type or 'image/png'
|
||||
|
||||
GenService = request.env['encoach.ai.generation'].sudo()
|
||||
result = GenService.generate_writing_with_image(
|
||||
service = _get_gen_service()
|
||||
result = service.generate_writing_task(
|
||||
task=task,
|
||||
image_b64=image_data,
|
||||
mime_type=mime_type,
|
||||
difficulty=difficulty,
|
||||
)
|
||||
|
||||
@@ -255,8 +260,8 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
_logger.exception("Generate writing attachment error")
|
||||
return self._error_response('Internal server error', 500)
|
||||
|
||||
# -------------------------------------------- pre-built level exam
|
||||
@http.route('/api/exam/level/', type='http', auth='public', methods=['GET', 'OPTIONS'], csrf=False, cors='*')
|
||||
# -------------------------------------------- pre-built level exam (GET)
|
||||
@http.route('/api/exam/level', type='http', auth='public', methods=['GET', 'OPTIONS'], csrf=False, cors='*')
|
||||
def get_level_exam(self, **kwargs):
|
||||
"""Return a pre-built level exam (no AI generation)."""
|
||||
try:
|
||||
@@ -296,7 +301,7 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
return self._error_response('Internal server error', 500)
|
||||
|
||||
# -------------------------------------------- level exam import
|
||||
@http.route('/api/exam/level/import/', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*')
|
||||
@http.route('/api/exam/level/import', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*')
|
||||
def import_level_exam(self, **kwargs):
|
||||
"""Import level exam from uploaded file."""
|
||||
try:
|
||||
@@ -309,9 +314,8 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
if not exercises_file:
|
||||
return self._error_response('exercises file is required', 400)
|
||||
|
||||
import json as _json
|
||||
content = exercises_file.read().decode('utf-8')
|
||||
data = _json.loads(content)
|
||||
data = json.loads(content)
|
||||
|
||||
exam = request.env['encoach.exam'].sudo().create({
|
||||
'module': 'level',
|
||||
@@ -325,7 +329,7 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
return self._error_response('Internal server error', 500)
|
||||
|
||||
# -------------------------------------------- custom level exam
|
||||
@http.route('/api/exam/level/custom/', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*')
|
||||
@http.route('/api/exam/level/custom', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*')
|
||||
def generate_custom_level(self, **kwargs):
|
||||
"""Generate custom level exam from JSON specification."""
|
||||
try:
|
||||
@@ -336,9 +340,9 @@ class GenerationController(EncoachMixin, http.Controller):
|
||||
body = self._get_json_body()
|
||||
spec = body.get('spec', body)
|
||||
|
||||
GenService = request.env['encoach.ai.generation'].sudo()
|
||||
result = GenService.generate_level_exercises(
|
||||
exercises=spec.get('exercises', []),
|
||||
service = _get_gen_service()
|
||||
result = service.generate_level_exercises(
|
||||
exercises_config=spec.get('exercises', []),
|
||||
difficulty=spec.get('difficulty', ['B1', 'B2']),
|
||||
)
|
||||
return self._json_response({'exercises': result})
|
||||
|
||||
@@ -19,10 +19,7 @@ class SubscriptionsController(EncoachMixin, http.Controller):
|
||||
if not user:
|
||||
return self._error_response('Unauthorized', 401)
|
||||
|
||||
domain = [('is_active', '=', True)]
|
||||
entity_id = kwargs.get('entityID')
|
||||
if entity_id:
|
||||
domain.append(('entity_id', '=', int(entity_id)))
|
||||
domain = []
|
||||
|
||||
packages = request.env['encoach.package'].sudo().search(domain, order='price asc')
|
||||
return self._json_response([p.to_encoach_dict() for p in packages])
|
||||
|
||||
@@ -19,7 +19,7 @@ class TicketsController(EncoachMixin, http.Controller):
|
||||
return self._error_response('Unauthorized', 401)
|
||||
|
||||
domain = []
|
||||
user_type = getattr(user, 'encoach_user_type', 'student')
|
||||
user_type = getattr(user, 'encoach_type', 'student')
|
||||
if user_type not in ('admin', 'developer'):
|
||||
domain.append(('user_id', '=', user.id))
|
||||
|
||||
@@ -33,7 +33,7 @@ class TicketsController(EncoachMixin, http.Controller):
|
||||
records = Ticket.search(domain, offset=offset, limit=limit, order='create_date desc')
|
||||
|
||||
return self._json_response({
|
||||
'tickets': [self._serialize_ticket(t) for t in records],
|
||||
'tickets': [self._serialize(t) for t in records],
|
||||
'total': total,
|
||||
'page': page,
|
||||
'limit': limit,
|
||||
@@ -67,7 +67,7 @@ class TicketsController(EncoachMixin, http.Controller):
|
||||
vals['category'] = body['category']
|
||||
|
||||
ticket = request.env['encoach.ticket'].sudo().create(vals)
|
||||
return self._json_response(self._serialize_ticket(ticket), status=201)
|
||||
return self._json_response(self._serialize(ticket), status=201)
|
||||
except Exception:
|
||||
_logger.exception("Create ticket error")
|
||||
return self._error_response('Internal server error', 500)
|
||||
@@ -101,7 +101,7 @@ class TicketsController(EncoachMixin, http.Controller):
|
||||
if vals:
|
||||
ticket.write(vals)
|
||||
|
||||
return self._json_response(self._serialize_ticket(ticket))
|
||||
return self._json_response(self._serialize(ticket))
|
||||
except Exception:
|
||||
_logger.exception("Update ticket error")
|
||||
return self._error_response('Internal server error', 500)
|
||||
|
||||
@@ -105,10 +105,10 @@ class TrainingController(EncoachMixin, http.Controller):
|
||||
data = [{
|
||||
'_id': str(w.id),
|
||||
'id': w.id,
|
||||
'title': getattr(w, 'title', ''),
|
||||
'description': getattr(w, 'description', ''),
|
||||
'steps': json.loads(w.steps) if getattr(w, 'steps', None) else [],
|
||||
'module': getattr(w, 'module', ''),
|
||||
'title': w.name or '',
|
||||
'description': '',
|
||||
'steps': w.content if isinstance(w.content, list) else (json.loads(w.content) if isinstance(w.content, str) else []),
|
||||
'module': w.module or '',
|
||||
} for w in records]
|
||||
|
||||
return self._json_response(data)
|
||||
|
||||
@@ -78,11 +78,11 @@ class UsersController(EncoachMixin, http.Controller):
|
||||
|
||||
user_type = kwargs.get('type')
|
||||
if user_type:
|
||||
domain.append(('encoach_user_type', '=', user_type))
|
||||
domain.append(('encoach_type', '=', user_type))
|
||||
|
||||
entity_id = kwargs.get('entityID')
|
||||
if entity_id:
|
||||
domain.append(('encoach_entity_id', '=', int(entity_id)))
|
||||
domain.append(('entity_rel_ids.entity_id', '=', int(entity_id)))
|
||||
|
||||
Users = request.env['res.users'].sudo()
|
||||
total = Users.search_count(domain)
|
||||
|
||||
@@ -5,10 +5,18 @@ class EncoachTicket(models.Model):
|
||||
_name = "encoach.ticket"
|
||||
_description = "EnCoach Support Ticket"
|
||||
|
||||
user_id = fields.Many2one(
|
||||
"res.users", string="Reporter", ondelete="set null", index=True,
|
||||
)
|
||||
reporter_name = fields.Char()
|
||||
reporter_email = fields.Char()
|
||||
subject = fields.Char(required=True)
|
||||
description = fields.Text()
|
||||
priority = fields.Selection(
|
||||
[("low", "Low"), ("medium", "Medium"), ("high", "High")],
|
||||
default="medium",
|
||||
)
|
||||
category = fields.Char()
|
||||
ticket_type = fields.Selection(
|
||||
[
|
||||
("feedback", "Feedback"),
|
||||
@@ -37,11 +45,15 @@ class EncoachTicket(models.Model):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"id": self.id,
|
||||
"_id": str(self.id),
|
||||
"userId": str(self.user_id.id) if self.user_id else None,
|
||||
"reporterName": self.reporter_name or "",
|
||||
"reporterEmail": self.reporter_email or "",
|
||||
"subject": self.subject or "",
|
||||
"description": self.description or "",
|
||||
"type": self.ticket_type or "",
|
||||
"priority": self.priority or "medium",
|
||||
"category": self.category or "",
|
||||
"status": self.status,
|
||||
"examInformation": self.exam_information,
|
||||
"assignedTo": (
|
||||
|
||||
@@ -6,6 +6,7 @@ class EncoachWalkthrough(models.Model):
|
||||
_description = "EnCoach Walkthrough"
|
||||
|
||||
name = fields.Char()
|
||||
sequence = fields.Integer(default=10)
|
||||
content = fields.Json()
|
||||
module = fields.Selection(
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user