bemade-addons/bemade_sports_clinic/controllers/player_management_portal.py
Denis Durepos 53a027c87c feat: Production readiness sanitization and access control refactoring
- Remove all debug logging and statements for production deployment
- Convert operational logging from info to debug level where appropriate
- Clean up debug test files and commented code
- Refactor access control helpers into centralized AccessControlMixin
- Consolidate duplicated access control methods across controllers
- Enforce team-based access control for all portal users
- Fix access control logic to match expected security behavior
- Move TODO.md to notes/ directory for better organization
- All 76 tests passing with proper security enforcement

Production ready: Clean codebase with centralized access control and no debug noise
2025-07-31 14:55:18 -04:00

363 lines
15 KiB
Python

import logging
from odoo import http, fields, _
from odoo.exceptions import UserError, ValidationError
from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal, pager
from .access_control_mixin import AccessControlMixin
_logger = logging.getLogger(__name__)
class PlayerManagementPortal(CustomerPortal, AccessControlMixin):
"""Controller for player management functionality in the portal"""
# Access control methods now inherited from AccessControlMixin
@http.route(['/my/player/edit'], type='http', auth='user', website=True)
def edit_player_form(self, patient_id, **post):
"""Show form to edit player information"""
try:
patient = self._check_access_to_patient(patient_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return_url = post.get('return_url', f'/my/player?player_id={patient_id}')
# Check if user is a treatment professional
user = request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
teams = patient.team_ids
# Create a dictionary with patient info for protected fields
patient_info = {}
# Only include protected fields if user has appropriate permissions
if is_treatment_prof:
# Access fields directly - field-level security is already defined
# with appropriate groups for each field
# Additional fields available to treatment professionals
# Basic fields
patient_info['date_of_birth'] = patient.date_of_birth
patient_info['age'] = patient.age
patient_info['allergies'] = patient.allergies
patient_info['team_info_notes'] = patient.team_info_notes
# Status fields
patient_info['match_status'] = patient.match_status
patient_info['practice_status'] = patient.practice_status
# Injury tracking fields
patient_info['injured_since'] = patient.injured_since
# Add any other protected fields that should be available to treatment professionals
# You can add more fields here as needed
# Patient info prepared for treatment professional view
# Get Canada and Canadian provinces/territories for address dropdowns
canada = request.env['res.country'].search([('code', '=', 'CA')], limit=1)
states = request.env['res.country.state'].search([('country_id', '=', canada.id)], order='name') if canada else request.env['res.country.state']
countries = canada if canada else request.env['res.country'].search([], order='name')
values = {
'patient': patient, # Keep original patient
'patient_info': patient_info, # Add patient_info for protected fields
'teams': teams,
'states': states,
'countries': countries,
'return_url': return_url,
'page_name': 'edit_player',
'is_treatment_prof': is_treatment_prof,
}
return request.render('bemade_sports_clinic.portal_edit_player', values)
@http.route(['/my/player/save'], type='http', auth='user', website=True, methods=['POST'])
def edit_player_submit(self, **post):
"""Process the form submission to update player information"""
patient_id = post.get('patient_id')
if not patient_id:
return request.redirect('/my/players')
try:
patient = self._check_access_to_patient(patient_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
# Check if user is a treatment professional
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Prepare values for patient update
vals = {}
# Basic information - any portal user with access can update these
if post.get('first_name') and post.get('last_name'):
vals.update({
'first_name': post.get('first_name'),
'last_name': post.get('last_name'),
})
# Contact information
if post.get('email'):
vals.update({
'email': post.get('email'),
})
if post.get('phone'):
vals.update({
'phone': post.get('phone'),
})
# Address information - any portal user with access can update these
address_fields = ['street', 'street2', 'city', 'zip']
for field in address_fields:
if field in post:
vals[field] = post.get(field) or False
# Handle state and country selections
if post.get('state_id'):
try:
state_id = int(post.get('state_id'))
vals['state_id'] = state_id
except (ValueError, TypeError):
pass # Invalid state_id, skip
if post.get('country_id'):
try:
country_id = int(post.get('country_id'))
vals['country_id'] = country_id
except (ValueError, TypeError):
pass # Invalid country_id, skip
# Additional fields that only treatment professionals can update
if is_treatment_prof:
if post.get('date_of_birth'):
vals.update({
'date_of_birth': post.get('date_of_birth'),
})
# Medical information
if 'allergies' in post:
vals.update({
'allergies': post.get('allergies') or False,
})
if 'team_info_notes' in post:
vals.update({
'team_info_notes': post.get('team_info_notes') or False,
})
# Status fields
if post.get('match_status'):
vals.update({
'match_status': post.get('match_status'),
})
if post.get('practice_status'):
vals.update({
'practice_status': post.get('practice_status'),
})
# Update the patient - no sudo needed as field-level security is in place
if vals:
patient.write(vals)
return request.redirect(f'/my/player?player_id={patient_id}')
@http.route(['/my/player/contact/add'], type='http', auth='user', website=True)
def add_contact_form(self, patient_id, **post):
"""Show form to add a new emergency contact for a player"""
try:
patient = self._check_access_to_patient(patient_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return_url = post.get('return_url', f'/my/player?player_id={patient_id}')
# Check if user is a treatment professional
user = request.env.user
is_treatment_prof = user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Regular coaches shouldn't be able to add emergency contacts
if not is_treatment_prof:
return request.redirect(return_url)
values = {
'patient': patient,
'return_url': return_url,
'page_name': 'add_contact',
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
}
return request.render('bemade_sports_clinic.portal_add_contact', values)
@http.route(['/my/player/contact/save'], type='http', auth='user', website=True, methods=['POST'])
def add_contact_submit(self, **post):
"""Process the form submission to add a new emergency contact"""
patient_id = post.get('patient_id')
if not patient_id:
return request.redirect('/my/players')
try:
patient = self._check_access_to_patient(patient_id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
# Check if user is a treatment professional
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Regular coaches shouldn't be able to add emergency contacts
if not is_treatment_prof:
return request.redirect(f'/my/player?player_id={patient_id}')
# Required fields
if not post.get('name') or not post.get('contact_type'):
values = {
'patient': patient,
'error': _("Name and contact type are required fields"),
'return_url': f'/my/player?player_id={patient_id}',
'page_name': 'add_contact',
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
}
values.update(post)
return request.render('bemade_sports_clinic.portal_add_contact', values)
# Prepare values for contact creation
vals = {
'patient_id': int(patient_id),
'name': post.get('name'),
'contact_type': post.get('contact_type'),
}
# Optional fields
if post.get('mobile'):
vals['mobile'] = post.get('mobile')
if post.get('email'):
vals['email'] = post.get('email')
# Create the contact
request.env['sports.patient.contact'].sudo().create(vals)
return request.redirect(f'/my/player?player_id={patient_id}')
@http.route(['/my/player/contact/edit'], type='http', auth='user', website=True)
def edit_contact_form(self, contact_id, **post):
"""Show form to edit an existing emergency contact"""
contact = request.env['sports.patient.contact'].browse(int(contact_id))
if not contact.exists():
return request.redirect('/my/players')
try:
patient = self._check_access_to_patient(contact.patient_id.id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
return_url = post.get('return_url', f'/my/player?player_id={patient.id}')
# Check if user is a treatment professional
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Regular coaches shouldn't be able to edit emergency contacts
if not is_treatment_prof:
return request.redirect(return_url)
values = {
'patient': patient,
'contact': contact,
'return_url': return_url,
'page_name': 'edit_contact',
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
}
return request.render('bemade_sports_clinic.portal_edit_contact', values)
@http.route(['/my/player/contact/update'], type='http', auth='user', website=True, methods=['POST'])
def edit_contact_submit(self, **post):
"""Process the form submission to update an emergency contact"""
contact_id = post.get('contact_id')
if not contact_id:
return request.redirect('/my/players')
contact = request.env['sports.patient.contact'].browse(int(contact_id))
if not contact.exists():
return request.redirect('/my/players')
try:
patient = self._check_access_to_patient(contact.patient_id.id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
# Check if user is a treatment professional
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Regular coaches shouldn't be able to edit emergency contacts
if not is_treatment_prof:
return request.redirect(f'/my/player?player_id={patient.id}')
# Required fields
if not post.get('name') or not post.get('contact_type'):
values = {
'patient': patient,
'contact': contact,
'error': _("Name and contact type are required fields"),
'return_url': f'/my/player?player_id={patient.id}',
'page_name': 'edit_contact',
'relationship_types': request.env['sports.patient.contact']._fields['contact_type'].selection,
}
values.update(post)
return request.render('bemade_sports_clinic.portal_edit_contact', values)
# Prepare values for contact update
vals = {
'name': post.get('name'),
'contact_type': post.get('contact_type'),
}
# Optional fields
if post.get('mobile'):
vals['mobile'] = post.get('mobile')
else:
vals['mobile'] = False
if post.get('email'):
vals['email'] = post.get('email')
else:
vals['email'] = False
# Update the contact
contact.sudo().write(vals)
return request.redirect(f'/my/player?player_id={patient.id}')
@http.route(['/my/player/contact/delete'], type='http', auth='user', website=True, methods=['POST'])
def delete_contact(self, contact_id, **post):
"""Delete an emergency contact"""
contact = request.env['sports.patient.contact'].browse(int(contact_id))
if not contact.exists():
return request.redirect('/my/players')
try:
patient = self._check_access_to_patient(contact.patient_id.id)
except UserError as e:
return request.render('portal.403', {'error': str(e)})
# Check if user is a treatment professional
is_treatment_prof = request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')
# Regular coaches shouldn't be able to delete emergency contacts
if not is_treatment_prof:
return request.redirect(f'/my/player?player_id={patient.id}')
# Delete the contact
contact.sudo().unlink()
return request.redirect(f'/my/player?player_id={patient.id}')