Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sweep: Add Entry Functionality #10

Open
alexboden opened this issue Jul 30, 2023 · 2 comments · May be fixed by #12
Open

Sweep: Add Entry Functionality #10

alexboden opened this issue Jul 30, 2023 · 2 comments · May be fixed by #12
Labels
sweep Assigns Sweep to an issue or pull request.

Comments

@alexboden
Copy link
Owner

Details

For each event table in templates/entries.html and each table in template/teams.html add the option to enter a new entry. This entry would then be saved to the database via a new api.

@alexboden alexboden added the sweep Assigns Sweep to an issue or pull request. label Jul 30, 2023
@sweep-ai
Copy link
Contributor

sweep-ai bot commented Jul 30, 2023

Here's the PR! #12.

⚡ Sweep Free Trial: I used GPT-4 to create this ticket. You have 1 GPT-4 tickets left. For more GPT-4 tickets, visit our payment portal.


Step 1: 🔍 Code Search

I found the following snippets in your repository. I will now analyze these snippets and come up with a plan.

Some code snippets I looked at (click to expand). If some file is missing from here, you can mention the path in the ticket description.

{% include 'header.html' %}
<!DOCTYPE html>
<html>
<head>
<title>Entries</title>
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<h1 class="col-md-5">Entries by Event</h1>
<div class="col-md-6" style="margin-top: 3rem;">
<button class="btn btn-lg btn-primary" type="button" id="btn-collapse-all">Collapse All</button>
</div>
</div>
</div>
<div class="container">
{% for event in events %}
<div class="card my-3">
<div class="row">
<div class="col-md-8">
<h2 class="mb-0">{{ event }}</h2>
</div>
<div class="col-md-4 text-left">
<button class="btn btn-primary mb-3" type="button" id="btn-toggle-table-{{ event }}"
data-table-id="{{ event }}" style="margin-top: 1rem;">
Toggle Table
</button>
</div>
</div>
</div>
<table class="results-table table table-striped" id="{{ event }}-table">
<thead>
<tr>
<th>Ranking</th>
<th>Name</th>
<th>Seed Time</th>
<th>Team</th>
<th>Points</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for entry in events[event] %}
<tr>
<td>{{ entry['ranking'] }}</td>
<td>{{ entry['name'] }}</td>
<td>{{ entry['seed_time'] }}</td>
<td>{{ entry['team_name'] }}</td>
<td>{{ entry['points'] }}</td>
<td>
<button type="button" class="btn btn-move-up btn-success">Move Up</button>
<button type="button" class="btn btn-move-down">Move Down</button>
<button type="button" class="btn btn-delete btn-danger">Delete</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
</div>
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
<script src="{{ url_for('static', filename='table.js') }}"></script>
<script src="{{ url_for('static', filename='collapsible-entries.js') }}"></script>
</body>
</html>

