Active la regle isort (I) de ruff. 45 fichiers reordonnes, aucun changement de comportement : la suite passe avant comme apres. app/models/__init__.py en est exclu. Ses imports sont ranges en onze couches commentees qui decrivent le graphe de dependances ; trier par ordre alphabetique laisse chaque titre au-dessus d un import qu il ne decrit pas, et ce fichier n a qu un role, etre lu. Commit isole, comme le formatage : un diff de brassage ne doit pas servir de couverture a un changement de comportement.
233 lines
7.9 KiB
Python
233 lines
7.9 KiB
Python
"""When people are free.
|
|
|
|
Two calendars that share a shape without sharing a purpose: a player's
|
|
weekly availability blocks, and a coach's bookable slots for one-on-one
|
|
sessions.
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from flask import flash, jsonify, redirect, render_template, request, url_for
|
|
from flask_babel import gettext as _
|
|
from flask_login import current_user, login_required
|
|
|
|
from app.extensions import db
|
|
from app.models import Coach, CoachAvailability, PlayerDisponibility, User
|
|
from app.routes.users.blueprint import users_bp
|
|
|
|
DAY_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
|
|
|
|
|
def add_30_minutes(t):
|
|
return (datetime.combine(datetime.today(), t) + timedelta(minutes=30)).time()
|
|
|
|
|
|
@users_bp.route('/disponibilities')
|
|
@login_required
|
|
def get_disponibilities():
|
|
"""API endpoint to get all player disponibilities for scheduling."""
|
|
if not current_user.can_manage_teams() and not current_user.can_schedule_matches():
|
|
return jsonify({'error': 'Unauthorized'}), 403
|
|
|
|
players = (
|
|
User.query.filter_by(role='player', is_active_account=True).order_by(User.username).all()
|
|
)
|
|
result = {}
|
|
for player in players:
|
|
disponibilities = list(player.disponibilities)
|
|
result[player.id] = {
|
|
'username': player.username,
|
|
'disponibilities': [
|
|
{
|
|
'id': d.id,
|
|
'day_of_week': d.day_of_week,
|
|
'day_name': DAY_NAMES[d.day_of_week],
|
|
'start_time': d.start_time.strftime('%H:%M'),
|
|
'end_time': d.end_time.strftime('%H:%M'),
|
|
}
|
|
for d in disponibilities
|
|
],
|
|
}
|
|
return jsonify(result)
|
|
|
|
|
|
@users_bp.route('/disponibilities/my')
|
|
@login_required
|
|
def get_my_disponibilities():
|
|
"""API endpoint for players to get their own disponibilities."""
|
|
disponibilities = PlayerDisponibility.query.filter_by(player_id=current_user.id).all()
|
|
result = {}
|
|
for d in disponibilities:
|
|
day = d.day_of_week
|
|
if day not in result:
|
|
result[day] = []
|
|
result[day].append(
|
|
{
|
|
'id': d.id,
|
|
'day_of_week': d.day_of_week,
|
|
'day_name': DAY_NAMES[d.day_of_week],
|
|
'start_time': d.start_time.strftime('%H:%M'),
|
|
'end_time': d.end_time.strftime('%H:%M'),
|
|
}
|
|
)
|
|
return jsonify(result)
|
|
|
|
|
|
@users_bp.route('/disponibilities/add', methods=['POST'])
|
|
@login_required
|
|
def add_disponibility():
|
|
"""Add a disponibility block for the current player."""
|
|
day_of_week = request.form.get('day_of_week', type=int)
|
|
start_time_str = request.form.get('start_time')
|
|
if day_of_week is None or day_of_week < 0 or day_of_week > 6:
|
|
return jsonify({'error': 'Invalid day of week'}), 400
|
|
try:
|
|
start_time = datetime.strptime(start_time_str, '%H:%M').time()
|
|
except (ValueError, TypeError):
|
|
return jsonify({'error': 'Invalid time format'}), 400
|
|
|
|
end_time = add_30_minutes(start_time)
|
|
disponibility = PlayerDisponibility(
|
|
player_id=current_user.id,
|
|
day_of_week=day_of_week,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
)
|
|
db.session.add(disponibility)
|
|
db.session.commit()
|
|
return jsonify(
|
|
{
|
|
'id': disponibility.id,
|
|
'day_of_week': disponibility.day_of_week,
|
|
'day_name': DAY_NAMES[disponibility.day_of_week],
|
|
'start_time': disponibility.start_time.strftime('%H:%M'),
|
|
'end_time': disponibility.end_time.strftime('%H:%M'),
|
|
}
|
|
)
|
|
|
|
|
|
@users_bp.route('/disponibilities/add_bulk', methods=['POST'])
|
|
@login_required
|
|
def add_disponibilities_bulk():
|
|
"""Add multiple disponibility blocks at once."""
|
|
data = request.get_json()
|
|
slots = data.get('slots', [])
|
|
created = []
|
|
for slot in slots:
|
|
day_of_week = slot.get('day_of_week')
|
|
start_time_str = slot.get('start_time')
|
|
if day_of_week is None or day_of_week < 0 or day_of_week > 6:
|
|
continue
|
|
try:
|
|
start_time = datetime.strptime(start_time_str, '%H:%M').time()
|
|
except (ValueError, TypeError):
|
|
continue
|
|
|
|
end_time = add_30_minutes(start_time)
|
|
existing = PlayerDisponibility.query.filter_by(
|
|
player_id=current_user.id,
|
|
day_of_week=day_of_week,
|
|
start_time=start_time,
|
|
).first()
|
|
if not existing:
|
|
disponibility = PlayerDisponibility(
|
|
player_id=current_user.id,
|
|
day_of_week=day_of_week,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
)
|
|
db.session.add(disponibility)
|
|
db.session.flush()
|
|
created.append(
|
|
{
|
|
'id': disponibility.id,
|
|
'day_of_week': disponibility.day_of_week,
|
|
'day_name': DAY_NAMES[disponibility.day_of_week],
|
|
'start_time': disponibility.start_time.strftime('%H:%M'),
|
|
}
|
|
)
|
|
db.session.commit()
|
|
return jsonify({'success': True, 'created': created})
|
|
|
|
|
|
@users_bp.route('/disponibilities/clear', methods=['POST'])
|
|
@login_required
|
|
def clear_disponibilities():
|
|
"""Clear all disponibilities for the current player."""
|
|
PlayerDisponibility.query.filter_by(player_id=current_user.id).delete()
|
|
db.session.commit()
|
|
return jsonify({'success': True})
|
|
|
|
|
|
@users_bp.route('/disponibilities/<int:disponibility_id>/delete', methods=['POST'])
|
|
@login_required
|
|
def delete_disponibility(disponibility_id):
|
|
"""Delete a disponibility block."""
|
|
disponibility = PlayerDisponibility.query.get_or_404(disponibility_id)
|
|
if disponibility.player_id != current_user.id:
|
|
return jsonify({'error': 'Unauthorized'}), 403
|
|
db.session.delete(disponibility)
|
|
db.session.commit()
|
|
return jsonify({'success': True})
|
|
|
|
|
|
@users_bp.route('/coach-availability', methods=['GET', 'POST'])
|
|
@login_required
|
|
def manage_coach_availability():
|
|
"""Manage coach availability for One on One sessions."""
|
|
if not isinstance(current_user, Coach):
|
|
flash(_('Only coaches can manage availability.'), 'danger')
|
|
return redirect(url_for('main.dashboard'))
|
|
|
|
if request.method == 'POST':
|
|
data = request.get_json()
|
|
slots = data.get('slots', []) if data else []
|
|
|
|
# Clear existing availability
|
|
CoachAvailability.query.filter_by(coach_id=current_user.id).delete()
|
|
|
|
# Add new slots
|
|
for slot in slots:
|
|
day_of_week = slot.get('day_of_week')
|
|
start_time_str = slot.get('start_time')
|
|
if day_of_week is None or day_of_week < 0 or day_of_week > 6:
|
|
continue
|
|
try:
|
|
start_time = datetime.strptime(start_time_str, '%H:%M').time()
|
|
end_time = (
|
|
datetime.combine(datetime.today(), start_time) + timedelta(minutes=30)
|
|
).time()
|
|
except (ValueError, TypeError):
|
|
continue
|
|
|
|
availability = CoachAvailability(
|
|
coach_id=current_user.id,
|
|
day_of_week=day_of_week,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
)
|
|
db.session.add(availability)
|
|
|
|
db.session.commit()
|
|
return jsonify({'success': True})
|
|
|
|
existing_availability = CoachAvailability.query.filter_by(
|
|
coach_id=current_user.id,
|
|
).all()
|
|
|
|
return render_template(
|
|
'pages/coach_availability.html', existing_availability=existing_availability
|
|
)
|
|
|
|
|
|
@users_bp.route('/coach-availability/clear', methods=['POST'])
|
|
@login_required
|
|
def clear_coach_availability():
|
|
"""Clear all coach availability slots."""
|
|
if not isinstance(current_user, Coach):
|
|
return jsonify({'error': 'Unauthorized'}), 403
|
|
|
|
CoachAvailability.query.filter_by(coach_id=current_user.id).delete()
|
|
db.session.commit()
|
|
return jsonify({'success': True})
|