import base64 import logging from odoo import fields, http from odoo.http import request _logger = logging.getLogger(__name__) def _require_login(path): """Redirect to login if visitor is not authenticated.""" if request.env.user._is_public(): return request.redirect(f'/web/login?redirect={path}') return None class SchoolAssignmentController(http.Controller): def _get_student_for_user(self): """Return the community.school.student record linked to the current portal user.""" partner = request.env.user.partner_id return request.env['community.school.student'].sudo().search( [('partner_id', '=', partner.id)], limit=1 ) def _get_teacher_classes(self): partner = request.env.user.partner_id return request.env['community.school.class'].sudo().search( [('teacher_id', '=', partner.id)] ) # ───────────────────────────────────────────────────────────── # TEACHER: Create / manage assignments # ───────────────────────────────────────────────────────────── @http.route('/school/assignments/teacher', type='http', auth='public', website=True) def teacher_assignment_list(self, **kwargs): redirect = _require_login('/school/assignments/teacher') if redirect: return redirect classes = self._get_teacher_classes() if not classes: return request.render('community_school.portal_no_classes', {}) assignments = request.env['community.school.assignment'].sudo().search( [('class_id', 'in', classes.ids)], order='due_date asc' ) return request.render('community_school.portal_teacher_assignment_list', { 'assignments': assignments, 'classes': classes, }) @http.route('/school/assignments/teacher/new', type='http', auth='public', website=True) def teacher_assignment_new(self, **kwargs): redirect = _require_login('/school/assignments/teacher/new') if redirect: return redirect classes = self._get_teacher_classes() if not classes: return request.render('community_school.portal_no_classes', {}) if request.httprequest.method == 'POST': class_id = int(kwargs.get('class_id', 0)) klass = classes.filtered(lambda c: c.id == class_id) if not klass: return request.redirect('/school/assignments/teacher') vals = { 'name': kwargs.get('title', '').strip(), 'class_id': class_id, 'description': kwargs.get('description', '').strip() or False, 'due_date': kwargs.get('due_date') or False, 'max_score': int(kwargs.get('max_score') or 100), 'state': 'published', } request.env['community.school.assignment'].sudo().create(vals) return request.redirect('/school/assignments/teacher') return request.render('community_school.portal_teacher_assignment_form', { 'classes': classes, 'assignment': None, }) @http.route('/school/assignments/teacher//edit', type='http', auth='public', website=True) def teacher_assignment_edit(self, assignment_id, **kwargs): redirect = _require_login(f'/school/assignments/teacher/{assignment_id}/edit') if redirect: return redirect classes = self._get_teacher_classes() assignment = request.env['community.school.assignment'].sudo().browse(assignment_id) if not assignment.exists() or assignment.class_id not in classes: return request.redirect('/school/assignments/teacher') if request.httprequest.method == 'POST': vals = { 'name': kwargs.get('title', '').strip(), 'class_id': int(kwargs.get('class_id', assignment.class_id.id)), 'description': kwargs.get('description', '').strip() or False, 'due_date': kwargs.get('due_date') or False, 'max_score': int(kwargs.get('max_score') or 100), } action = kwargs.get('action_state') if action == 'publish': vals['state'] = 'published' elif action == 'close': vals['state'] = 'closed' elif action == 'draft': vals['state'] = 'draft' assignment.write(vals) return request.redirect('/school/assignments/teacher') return request.render('community_school.portal_teacher_assignment_form', { 'classes': classes, 'assignment': assignment, }) @http.route('/school/assignments/teacher//submissions', type='http', auth='public', website=True) def teacher_view_submissions(self, assignment_id, **kwargs): redirect = _require_login(f'/school/assignments/teacher/{assignment_id}/submissions') if redirect: return redirect classes = self._get_teacher_classes() assignment = request.env['community.school.assignment'].sudo().browse(assignment_id) if not assignment.exists() or assignment.class_id not in classes: return request.redirect('/school/assignments/teacher') submissions = request.env['community.school.submission'].sudo().search( [('assignment_id', '=', assignment_id)] ) return request.render('community_school.portal_teacher_submissions', { 'assignment': assignment, 'submissions': submissions, }) @http.route('/school/assignments/teacher/submission//grade', type='http', auth='public', website=True, methods=['POST']) def teacher_grade_submission(self, submission_id, **kwargs): redirect = _require_login(f'/school/assignments/teacher/submission/{submission_id}/grade') if redirect: return redirect classes = self._get_teacher_classes() submission = request.env['community.school.submission'].sudo().browse(submission_id) if not submission.exists() or submission.assignment_id.class_id not in classes: return request.redirect('/school/assignments/teacher') score = kwargs.get('score', '').strip() feedback = kwargs.get('feedback', '').strip() vals = {'feedback': feedback or False, 'state': 'graded'} if score.isdigit(): vals['score'] = int(score) submission.write(vals) return request.redirect( f'/school/assignments/teacher/{submission.assignment_id.id}/submissions' ) # ───────────────────────────────────────────────────────────── # STUDENT: View assignments & upload submissions # ───────────────────────────────────────────────────────────── @http.route('/school/assignments/student', type='http', auth='public', website=True) def student_assignment_list(self, **kwargs): redirect = _require_login('/school/assignments/student') if redirect: return redirect student = self._get_student_for_user() if not student: return request.render('community_school.portal_no_student', {}) enrolled_class_ids = student.enrollment_ids.filtered( lambda e: e.state == 'enrolled' ).mapped('class_id').ids assignments = request.env['community.school.assignment'].sudo().search( [('class_id', 'in', enrolled_class_ids), ('state', '=', 'published')], order='due_date asc', ) # Build map of existing submissions for this student my_submissions = request.env['community.school.submission'].sudo().search( [('student_id', '=', student.id), ('assignment_id', 'in', assignments.ids)] ) submission_by_assignment = {s.assignment_id.id: s for s in my_submissions} return request.render('community_school.portal_student_assignment_list', { 'assignments': assignments, 'submission_by_assignment': submission_by_assignment, 'student': student, }) @http.route('/school/assignments/student/', type='http', auth='public', website=True) def student_assignment_detail(self, assignment_id, **kwargs): redirect = _require_login(f'/school/assignments/student/{assignment_id}') if redirect: return redirect student = self._get_student_for_user() if not student: return request.render('community_school.portal_no_student', {}) assignment = request.env['community.school.assignment'].sudo().browse(assignment_id) if not assignment.exists() or assignment.state != 'published': return request.redirect('/school/assignments/student') # Verify student is enrolled in this class enrollment = student.enrollment_ids.filtered( lambda e: e.class_id.id == assignment.class_id.id and e.state == 'enrolled' ) if not enrollment: return request.redirect('/school/assignments/student') existing_submission = request.env['community.school.submission'].sudo().search( [('assignment_id', '=', assignment_id), ('student_id', '=', student.id)], limit=1 ) if request.httprequest.method == 'POST': if existing_submission: return request.redirect(f'/school/assignments/student/{assignment_id}') notes = kwargs.get('notes', '').strip() file_upload = request.httprequest.files.get('submission_file') attachment_data = False attachment_name = False if file_upload and file_upload.filename: try: attachment_data = base64.b64encode(file_upload.read()) attachment_name = file_upload.filename except Exception: _logger.warning('Failed to read uploaded submission file') request.env['community.school.submission'].sudo().create({ 'assignment_id': assignment_id, 'enrollment_id': enrollment[0].id, 'notes': notes or False, 'attachment_data': attachment_data, 'attachment_name': attachment_name, 'submitted_on': fields.Datetime.now(), 'state': 'submitted', }) return request.redirect(f'/school/assignments/student/{assignment_id}') return request.render('community_school.portal_student_assignment_detail', { 'assignment': assignment, 'submission': existing_submission, 'student': student, })