{% include 'header.html' %}
<!DOCTYPE html>
<html>
<head>
<title>Entries by Team</title>
<!-- Bootstrap CSS -->
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="container">
<div class="row">
<div class="col-md-5">
<h1>Entries by Team</h1>
</div>
<div class="col-md-6">
<button class="btn btn-lg btn-primary" type="button" id="btn-collapse-all-team" style="margin-top: 1.5rem;">Collapse All Teams</button>
</div>
</div>
</div>
{% for team_name, team_data in entries_by_team.items() %}
<div class="card-header">
<div class="container">
<div class="row">
<div class="col-md-4">
<h1>Team: {{ team_name }}</h1>
</div>
<div class="col-md-4">
<h2>Points: {{ team_data['points'] }}</h2>
</div>
<div class="col-md-4">
<h2>Swimmers: {{ team_data['number_of_swimmers'] }}</h2>
</div>
</div>
<div class="row">
<div class="col-md-3">
<button class="btn btn-block btn-warning" id="btn-collapse-team" team-id={{team_name.split()[0]}} style="margin-top: 1.5rem;">Collapse Team</button>
</div>
<div class="col-md-3">
<button class="btn btn-block btn-primary" id="btn-expand-team" team-id={{team_name.split()[0]}} style="margin-top: 1.5rem;">Expand Team</button>
</div>
<div class="col-md-3">
<button class="btn btn-block btn-warning" id="btn-collapse-entries-team" team-id={{team_name.split()[0]}} style="margin-top: 1.5rem;">Collapse Entries</button>
</div>
<div class="col-md-3">
<button class="btn btn-block btn-primary" id="btn-expand-entries-team" team-id={{team_name.split()[0]}} style="margin-top: 1.5rem;">Expand Entries</button>
</div>
</div>
</div>
<div class="card-body">
<div id="team-div-{{team_name.split()[0]}}">
{% for swimmer_name, swimmer_data in team_data['swimmers'].items() %}
<!-- calculate swimmer points -->
{% set swimmer_points = 0 %}
{% for entry in swimmer_data %}
{% set swimmer_points = swimmer_points + individual_points.get(entry['ranking'], 0) %}
{% endfor %}
<div class="card my-3">
<div class="row">
<div class="col-md-8">
<h4 id = "{{swimmer_name.replace(' ', '')}}-header" >
{{ swimmer_name }} - {{ swimmer_data['entries']|length }} Entries - {{ swimmer_data['points'] }} Points</h4>
</div>
<div class="col-md-4 text-right">
<button class="btn btn-primary mb-3" type="button" id="btn-toggle-table-{{ swimmer_name }}"
data-table-id="{{ swimmer_name }}" style="margin-top: 1rem;">
Toggle</button>
</div>
</div>
</div>
<table class="table table-striped" id="{{swimmer_name.replace(' ', '')}}-table" table_team_id = "{{team_name.split()[0]}}">
<thead>
<tr> <th>Name</th> <th>Event</th> <th>Time</th> <th>Ranking</th> <th>Points</th> <th>Delete</th></tr>
</thead>
<tbody>
{% for event in swimmer_data['entries'] %}
<tr>
<td>{{ swimmer_name }}</td>
<td>{{ event['event_name'] }}</td>
<td>{{ event['seed_time'] }}</td>
<td>{{ event['ranking'] }}</td>
<td>{{ individual_points.get(event['ranking'], 0) }}</td>
<td>
<button class="btn btn-danger delete-button" data-swimmer-name = "{{ swimmer_name }}" data-swimmer-event = "{{ event['event_name'] }}">Delete</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
</div>
{% endfor %}
</div>
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
<script src="{{ url_for('static', filename='teams-delete.js') }}"></script>
<script src="{{ url_for('static', filename='collapsible-team.js') }}"></script>
</body>

