164 lines
6.0 KiB
Python

from datetime import datetime
from flask import Flask, render_template, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import func
import os
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL')
db = SQLAlchemy(app)
# Models
class Player(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False, unique=True)
class Date(db.Model):
id = db.Column(db.Integer, primary_key=True)
date_str = db.Column(db.String(20), nullable=False, unique=True) # e.g. '08/05/25'
class Attendance(db.Model):
"""
Attendance record for a player on a date.
status: 'yes', 'no', 'maybe', or blank (None)
"""
id = db.Column(db.Integer, primary_key=True)
date_id = db.Column(db.Integer, db.ForeignKey('date.id'), nullable=False)
player_id = db.Column(db.Integer, db.ForeignKey('player.id'), nullable=False)
status = db.Column(db.String(10)) # 'yes', 'no', 'maybe', or blank
__table_args__ = (db.UniqueConstraint('date_id', 'player_id', name='_date_player_uc'),)
class GuestName(db.Model):
id = db.Column(db.Integer, primary_key=True)
date_id = db.Column(db.Integer, db.ForeignKey('date.id'), nullable=False, unique=True)
name = db.Column(db.String(50), nullable=False)
def get_initial_data():
# Ensure tables exist
db.create_all()
# If no players, insert defaults
if Player.query.count() == 0:
for name in ["Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Hannah"]:
db.session.add(Player(name=name))
db.session.commit()
if not GuestName.query.first():
# Set default guest name for all dates
pass
def parse_date(date_str):
return datetime.strptime(date_str, '%d/%m/%y')
def db_to_json():
players = [p.name for p in Player.query.order_by(Player.id)]
guest = "Guest"
dates = sorted([d.date_str for d in Date.query.all()], key=parse_date)
attendance = {}
for att in Attendance.query.all():
date = Date.query.get(att.date_id).date_str
player = Player.query.get(att.player_id)
colIdx = players.index(player.name) if player.name in players else len(players) # guest is last
key = f"{date}|{colIdx}"
if att.status == 'yes':
attendance[key] = True
elif att.status == 'no':
attendance[key] = 'no'
elif att.status == 'maybe':
attendance[key] = 'maybe'
guest_names = {Date.query.get(g.date_id).date_str: g.name for g in GuestName.query.all()}
return {
"players": players,
"guest": guest,
"dates": dates,
"attendance": attendance,
"guestNames": guest_names
}
def json_to_db(data):
try:
# Start a single transaction for all operations
with db.session.begin():
# Clear existing attendance records
db.session.query(Attendance).delete()
db.session.query(GuestName).delete()
# Update players - keep existing ones, add new ones
existing_players = {p.name: p for p in Player.query.all()}
for name in data.get("players", []):
if name not in existing_players:
db.session.add(Player(name=name))
# Update dates - keep existing ones, add new ones
existing_dates = {d.date_str: d for d in Date.query.all()}
for date_str in data.get("dates", []):
if date_str not in existing_dates:
date = Date(date_str=date_str)
db.session.add(date)
existing_dates[date_str] = date
# Refresh player list after potential additions
player_dict = {p.name: p for p in Player.query.all()}
# Add attendance records
for date_str in data.get("dates", []):
date = existing_dates[date_str]
for idx, player_name in enumerate(data["players"] + [data["guest"]]):
key = f"{date_str}|{idx}"
status = data["attendance"].get(key)
if status and player_name in player_dict:
player = player_dict[player_name]
# Convert boolean True to string 'yes'
status_value = 'yes' if status is True else status
# Only add if status is one of the valid values
if status_value in ['yes', 'no', 'maybe']:
db.session.add(Attendance(date_id=date.id, player_id=player.id, status=status_value))
# Add guest name if present
guest_name = data.get("guestNames", {}).get(date_str)
if guest_name:
db.session.add(GuestName(date_id=date.id, name=guest_name))
# The transaction will be committed automatically if no exceptions occur
except Exception as e:
# The transaction will be rolled back automatically if an exception occurs
print("Error during database operation:", e)
raise
@app.route('/')
def index():
return render_template('index.html')
@app.route('/reports')
def reports():
return render_template('reports.html')
@app.route('/api/data', methods=['GET'])
def get_data():
get_initial_data()
return jsonify(db_to_json())
@app.route('/api/data', methods=['POST'])
def update_data():
data = request.json
json_to_db(data)
return jsonify({"status": "success"})
# Optional: export from DB as JSON (for compatibility)
@app.route('/export-data')
def export_data():
from flask import Response
get_initial_data()
data = db_to_json()
return Response(
response=json.dumps(data, indent=2),
mimetype='application/json',
headers={'Content-Disposition': 'attachment;filename=attendance_data.json'}
)
if __name__ == '__main__':
import os
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port, debug=False)