245 lines
11 KiB
Python
245 lines
11 KiB
Python
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/<int:assignment_id>/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/<int:assignment_id>/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/<int:submission_id>/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/<int:assignment_id>',
|
|
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,
|
|
})
|