from flask import Flask, render_template, request, session, jsonify, redirect, url_for, send_file
from Meet.meet import Meet
from pypdf import PdfReader
from pymongo import MongoClient
from config import individual_points, MAX_INDIVIDUAL_EVENTS
import pandas as pd
import csv
import datetime as dt
client = MongoClient('mongodb://localhost:27017/')
db = client['swimdatabase']
collection = db['entries']
user_preferences = db['user_preferences']
user_preferences.delete_many({})
user_preferences.insert_one({'gender': 'Men'})
app = Flask(__name__)
app.debug = True
@app.route('/', methods=['GET', 'POST'])
def home():
if request.method == 'POST':
# Check if a file was uploaded
if 'file' not in request.files:
return render_template('upload.html', error='No file selected.')
file_submission = request.files['file']
# Check the file type
file_type = file_submission.filename.split('.')[-1].lower()
if file_type == 'pdf':
# Process the PDF
reader = PdfReader(file_submission)
text_extract = ""
for i in range(len(reader.pages)):
text_extract += (reader.pages[i].extract_text())
m = Meet(text_extract)
# Clear the database
collection.delete_many({})
for event in m.events:
for entry in event.entries:
collection.insert_one(entry.__dict__)
elif file_type == 'csv':
# Process the CSV
df = pd.read_csv(file_submission)
records = df.to_dict(orient='records')
collection.insert_many(records)
else:
return render_template('upload.html', error='File must be a PDF or CSV.')
return redirect(url_for('entries'))
# If the request method is GET, render the home page
return render_template('upload.html')
def get_filtered_entries():
current_gender = user_preferences.find_one()['gender']
entries = collection.find()
filtered_entries = []
for entry in entries:
if "Women" in entry['event_name'] and current_gender == "Women":
filtered_entries.append(entry)
if not "Women" in entry['event_name'] and current_gender == "Men":
filtered_entries.append(entry)
return filtered_entries
@app.route('/swap_swimmers', methods=['POST'])
def swap_swimmers():
request_json = request.get_json()
event, swimmer1, swimmer2 = request_json['event'], request_json['name1'], request_json['name2']
# Find the entries for the two swimmers
swimmer1_entry = collection.find_one(
{'name': swimmer1, 'event_name': event})
swimmer2_entry = collection.find_one(
{'name': swimmer2, 'event_name': event})
if swimmer1_entry is None or swimmer2_entry is None:
return jsonify({'success': False, 'message': 'Swimmer or event not found'})
# Swap the ranking and points values
result1 = collection.update_one({'name': swimmer1, 'event_name': event}, {'$set': {
'ranking': swimmer2_entry['ranking'], 'points': swimmer2_entry['points']}})
result2 = collection.update_one({'name': swimmer2, 'event_name': event}, {'$set': {
'ranking': swimmer1_entry['ranking'], 'points': swimmer1_entry['points']}})
if result1.matched_count == 1 and result1.modified_count == 1 and result2.matched_count == 1 and result2.modified_count == 1:
return jsonify({'success': True})
else:
return jsonify({'success': False, 'message': 'Error updating database'})
@app.route('/delete_swimmer', methods=['POST'])
def delete_swimmer():
request_json = request.get_json()
event, swimmer = request_json['event'], request_json['name']
print(event, swimmer)
# Get all swimmers in the event
event_data = collection.find({'event_name': event}).sort('points', -1)
# Determine the current rank and points of the swimmer to be deleted
swimmer_data = collection.find_one({'name': swimmer, 'event_name': event})
current_rank = swimmer_data['ranking']
current_points = swimmer_data['points']
# Delete the swimmer
status = collection.delete_one({'name': swimmer, 'event_name': event})
if status.deleted_count == 1:
# Update the rankings and points of the remaining swimmers
for i, swimmer in enumerate(event_data):
if swimmer['ranking'] > current_rank:
# Update the rank of the swimmer
new_rank = swimmer['ranking'] - 1
collection.update_one({'name': swimmer['name'], 'event_name': event}, {
'$set': {'ranking': new_rank}})
# Update the points of the swimmer
new_points = individual_points.get(new_rank, 0)
collection.update_one({'name': swimmer['name'], 'event_name': event}, {
'$set': {'points': new_points}})
else:
break # Stop updating swimmers once the deleted swimmer's rank is reached
return jsonify({'success': True})
else:
return jsonify({'success': False, 'message': 'Error deleting swimmer'})
@app.route('/update_swimmer', methods=['POST'])
def update_swimmer():
request_json = request.get_json()
event, swimmer, ranking = request_json['event'], request_json['name'], request_json['rank']
points = 0
if ranking <= 16:
points = individual_points[ranking]
status = collection.update_one({'name': swimmer, 'event_name': event}, {
'$set': {'points': points, 'ranking': ranking}})
if status is not None:
return jsonify({'success': True})
else:
return jsonify({'success': False, 'message': 'Error updating swimmer'})
@app.route('/results')
def display_results():
results = session.get('results', [])
return render_template('results.html', results=results)
@app.route('/points_by_team')
def points_by_team():
entries = get_filtered_entries()
points_by_team = {}
for entry in entries:
points_by_team[entry['team_name']] = points_by_team.get(
entry['team_name'], 0) + entry['points']
# sort the dictionary by value
points_by_team = {k: v for k, v in sorted(
points_by_team.items(), key=lambda item: item[1], reverse=True)}
return points_by_team
@app.route('/entries_by_team')
def entries_by_team():
entries = get_filtered_entries()
ret = {}
for entry in entries:
if entry['team_name'] not in ret:
ret[entry['team_name']] = {'swimmers': {}, 'points': 0,
'number_of_swimmers': 0, 'over_entered_swimmers': [], 'team_name': entry['team_name']}
if entry['name'] not in ret[entry['team_name']]['swimmers']:
ret[entry['team_name']]['number_of_swimmers'] += 1
ret[entry['team_name']]['swimmers'][entry['name']] = {
'entries': [], 'points': 0}
ret[entry['team_name']]['swimmers'][entry['name']
]['entries'].append(entry)
if 'ranking' not in entry:
print(entry)
ret[entry['team_name']]['swimmers'][entry['name']
]['points'] += individual_points.get(entry['ranking'], 0)
if len(ret[entry['team_name']]['swimmers'][entry['name']]) > MAX_INDIVIDUAL_EVENTS:
ret[entry['team_name']]['over_entered_swimmers'].append(
entry['name'])
ret[entry['team_name']
]['points'] += individual_points.get(entry['ranking'], 0)
# sort by total points
ret = {k: v for k, v in sorted(
ret.items(), key=lambda item: item[1]['points'], reverse=True)}
# sort each team's swimmers by their total points ie the value of the 'points' key
for team_name, team_data in ret.items():
swimmers = team_data['swimmers']
sorted_swimmers = sorted(
swimmers.items(), key=lambda item: item[1]['points'], reverse=True)
ret[team_name]['swimmers'] = dict(sorted_swimmers)
return ret
@app.route('/teams')
def teams():
entries_by_team_dict = entries_by_team()
points_by_team_dict = points_by_team()
gender = user_preferences.find_one()['gender']
return render_template('teams.html', points_by_team=points_by_team_dict, entries_by_team=entries_by_team_dict, individual_points=individual_points, gender=gender)
@app.route('/entries')
def entries():
entries = get_filtered_entries()
entries_by_event = {}
for entry in entries:
entries_by_event[entry['event_name']] = entries_by_event.get(
entry['event_name'], []) + [entry]
gender = user_preferences.find_one()['gender']
points_by_team_dict = points_by_team()
return render_template('entries.html', events=entries_by_event, points_by_team=points_by_team_dict, gender=gender)
@app.route('/update_gender', methods=['POST'])
def update_gender():
request_json = request.get_json()
gender = request_json['gender']
status = user_preferences.update_one({}, {'$set': {'gender': gender}})
if status is not None:
return jsonify({'success': True})
else:
return jsonify({'success': False, 'message': 'Error updating swimmer'})
@app.route('/export', methods=['GET'])
def export():
cursor = collection.find()
df = pd.DataFrame(list(cursor))
# drop the _id column
df.drop('_id', axis=1, inplace=True)
cur_date = dt.datetime.now()
formatted_datetime = cur_date.strftime("%Y-%m-%d %H:%M")
name = "Swim Analysis:" + formatted_datetime + ".csv"
df.to_csv(name, index=False)
return send_file(name)
@app.route('/import_csv', methods=['POST'])
def import_csv():
if 'file' not in request.files:
return 'No file part'
f = request.files['file']
if f.filename == '':
return 'No selected file'
if f and f.filename.endswith('.csv'):
df = pd.read_csv(f)
records = df.to_dict(orient='records')
collection.insert_many(records)
return redirect(url_for('entries'))
if __name__ == '__main__':
app.run(port=3000, debug=True)

