From 0c60a4b4d876246f68997c84f3aacc7460d05e09 Mon Sep 17 00:00:00 2001 From: Jonny Ervine Date: Sat, 4 Oct 2025 16:47:40 +0800 Subject: [PATCH] Add extra DB functions --- motm_app/main.py | 228 ++++++++++++++++++-- motm_app/templates/admin_dashboard.html | 8 + motm_app/templates/motm_management.html | 272 ++++++++++++++++++++++++ 3 files changed, 489 insertions(+), 19 deletions(-) create mode 100644 motm_app/templates/motm_management.html diff --git a/motm_app/main.py b/motm_app/main.py index 7dcd533..6c9ecf8 100644 --- a/motm_app/main.py +++ b/motm_app/main.py @@ -111,7 +111,7 @@ def motm_vote(randomUrlSuffix): # Get match comments sql5 = text("SELECT comment FROM _motmcomments WHERE matchDate = :match_date ORDER BY RANDOM() LIMIT 1") - comment_result = sql_read(sql5, {'match_date': nextInfo[0]['nextdate']}) + comment_result = sql_read(sql5, {'match_date': nextInfo[0]['nextdate'].strftime('%Y-%m-%d')}) comment = comment_result[0]['comment'] if comment_result else "No comments added yet" form = motmForm() @@ -120,7 +120,9 @@ def motm_vote(randomUrlSuffix): if nextInfo and nextInfo[0].get('motmurlsuffix'): randomSuff = nextInfo[0]['motmurlsuffix'] if randomSuff == randomUrlSuffix: - return render_template('motm_vote.html', data=rows, comment=comment, formatDate=formatDate, matchNumber=nextInfo[0].get('nextfixture', ''), oppo=oppo, hkfcLogo=hkfcLogo, oppoLogo=oppoLogo, dotdURL=dotdURL, motmURL=motmURL, form=form) + # Use nextdate to generate proper match number instead of nextfixture + match_number = nextInfo[0]['nextdate'].strftime('%Y-%m-%d') if nextInfo[0]['nextdate'] else '' + return render_template('motm_vote.html', data=rows, comment=comment, formatDate=formatDate, matchNumber=match_number, oppo=oppo, hkfcLogo=hkfcLogo, oppoLogo=oppoLogo, dotdURL=dotdURL, motmURL=motmURL, form=form) else: return render_template('error.html', message="Invalid voting URL. Please use the correct URL provided by the admin.") else: @@ -144,8 +146,8 @@ def match_comments(): _comment = request.form['matchComment'] if _comment != 'Optional comments added here': _fixed_comment = _comment.replace("'", "\\'") - sql3 = text("INSERT INTO _motmcomments (matchDate, opposition, comment) VALUES (:comment_date, :opposition, :comment)") - sql_write(sql3, {'comment_date': commentDate, 'opposition': _oppo, 'comment': _fixed_comment}) + sql3 = text("INSERT INTO _motmcomments (matchDate, comment) VALUES (:comment_date, :comment)") + sql_write(sql3, {'comment_date': commentDate, 'comment': _fixed_comment}) sql = text("SELECT comment FROM _motmcomments WHERE matchDate = :match_date ORDER BY RANDOM()") comments = sql_read(sql, {'match_date': _matchDate}) return render_template('match_comments.html', comments=comments, hkfcLogo=hkfcLogo, oppoLogo=oppoLogo) @@ -188,30 +190,28 @@ def vote_thanks(): motm_name = motm_player[0]['playernickname'] if motm_player else f'Player {_motm}' dotd_name = dotd_player[0]['playernickname'] if dotd_player else f'Player {_dotd}' - # Update MOTM vote - use PostgreSQL UPSERT syntax + # Update MOTM vote - use PostgreSQL UPSERT syntax (don't update totals) sql_motm = text(f""" - INSERT INTO _hkfc_c_motm (playernumber, playername, motmtotal, {motm_col}) - VALUES (:player_num, :player_name, 1, 1) + INSERT INTO _hkfc_c_motm (playernumber, playername, {motm_col}) + VALUES (:player_num, :player_name, 1) ON CONFLICT (playernumber) DO UPDATE SET - motmTotal = _hkfc_c_motm.motmTotal + 1, {motm_col} = _hkfc_c_motm.{motm_col} + 1 """) sql_write(sql_motm, {'player_num': _motm, 'player_name': motm_name}) - # Update DotD vote - use PostgreSQL UPSERT syntax + # Update DotD vote - use PostgreSQL UPSERT syntax (don't update totals) sql_dotd = text(f""" - INSERT INTO _hkfc_c_motm (playernumber, playername, dotdtotal, {dotd_col}) - VALUES (:player_num, :player_name, 1, 1) + INSERT INTO _hkfc_c_motm (playernumber, playername, {dotd_col}) + VALUES (:player_num, :player_name, 1) ON CONFLICT (playernumber) DO UPDATE SET - dotdTotal = _hkfc_c_motm.dotdTotal + 1, {dotd_col} = _hkfc_c_motm.{dotd_col} + 1 """) sql_write(sql_dotd, {'player_num': _dotd, 'player_name': dotd_name}) # Handle comments if _comments and _comments != "Optional comments added here": - sql3 = text("INSERT INTO _motmcomments (matchDate, opposition, comment) VALUES (:match_date, :opposition, :comment)") - sql_write(sql3, {'match_date': _matchDate, 'opposition': _oppo, 'comment': _fixed_comments}) + sql3 = text("INSERT INTO _motmcomments (matchDate, comment) VALUES (:match_date, :comment)") + sql_write(sql3, {'match_date': _matchDate, 'comment': _fixed_comments}) return render_template('vote_thanks.html') else: @@ -919,6 +919,174 @@ def club_selection(): return render_template('club_selection.html', form=form, clubs=clubs, selected_clubs=[]) +@app.route('/admin/motm/manage', methods=['GET', 'POST']) +@basic_auth.required +def motm_management(): + """Manage MOTM/DotD counts and reset functionality""" + + if request.method == 'POST': + action = request.form.get('action') + + if action == 'reset_fixture': + fixture_date = request.form.get('fixture_date') + if fixture_date: + motm_col = f'motm_{fixture_date}' + dotd_col = f'dotd_{fixture_date}' + + # Reset fixture-specific columns + sql_reset = text(f""" + UPDATE _hkfc_c_motm + SET {motm_col} = 0, {dotd_col} = 0 + WHERE {motm_col} > 0 OR {dotd_col} > 0 + """) + sql_write(sql_reset) + flash(f'Reset MOTM/DotD counts for fixture {fixture_date}', 'success') + + elif action == 'reset_totals': + # Reset all total columns + sql_reset_totals = text(""" + UPDATE _hkfc_c_motm + SET motmtotal = 0, dotdtotal = 0, assiststotal = 0, goalstotal = 0 + """) + sql_write(sql_reset_totals) + flash('Reset all MOTM/DotD totals', 'success') + + elif action == 'reset_all': + # Reset everything - dynamically reset all fixture columns + sql_columns = text(""" + SELECT column_name + FROM information_schema.columns + WHERE table_name = '_hkfc_c_motm' + AND (column_name LIKE 'motm_%' OR column_name LIKE 'dotd_%') + AND column_name NOT LIKE '%total' + """) + columns = sql_read(sql_columns) + + # Build dynamic SET clause + set_clauses = ['motmtotal = 0', 'dotdtotal = 0', 'assiststotal = 0', 'goalstotal = 0'] + for col in columns: + set_clauses.append(f"{col['column_name']} = 0") + + sql_reset_all = text(f""" + UPDATE _hkfc_c_motm + SET {', '.join(set_clauses)} + """) + sql_write(sql_reset_all) + flash('Reset all MOTM/DotD data', 'success') + + elif action == 'reset_player_fixture': + player_number = request.form.get('player_number') + fixture_date = request.form.get('fixture_date') + if player_number and fixture_date: + motm_col = f'motm_{fixture_date}' + dotd_col = f'dotd_{fixture_date}' + + # Reset specific player's fixture counts + sql_reset_player = text(f""" + UPDATE _hkfc_c_motm + SET {motm_col} = 0, {dotd_col} = 0 + WHERE playernumber = :player_number + """) + sql_write(sql_reset_player, {'player_number': player_number}) + flash(f'Reset MOTM/DotD counts for player #{player_number} in fixture {fixture_date}', 'success') + + elif action == 'drop_column': + column_name = request.form.get('column_name') + if column_name: + try: + sql_drop = text(f"ALTER TABLE _hkfc_c_motm DROP COLUMN {column_name}") + sql_write(sql_drop) + flash(f'Successfully dropped column {column_name}', 'success') + except Exception as e: + flash(f'Error dropping column {column_name}: {str(e)}', 'error') + + elif action == 'drop_fixture_columns': + fixture_date = request.form.get('fixture_date') + if fixture_date: + motm_col = f'motm_{fixture_date}' + dotd_col = f'dotd_{fixture_date}' + try: + sql_drop_motm = text(f"ALTER TABLE _hkfc_c_motm DROP COLUMN {motm_col}") + sql_drop_dotd = text(f"ALTER TABLE _hkfc_c_motm DROP COLUMN {dotd_col}") + sql_write(sql_drop_motm) + sql_write(sql_drop_dotd) + flash(f'Successfully dropped columns for fixture {fixture_date}', 'success') + except Exception as e: + flash(f'Error dropping fixture columns: {str(e)}', 'error') + + elif action == 'sync_totals': + # Sync stored totals with calculated totals + sql_data = text("SELECT * FROM _hkfc_c_motm") + players = sql_read(sql_data) + + updated_count = 0 + for player in players: + motm_total = 0 + dotd_total = 0 + + # Calculate totals from fixture columns + for col_name in player.keys(): + if col_name.startswith('motm_') and col_name != 'motmtotal': + motm_total += player[col_name] or 0 + elif col_name.startswith('dotd_') and col_name != 'dotdtotal': + dotd_total += player[col_name] or 0 + + # Update stored totals if they differ + if player.get('motmtotal', 0) != motm_total or player.get('dotdtotal', 0) != dotd_total: + sql_update = text("UPDATE _hkfc_c_motm SET motmtotal = :motm_total, dotdtotal = :dotd_total WHERE playernumber = :player_num") + sql_write(sql_update, { + 'motm_total': motm_total, + 'dotd_total': dotd_total, + 'player_num': player['playernumber'] + }) + updated_count += 1 + + flash(f'Synced totals for {updated_count} players', 'success') + + # Get all fixture columns + sql_columns = text(""" + SELECT column_name + FROM information_schema.columns + WHERE table_name = '_hkfc_c_motm' + AND column_name LIKE 'motm_%' OR column_name LIKE 'dotd_%' + ORDER BY column_name + """) + columns = sql_read(sql_columns) + + # Extract unique fixture dates + fixture_dates = set() + for col in columns: + if col['column_name'].startswith('motm_') or col['column_name'].startswith('dotd_'): + date_part = col['column_name'].split('_')[1] + if date_part != 'total': + fixture_dates.add(date_part) + + fixture_dates = sorted(fixture_dates, reverse=True) + + # Get current data with dynamic totals + sql_data = text("SELECT * FROM _hkfc_c_motm ORDER BY playernumber") + motm_data = sql_read(sql_data) + + # Calculate dynamic totals for each player + for player in motm_data: + motm_total = 0 + dotd_total = 0 + + # Sum all fixture-specific columns + for col_name in player.keys(): + if col_name.startswith('motm_') and col_name != 'motmtotal': + motm_total += player[col_name] or 0 + elif col_name.startswith('dotd_') and col_name != 'dotdtotal': + dotd_total += player[col_name] or 0 + + player['calculated_motmtotal'] = motm_total + player['calculated_dotdtotal'] = dotd_total + + return render_template('motm_management.html', + fixture_dates=fixture_dates, + motm_data=motm_data) + + @app.route('/admin/squad/submit', methods=['POST']) @basic_auth.required def match_squad_submit(): @@ -1263,12 +1431,34 @@ def vote_results(): @app.route('/api/poty-results') def poty_results(): - """API endpoint for Player of the Year results""" - sql = text("SELECT playername, motmtotal, dotdtotal FROM _hkfc_c_motm WHERE (motmtotal > 0) OR (dotdtotal > 0)") - print(f"SQL: {sql}") + """API endpoint for Player of the Year results with dynamic totals""" + # Get all players with their fixture-specific columns + sql = text("SELECT * FROM _hkfc_c_motm") rows = sql_read(sql) - print(f"Results: {rows}") - return jsonify(rows) + + # Calculate dynamic totals for each player + results = [] + for player in rows: + motm_total = 0 + dotd_total = 0 + + # Sum all fixture-specific columns + for col_name in player.keys(): + if col_name.startswith('motm_') and col_name != 'motmtotal': + motm_total += player[col_name] or 0 + elif col_name.startswith('dotd_') and col_name != 'dotdtotal': + dotd_total += player[col_name] or 0 + + # Only include players with votes + if motm_total > 0 or dotd_total > 0: + results.append({ + 'playername': player['playername'], + 'motmtotal': motm_total, + 'dotdtotal': dotd_total + }) + + print(f"Dynamic POTY Results: {results}") + return jsonify(results) @app.route('/admin/voting') diff --git a/motm_app/templates/admin_dashboard.html b/motm_app/templates/admin_dashboard.html index edbebb7..646f0a0 100644 --- a/motm_app/templates/admin_dashboard.html +++ b/motm_app/templates/admin_dashboard.html @@ -144,6 +144,14 @@ +
+ +
diff --git a/motm_app/templates/motm_management.html b/motm_app/templates/motm_management.html new file mode 100644 index 0000000..a1423db --- /dev/null +++ b/motm_app/templates/motm_management.html @@ -0,0 +1,272 @@ + + + + + + MOTM Management - HKFC Men's C Team + + + + +
+
+
+

