220 lines
9.1 KiB
Python

# pyrefly: ignore [missing-import]
from odoo import http, fields
# pyrefly: ignore [missing-import]
from odoo.http import request
# pyrefly: ignore [missing-import]
from odoo.addons.portal.controllers.portal import CustomerPortal
class HostelPortalController(CustomerPortal):
"""Student portal controller for hostel management.
SECURITY DESIGN:
- Identity is ALWAYS derived from request.env.user.student_id
- URL-supplied IDs (complaint_id, etc.) are NEVER trusted for authorization
- All lookups use additional domain [('student_id', '=', student.id)]
- Record rules provide a second layer of enforcement at ORM level
sudo() AUDIT:
- sudo() is NOT used in any route below
- Portal user has read access to own records via record rules
- Create (complaint, visitor) is allowed via group_portal model access
with record-rule enforcement on student_id.portal_user_id = user.id
"""
def _get_student(self):
"""Authoritative student lookup — NEVER trust URL parameters."""
return request.env.user.student_id
def _get_hostel_member(self):
"""Returns the student's active allocation (if any)."""
student = self._get_student()
if not student:
return request.env['education.hostel.member'].browse()
return request.env['education.hostel.member'].search([
('student_id', '=', student.id),
('status', '=', 'active'),
], limit=1)
def _prepare_home_portal_values(self, counters):
values = super()._prepare_home_portal_values(counters)
student = self._get_student()
if student:
member = self._get_hostel_member()
values['hostel_member_count'] = 1 if member else 0
return values
# ── /my/hostel ────────────────────────────────────────────────────────
@http.route(['/my/hostel'], type='http', auth='user', website=True)
def hostel_dashboard(self, **kw):
student = self._get_student()
if not student:
return request.render('education_portal.portal_no_student')
member = self._get_hostel_member()
# Complaints — only student's own (record rule also enforces)
complaints = request.env['education.hostel.complaint'].search([
('student_id', '=', student.id),
], order='submitted_date desc', limit=5)
# Fees — only student's own
fees = request.env['education.hostel.fee'].search([
('student_id', '=', student.id),
('state', 'in', ('due', 'overdue')),
], order='due_date asc', limit=5)
# Recent visitors — only student's own
visitors = request.env['education.hostel.visitor'].search([
('student_id', '=', student.id),
], order='visit_date desc', limit=3)
return request.render('education_hostel.portal_hostel_dashboard', {
'student': student,
'member': member,
'complaints': complaints,
'fees': fees,
'visitors': visitors,
'page_name': 'hostel_dashboard',
})
# ── /my/hostel/allocation ─────────────────────────────────────────────
@http.route(['/my/hostel/allocation'], type='http', auth='user', website=True)
def hostel_allocation(self, **kw):
student = self._get_student()
if not student:
return request.render('education_portal.portal_no_student')
member = self._get_hostel_member()
if not member:
return request.render('education_hostel.portal_hostel_no_allocation')
return request.render('education_hostel.portal_hostel_allocation', {
'student': student,
'member': member,
'page_name': 'hostel_allocation',
})
# ── /my/hostel/complaints ─────────────────────────────────────────────
@http.route(['/my/hostel/complaints'], type='http', auth='user', website=True)
def hostel_complaints(self, **kw):
student = self._get_student()
if not student:
return request.render('education_portal.portal_no_student')
complaints = request.env['education.hostel.complaint'].search([
('student_id', '=', student.id),
], order='submitted_date desc')
return request.render('education_hostel.portal_hostel_complaints', {
'student': student,
'complaints': complaints,
'page_name': 'hostel_complaints',
})
# ── /my/hostel/complaint/new ──────────────────────────────────────────
@http.route(['/my/hostel/complaint/new'], type='http', auth='user', website=True,
methods=['GET', 'POST'])
def hostel_complaint_new(self, **post):
student = self._get_student()
if not student:
return request.render('education_portal.portal_no_student')
member = self._get_hostel_member()
if not member:
return request.redirect('/my/hostel')
if request.httprequest.method == 'POST':
title = post.get('title', '').strip()
category = post.get('category', 'other')
description = post.get('description', '').strip()
if not title:
return request.render('education_hostel.portal_hostel_complaint_form', {
'student': student,
'member': member,
'error': 'Complaint title is required.',
'post': post,
'page_name': 'hostel_complaint_new',
})
# Create complaint — student_id is set server-side, NEVER from POST
complaint = request.env['education.hostel.complaint'].create({
'student_id': student.id, # server-derived identity
'hostel_id': member.hostel_id.id,
'room_id': member.room_id.id,
'category': category,
'title': title,
'description': description,
'state': 'submitted',
'submitted_date': fields.Date.today(),
'company_id': student.company_id.id,
})
return request.redirect('/my/hostel/complaints')
return request.render('education_hostel.portal_hostel_complaint_form', {
'student': student,
'member': member,
'page_name': 'hostel_complaint_new',
})
# ── /my/hostel/fees ───────────────────────────────────────────────────
@http.route(['/my/hostel/fees'], type='http', auth='user', website=True)
def hostel_fees(self, **kw):
student = self._get_student()
if not student:
return request.render('education_portal.portal_no_student')
fees = request.env['education.hostel.fee'].search([
('student_id', '=', student.id),
], order='due_date desc')
return request.render('education_hostel.portal_hostel_fees', {
'student': student,
'fees': fees,
'page_name': 'hostel_fees',
})
# ── /my/hostel/visitors ───────────────────────────────────────────────
@http.route(['/my/hostel/visitors'], type='http', auth='user', website=True,
methods=['GET', 'POST'])
def hostel_visitors(self, **post):
student = self._get_student()
if not student:
return request.render('education_portal.portal_no_student')
member = self._get_hostel_member()
if request.httprequest.method == 'POST' and member:
visitor_name = post.get('visitor_name', '').strip()
if visitor_name:
request.env['education.hostel.visitor'].create({
'student_id': student.id, # server-derived identity
'hostel_id': member.hostel_id.id,
'visitor_name': visitor_name,
'relationship': post.get('relationship', '').strip(),
'phone': post.get('phone', '').strip(),
'purpose': post.get('purpose', '').strip(),
'visit_date': post.get('visit_date') or fields.Date.today(),
'state': 'pending',
'company_id': student.company_id.id,
})
return request.redirect('/my/hostel/visitors')
visitors = request.env['education.hostel.visitor'].search([
('student_id', '=', student.id),
], order='visit_date desc')
return request.render('education_hostel.portal_hostel_visitors', {
'student': student,
'member': member,
'visitors': visitors,
'page_name': 'hostel_visitors',
})