from abc import ABC, abstractmethod
import re
from config import individual_points
class Entry(ABC):
"""
An abstract base class for entries.
Attributes:
TIME_PATTERN (str): A regular expression pattern for matching time strings.
"""
TIME_PATTERN = "(([0-5]?[0-9]:)?[0-5][0-9]\.[0-9][0-9])|NT"
@abstractmethod
def __init__(self):
"""
Constructor for Entry class.
"""
pass
class IndividualEntry(Entry):
"""
A class representing an individual entry.
Attributes:
team_name (str): The name of the team.
seed_time (str): The seed time for the entry.
age (str): The age of the entrant.
name (str): The name of the entrant.
ranking (int): The ranking of the entrant.
points (int): The number of points earned by the entrant.
"""
def __init__(self, event_string, event_name):
"""
Constructor for IndividualEntry class.
Args:
event_string (str): The string representation of the entry.
Must be in the format of "team_name seed_time age name ranking".
"""
self.event_name = event_name
seed_time_search = re.search(Entry.TIME_PATTERN, event_string)
self.team_name = event_string[0:seed_time_search.start()].strip()
self.seed_time = event_string[seed_time_search.start():seed_time_search.end()].strip()
rest_of_string = event_string[seed_time_search.end():].strip()
self.age = rest_of_string[0:rest_of_string.find(" ")].strip()
rest_of_string = rest_of_string[rest_of_string.find(" "):].strip()
ranking_search = re.search("[0-9]+", rest_of_string)
self.name = rest_of_string[0:ranking_search.start()].strip()
self.ranking = int(rest_of_string[ranking_search.start():ranking_search.end()].strip())
if self.ranking in individual_points:
self.points = individual_points[self.ranking]
else:
self.points = 0
def __str__(self):
"""
Returns a string representation of the IndividualEntry object.
Returns:
str: A string representation of the IndividualEntry object.
"""
return f"{self.ranking} {self.name} {self.seed_time} {self.team_name} {self.points} \n"
def to_dict(self):
return {
"event_name": self.event_name,
"ranking": self.ranking,
"time": self.seed_time,
"name": self.name,
"team": self.team_name,
"points": self.points
}
class RelayEntry(Entry):
# team_name
# seed_time
# ranking
# score
pass