MOTM/DotD Management

+

Manage and reset Man of the Match and Dick of the Day counts

+ +
+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} + + +
+

Reset Controls

+

Use these controls to reset MOTM/DotD counts for specific fixtures or all data.

+ +
+
+
Reset Specific Fixture
+
+ +
+ +
+ +
+
+ +
+
Reset All Totals
+

Reset motmtotal, dotdtotal, assiststotal, goalstotal columns

+
+ + +
+
+ +
+
Reset Everything
+

Reset all MOTM/DotD data including fixture-specific columns

+
+ + +
+
+
+ +
+
+
Sync Totals
+

Update stored totals to match calculated values from fixture columns

+
+ + +
+
+
+
+ + +
+

Current MOTM/DotD Data

+ + {% if motm_data %} +
+ + + + + + + + + + {% for date in fixture_dates %} + + + {% endfor %} + + + + {% for player in motm_data %} + + + + + + + + {% for date in fixture_dates %} + + + {% endfor %} + + {% endfor %} + +
Player #Player NameMOTM TotalDotD TotalGoals TotalAssists TotalMOTM {{ date }}DotD {{ date }}
{{ player.playernumber }}{{ player.playername }} + {{ player.calculated_motmtotal or 0 }} + {% if player.motmtotal != player.calculated_motmtotal %} + (stored: {{ player.motmtotal or 0 }}) + {% endif %} + + {{ player.calculated_dotdtotal or 0 }} + {% if player.dotdtotal != player.calculated_dotdtotal %} + (stored: {{ player.dotdtotal or 0 }}) + {% endif %} + + {{ player.goalstotal or 0 }} + + {{ player.assiststotal or 0 }} + + {% set motm_col = 'motm_' + date %} + {% set dotd_col = 'dotd_' + date %} + {% if player[motm_col] and player[motm_col] > 0 %} + {{ player[motm_col] }} + + {% else %} + 0 + {% endif %} + + {% if player[dotd_col] and player[dotd_col] > 0 %} + {{ player[dotd_col] }} + + {% else %} + 0 + {% endif %} +
+
+ {% else %} +
+
No MOTM/DotD data found
+

There is no data in the _hkfc_c_motm table. This might be because no votes have been cast yet.

+
+ {% endif %} +
+ + +
+

Column Management

+

Drop unwanted columns from the _hkfc_c_motm table.

+ +
+
+
Drop Specific Column
+
+ +
+ +
+ +
+
+ +
+
Drop Fixture Columns
+
+ +
+ +
+ +
+
+
+
+ + +
+

Available Fixtures

+ {% if fixture_dates %} +

The following fixture dates have MOTM/DotD columns in the database:

+
    + {% for date in fixture_dates %} +
  • {{ date }} - Columns: motm_{{ date }}, dotd_{{ date }}
  • + {% endfor %} +
+ {% else %} +
+
No fixture columns found
+

No fixture-specific MOTM/DotD columns were found in the database.

+
+ {% endif %} +
+
+
+
+ + + + + + + +