https://github.com/alexboden/swim-ranking-analysis/blob/543067f6383104d618c00c97969ccdea7a2dc706/static/table.js#L1-L202

I also found the following external resources that might be helpful:

Summaries of links found in the content:


Step 2: 🧐 Snippet Analysis

From looking through the relevant snippets, I decided to make the following modifications:

File Path Proposed Changes
templates/entries.html Add a form to each event table to allow users to enter a new entry. Auto-fill the event name based on the table it is inserting into.
templates/teams.html Add a form to each table to allow users to enter a new entry. Auto-fill the name field.
main.py Create a new API endpoint to handle the submission of new entries and save them to the database.

Step 3: 📝 Planning

I have created a plan for writing the pull request. I am now working my plan and coding the required changes to address this issue. Here is the planned pull request:

Add Entry Functionality
sweep/add-entry-functionality

Description

This PR adds entry functionality to the swim-ranking-analysis website. It addresses Issue #10 by allowing users to enter new entries in the event tables and team tables. The entered data is saved to the database via a new API endpoint.

Summary of Changes

  • Added a form to each event table in templates/entries.html to allow users to enter a new entry. The event name is auto-filled based on the table it is inserting into.
  • Added a form to each table in templates/teams.html to allow users to enter a new entry. The name field is auto-filled.
  • Created a new API endpoint in main.py to handle the submission of new entries and save them to the database.

Please review and merge this PR to enable the entry functionality on the website.


Step 4: ⌨️ Coding

I have finished coding the issue. I am now reviewing it for completeness.


Step 5: 🔁 Code Review

Success! 🚀


I'm a bot that handles simple bugs and feature requests but I might make mistakes. Please be kind!
Join Our Discord

@alexboden
Copy link
Owner Author

alexboden commented Jul 30, 2023

Sweep: When adding to the templates/entries page the event name should be auto filled based on the table it is inserting into. Similarly the name should be autofilled when entering into the templates/teams.html page.

@sweep-ai sweep-ai bot linked a pull request Jul 30, 2023 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
sweep Assigns Sweep to an issue or pull request.
Projects
None yet
1 participant