Separate MotM app out
This commit is contained in:
parent
5638b0af76
commit
d3eb5567b6
31
forms.py
31
forms.py
@ -78,13 +78,6 @@ class clubPlayingRecordsForm(FlaskForm):
|
|||||||
clubName = SelectField("Club to search", choices=[], coerce=str)
|
clubName = SelectField("Club to search", choices=[], coerce=str)
|
||||||
submitButton = SubmitField("Submit")
|
submitButton = SubmitField("Submit")
|
||||||
|
|
||||||
class motmForm(FlaskForm):
|
|
||||||
startDate = DateField('DatePicker', format='%d-%m-%Y')
|
|
||||||
endDate = DateField('DatePicker', format='%d-%m-%Y')
|
|
||||||
|
|
||||||
class motmAdminForm(FlaskForm):
|
|
||||||
startDate = DateField('DatePicker', format='%d-%m-%Y')
|
|
||||||
endDate = DateField('DatePicker', format='%d-%m-%Y')
|
|
||||||
|
|
||||||
class squadListForm(FlaskForm):
|
class squadListForm(FlaskForm):
|
||||||
teamName = SelectField("HKFC team to display")
|
teamName = SelectField("HKFC team to display")
|
||||||
@ -99,28 +92,4 @@ class adminSettingsForm(FlaskForm):
|
|||||||
saveButton = SubmitField('Save Settings')
|
saveButton = SubmitField('Save Settings')
|
||||||
activateButton = SubmitField('Activate MotM Vote')
|
activateButton = SubmitField('Activate MotM Vote')
|
||||||
|
|
||||||
class goalsAssistsForm(FlaskForm):
|
|
||||||
fixtureNumber = TextField('Fixture Number')
|
|
||||||
match = SelectField('Fixture')
|
|
||||||
homeTeam = TextField('Home Team')
|
|
||||||
awayTeam = TextField('Away Team')
|
|
||||||
playerNumber = TextField('Player Number')
|
|
||||||
playerName = TextField('Player Name')
|
|
||||||
assists = SelectField('Assists:', choices=[(0, '0'), (1, '1'), (2, '2'), (3, '3'), (4, '4')])
|
|
||||||
goals = SelectField('Goals:', choices=[(0, '0'), (1, '1'), (2, '2'), (3, '3'), (4, '4')])
|
|
||||||
submit = SubmitField('Submit')
|
|
||||||
# def __init__(self, *args, **kwargs):
|
|
||||||
# super(goalsAssistsForm, self).__init__(*args, **kwargs)
|
|
||||||
# read_only(self.homeTeam)
|
|
||||||
# read_only(self.awayTeam)
|
|
||||||
|
|
||||||
|
|
||||||
class adminSettingsForm2(FlaskForm):
|
|
||||||
nextMatch = SelectField('Fixture', choices=[], default=mySettings('match'))
|
|
||||||
nextOppoClub = TextField('Next Opposition Club:', default=mySettings('club'))
|
|
||||||
nextOppoTeam = TextField("Next Opposition Team:")
|
|
||||||
currMotM = SelectField('Current Man of the Match:', choices=[], default=mySettings('motm'))
|
|
||||||
currDotD = SelectField('Current Dick of the Day:', choices=[], default=mySettings('dotd'))
|
|
||||||
saveButton = SubmitField('Save Settings')
|
|
||||||
activateButton = SubmitField('Activate MotM Vote')
|
|
||||||
|
|
||||||
|
|||||||
15
main.py
15
main.py
@ -20,19 +20,6 @@ from routes import *
|
|||||||
app.register_blueprint(routes)
|
app.register_blueprint(routes)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/hkfc-d/vote-chart', methods=['GET', 'POST'])
|
|
||||||
def hkfc_d_vote_chart():
|
|
||||||
form = LoginForm()
|
|
||||||
print('Here we are')
|
|
||||||
if form.validate_on_submit():
|
|
||||||
sql = "SELECT username FROM hockeyUsers WHERE (username= '" + form.username.data + "')"
|
|
||||||
print(sql)
|
|
||||||
rows = sql_read(sql)
|
|
||||||
print(rows)
|
|
||||||
return redirect(url_for('/hkfc-d/voting'))
|
|
||||||
# return '<h1>Something went wrong there</h1>'
|
|
||||||
|
|
||||||
return render_template('hkfc-d/login-vote.html', form=form)
|
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
@ -44,7 +31,7 @@ def login():
|
|||||||
rows = sql_write(sql)
|
rows = sql_write(sql)
|
||||||
print(rows)
|
print(rows)
|
||||||
print(rows[0])
|
print(rows[0])
|
||||||
return redirect(url_for('/hkfc-d/voting'))
|
return redirect(url_for('dashboard'))
|
||||||
else:
|
else:
|
||||||
return 'Something went wrong'
|
return 'Something went wrong'
|
||||||
# return '<h1>Something went wrong there</h1>'
|
# return '<h1>Something went wrong there</h1>'
|
||||||
|
|||||||
102
motm_app/README.md
Normal file
102
motm_app/README.md
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# HKFC Men's D Team - MOTM (Man of the Match) System
|
||||||
|
|
||||||
|
This is a standalone Flask application for managing Man of the Match and Dick of the Day voting for the HKFC Men's D Team hockey club.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Public Section
|
||||||
|
- **Voting Interface**: Players can vote for MOTM and DotD via secure random URLs
|
||||||
|
- **Match Comments**: View and add comments from matches
|
||||||
|
- **Current Holders**: Display current MOTM and DotD holders
|
||||||
|
|
||||||
|
### Admin Section
|
||||||
|
- **Match Management**: Set up upcoming matches, opposition teams, and fixtures
|
||||||
|
- **Squad Management**: Add/remove players from match squads
|
||||||
|
- **Statistics**: Record goals and assists for players
|
||||||
|
- **Voting Results**: View real-time voting charts and results
|
||||||
|
- **Player of the Year**: Track season-long MOTM/DotD statistics
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Configure database settings in `db_config.py`
|
||||||
|
|
||||||
|
3. Run the application:
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The application will be available at `http://localhost:5000`
|
||||||
|
|
||||||
|
## Database Requirements
|
||||||
|
|
||||||
|
The application requires access to the following database tables:
|
||||||
|
- `_hkfcD_matchSquad` - Current match squad
|
||||||
|
- `_HKFC_players` - Player database
|
||||||
|
- `hkfcDAdminSettings` - Admin configuration
|
||||||
|
- `hockeyFixtures` - Match fixtures
|
||||||
|
- `_hkfc_d_motm` - MOTM/DotD voting results
|
||||||
|
- `_motmComments` - Match comments
|
||||||
|
- `_clubTeams` - Club and team information
|
||||||
|
- `mensHockeyClubs` - Club logos and information
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### Public Endpoints
|
||||||
|
- `/` - Main index page
|
||||||
|
- `/motm/<randomUrlSuffix>` - Voting page (requires valid URL suffix)
|
||||||
|
- `/motm/comments` - Match comments
|
||||||
|
- `/motm/vote-thanks` - Vote submission processing
|
||||||
|
|
||||||
|
### Admin Endpoints (Basic Auth Required)
|
||||||
|
- `/admin/motm` - MOTM administration
|
||||||
|
- `/admin/squad` - Squad management
|
||||||
|
- `/admin/squad/submit` - Process squad selection
|
||||||
|
- `/admin/squad/list` - View current squad
|
||||||
|
- `/admin/squad/remove` - Remove player from squad
|
||||||
|
- `/admin/squad/reset` - Reset squad for new match
|
||||||
|
- `/admin/stats` - Goals and assists administration
|
||||||
|
- `/admin/voting` - Voting results charts
|
||||||
|
- `/admin/poty` - Player of the Year charts
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
- `/api/vote-results` - Get voting results as JSON
|
||||||
|
- `/api/poty-results` - Get Player of the Year results as JSON
|
||||||
|
- `/admin/api/team/<club>` - Get teams for a club
|
||||||
|
- `/admin/api/logo/<club>` - Get club logo URL
|
||||||
|
- `/admin/api/fixture/<fixture>` - Get fixture information
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Admin sections are protected with HTTP Basic Authentication
|
||||||
|
- Voting URLs use random suffixes to prevent unauthorized access
|
||||||
|
- All admin actions require authentication
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. **Admin Setup**:
|
||||||
|
- Access `/admin/motm` to configure upcoming matches
|
||||||
|
- Use `/admin/squad` to select players for the match
|
||||||
|
- Activate voting to generate the public voting URL
|
||||||
|
|
||||||
|
2. **Player Voting**:
|
||||||
|
- Share the generated voting URL with players
|
||||||
|
- Players can vote for MOTM and DotD
|
||||||
|
- Optional comments can be added
|
||||||
|
|
||||||
|
3. **Results**:
|
||||||
|
- View real-time results at `/admin/voting`
|
||||||
|
- Track season statistics at `/admin/poty`
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Update the following in `db_config.py`:
|
||||||
|
- Database connection details
|
||||||
|
- Cloud SQL configuration (if using Google Cloud)
|
||||||
|
- Local database settings
|
||||||
|
|
||||||
|
The application uses the same database as the main hockey results system, so ensure proper database access is configured.
|
||||||
13
motm_app/app.py
Normal file
13
motm_app/app.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# encoding=utf-8
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
from flask import Flask
|
||||||
|
from flask_bootstrap import Bootstrap
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.secret_key = "4pFwRNNXs+xQSOEaHrq4iSBwl+mq1UTdRuxqhM+RQpo="
|
||||||
|
Bootstrap(app)
|
||||||
|
|
||||||
|
def randomUrlSuffix(stringLength=6):
|
||||||
|
lettersAndDigits = string.ascii_letters + string.digits
|
||||||
|
return ''.join(random.choice(lettersAndDigits) for i in range(stringLength))
|
||||||
24
motm_app/app.yaml
Normal file
24
motm_app/app.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
runtime: python39
|
||||||
|
|
||||||
|
env_variables:
|
||||||
|
CLOUDSQL_CONNECTION_NAME: "hk-hockey:asia-east2:hk-hockey-sql"
|
||||||
|
CLOUDSQL_USER: "hockeyWrite"
|
||||||
|
CLOUDSQL_WRITE_USER: "hockeyWrite"
|
||||||
|
CLOUDSQL_READ_USER: "hockeyRead"
|
||||||
|
CLOUDSQL_PASSWORD: "P8P1YopMlwg8TxhE"
|
||||||
|
CLOUDSQL_WRITE_PASSWORD: "1URYcxXXlQ6xOWgj"
|
||||||
|
CLOUDSQL_READ_PASSWORD: "o4GWrbbkBKy3oR6u"
|
||||||
|
CLOUDSQL_DATABASE: "20209_hockeyResults"
|
||||||
|
CLOUDSQL_DATABASE_STATIC: "hockeyResults"
|
||||||
|
CLOUDSQL_CHARSET: "utf8"
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- url: /static
|
||||||
|
static_dir: static
|
||||||
|
|
||||||
|
- url: /.*
|
||||||
|
script: auto
|
||||||
|
|
||||||
|
automatic_scaling:
|
||||||
|
min_instances: 1
|
||||||
|
max_instances: 10
|
||||||
121
motm_app/db_config.py
Normal file
121
motm_app/db_config.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# encoding=utf-8
|
||||||
|
import pymysql
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
# These environment variables are configured in app.yaml.
|
||||||
|
CLOUDSQL_CONNECTION_NAME = "hk-hockey:asia-east2:hk-hockey-sql"
|
||||||
|
LOCAL_DB_SERVER = "mariadb.db.svc.cluster.local"
|
||||||
|
CLOUDSQL_USER = "root"
|
||||||
|
CLOUDSQL_WRITE_USER = "hockeyWrite"
|
||||||
|
CLOUDSQL_READ_USER = "hockeyRead"
|
||||||
|
CLOUDSQL_PASSWORD = "P8P1YopMlwg8TxhE"
|
||||||
|
CLOUDSQL_WRITE_PASSWORD = "1URYcxXXlQ6xOWgj"
|
||||||
|
CLOUDSQL_READ_PASSWORD = "o4GWrbbkBKy3oR6u"
|
||||||
|
CLOUDSQL_DATABASE = "20209_hockeyResults"
|
||||||
|
LOCAL_DATABASE = "hockeyResults2021"
|
||||||
|
CLOUDSQL_DATABASE_STATIC = "hockeyResults"
|
||||||
|
CLOUDSQL_CHARSET = "utf8"
|
||||||
|
|
||||||
|
|
||||||
|
def write_cloudsql():
|
||||||
|
# When deployed to App Engine, the `SERVER_SOFTWARE` environment variable
|
||||||
|
# will be set to 'Google App Engine/version'.
|
||||||
|
if os.getenv('SERVER_SOFTWARE', '').startswith('Google App Engine/'):
|
||||||
|
# Connect using the unix socket located at
|
||||||
|
# /cloudsql/cloudsql-connection-name.
|
||||||
|
cloudsql_unix_socket = os.path.join('/cloudsql', CLOUDSQL_CONNECTION_NAME)
|
||||||
|
db = pymysql.connect(unix_socket=cloudsql_unix_socket, user=CLOUDSQL_WRITE_USER, passwd=CLOUDSQL_WRITE_PASSWORD, db=CLOUDSQL_DATABASE, charset=CLOUDSQL_CHARSET)
|
||||||
|
else:
|
||||||
|
db = pymysql.connect(host=LOCAL_DB_SERVER, user=CLOUDSQL_WRITE_USER, passwd=CLOUDSQL_WRITE_PASSWORD, db=LOCAL_DATABASE, charset=CLOUDSQL_CHARSET)
|
||||||
|
return db
|
||||||
|
|
||||||
|
def write_cloudsql_static():
|
||||||
|
# When deployed to App Engine, the `SERVER_SOFTWARE` environment variable
|
||||||
|
# will be set to 'Google App Engine/version'.
|
||||||
|
if os.getenv('SERVER_SOFTWARE', '').startswith('Google App Engine/'):
|
||||||
|
# Connect using the unix socket located at
|
||||||
|
# /cloudsql/cloudsql-connection-name.
|
||||||
|
cloudsql_unix_socket = os.path.join('/cloudsql', CLOUDSQL_CONNECTION_NAME)
|
||||||
|
db = pymysql.connect(unix_socket=cloudsql_unix_socket, user=CLOUDSQL_WRITE_USER, passwd=CLOUDSQL_WRITE_PASSWORD, db=CLOUDSQL_DATABASE_STATIC, charset=CLOUDSQL_CHARSET)
|
||||||
|
else:
|
||||||
|
db = pymysql.connect(host=LOCAL_DB_SERVER, user=CLOUDSQL_WRITE_USER, passwd=CLOUDSQL_WRITE_PASSWORD, db=CLOUDSQL_DATABASE_STATIC, charset=CLOUDSQL_CHARSET)
|
||||||
|
return db
|
||||||
|
|
||||||
|
def read_cloudsql():
|
||||||
|
# When deployed to App Engine, the `SERVER_SOFTWARE` environment variable
|
||||||
|
# will be set to 'Google App Engine/version'.
|
||||||
|
if os.getenv('SERVER_SOFTWARE', '').startswith('Google App Engine/'):
|
||||||
|
# Connect using the unix socket located at
|
||||||
|
# /cloudsql/cloudsql-connection-name.
|
||||||
|
cloudsql_unix_socket = os.path.join('/cloudsql', CLOUDSQL_CONNECTION_NAME)
|
||||||
|
db = pymysql.connect(unix_socket=cloudsql_unix_socket, user=CLOUDSQL_READ_USER, passwd=CLOUDSQL_READ_PASSWORD, db=CLOUDSQL_DATABASE, charset=CLOUDSQL_CHARSET)
|
||||||
|
else:
|
||||||
|
db = pymysql.connect(host=LOCAL_DB_SERVER, user=CLOUDSQL_READ_USER, passwd=CLOUDSQL_READ_PASSWORD, db=LOCAL_DATABASE, charset=CLOUDSQL_CHARSET)
|
||||||
|
return db
|
||||||
|
|
||||||
|
def read_cloudsql_static():
|
||||||
|
# When deployed to App Engine, the `SERVER_SOFTWARE` environment variable
|
||||||
|
# will be set to 'Google App Engine/version'.
|
||||||
|
if os.getenv('SERVER_SOFTWARE', '').startswith('Google App Engine/'):
|
||||||
|
# Connect using the unix socket located at
|
||||||
|
# /cloudsql/cloudsql-connection-name.
|
||||||
|
cloudsql_unix_socket = os.path.join('/cloudsql', CLOUDSQL_CONNECTION_NAME)
|
||||||
|
db = pymysql.connect(unix_socket=cloudsql_unix_socket, user=CLOUDSQL_READ_USER, passwd=CLOUDSQL_READ_PASSWORD, db=CLOUDSQL_DATABASE_STATIC, charset=CLOUDSQL_CHARSET)
|
||||||
|
else:
|
||||||
|
db = pymysql.connect(host=LOCAL_DB_SERVER, user=CLOUDSQL_READ_USER, passwd=CLOUDSQL_READ_PASSWORD, db=CLOUDSQL_DATABASE_STATIC, charset=CLOUDSQL_CHARSET)
|
||||||
|
return db
|
||||||
|
|
||||||
|
def sql_write(sql_cmd):
|
||||||
|
try:
|
||||||
|
db = write_cloudsql()
|
||||||
|
cursor = db.cursor(pymysql.cursors.DictCursor)
|
||||||
|
cursor.execute(sql_cmd)
|
||||||
|
db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
db.close()
|
||||||
|
return db
|
||||||
|
|
||||||
|
def sql_write_static(sql_cmd):
|
||||||
|
try:
|
||||||
|
db = write_cloudsql_static()
|
||||||
|
cursor = db.cursor(pymysql.cursors.DictCursor)
|
||||||
|
cursor.execute(sql_cmd)
|
||||||
|
db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
db.close()
|
||||||
|
return db
|
||||||
|
|
||||||
|
def sql_read(sql_cmd):
|
||||||
|
try:
|
||||||
|
db = read_cloudsql()
|
||||||
|
cursor = db.cursor(pymysql.cursors.DictCursor)
|
||||||
|
cursor.execute(sql_cmd)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
rows = ''
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
db.close()
|
||||||
|
return rows
|
||||||
|
|
||||||
|
def sql_read_static(sql_cmd):
|
||||||
|
try:
|
||||||
|
db = read_cloudsql_static()
|
||||||
|
cursor = db.cursor(pymysql.cursors.DictCursor)
|
||||||
|
cursor.execute(sql_cmd)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
rows = ''
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
db.close()
|
||||||
|
return rows
|
||||||
64
motm_app/deploy.py
Normal file
64
motm_app/deploy.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Deployment script for MOTM Flask application to Google App Engine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def deploy_to_app_engine():
|
||||||
|
"""Deploy the MOTM application to Google App Engine."""
|
||||||
|
|
||||||
|
print("🚀 Starting deployment to Google App Engine...")
|
||||||
|
|
||||||
|
# Check if gcloud is installed
|
||||||
|
try:
|
||||||
|
subprocess.run(['gcloud', '--version'], check=True, capture_output=True)
|
||||||
|
print("✓ Google Cloud SDK is installed")
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
print("❌ Google Cloud SDK not found. Please install it first:")
|
||||||
|
print(" https://cloud.google.com/sdk/docs/install")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if user is authenticated
|
||||||
|
try:
|
||||||
|
subprocess.run(['gcloud', 'auth', 'list', '--filter=status:ACTIVE'], check=True, capture_output=True)
|
||||||
|
print("✓ Google Cloud authentication verified")
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print("❌ Not authenticated with Google Cloud. Run 'gcloud auth login' first")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if app.yaml exists
|
||||||
|
if not os.path.exists('app.yaml'):
|
||||||
|
print("❌ app.yaml not found in current directory")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("✓ app.yaml found")
|
||||||
|
|
||||||
|
# Deploy the application
|
||||||
|
try:
|
||||||
|
print("📦 Deploying application...")
|
||||||
|
result = subprocess.run(['gcloud', 'app', 'deploy'], check=True)
|
||||||
|
print("✅ Deployment completed successfully!")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"❌ Deployment failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == '--deploy':
|
||||||
|
success = deploy_to_app_engine()
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
|
else:
|
||||||
|
print("MOTM Application Deployment Script")
|
||||||
|
print("=================================")
|
||||||
|
print()
|
||||||
|
print("Usage:")
|
||||||
|
print(" python deploy.py --deploy # Deploy to Google App Engine")
|
||||||
|
print()
|
||||||
|
print("Prerequisites:")
|
||||||
|
print(" 1. Install Google Cloud SDK")
|
||||||
|
print(" 2. Run 'gcloud auth login'")
|
||||||
|
print(" 3. Set your project: 'gcloud config set project YOUR_PROJECT_ID'")
|
||||||
|
print(" 4. Ensure app.yaml is configured correctly")
|
||||||
40
motm_app/forms.py
Normal file
40
motm_app/forms.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# encoding=utf-8
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import BooleanField, StringField, PasswordField, TextField, IntegerField, TextAreaField, SubmitField, RadioField, SelectField
|
||||||
|
from wtforms.fields.html5 import DateField
|
||||||
|
from wtforms_components import read_only
|
||||||
|
from wtforms import validators, ValidationError
|
||||||
|
from wtforms.validators import InputRequired, Email, Length
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class motmForm(FlaskForm):
|
||||||
|
startDate = DateField('DatePicker', format='%d-%m-%Y')
|
||||||
|
endDate = DateField('DatePicker', format='%d-%m-%Y')
|
||||||
|
|
||||||
|
|
||||||
|
class motmAdminForm(FlaskForm):
|
||||||
|
startDate = DateField('DatePicker', format='%d-%m-%Y')
|
||||||
|
endDate = DateField('DatePicker', format='%d-%m-%Y')
|
||||||
|
|
||||||
|
|
||||||
|
class adminSettingsForm2(FlaskForm):
|
||||||
|
nextMatch = SelectField('Fixture', choices=[])
|
||||||
|
nextOppoClub = TextField('Next Opposition Club:')
|
||||||
|
nextOppoTeam = TextField("Next Opposition Team:")
|
||||||
|
currMotM = SelectField('Current Man of the Match:', choices=[])
|
||||||
|
currDotD = SelectField('Current Dick of the Day:', choices=[])
|
||||||
|
saveButton = SubmitField('Save Settings')
|
||||||
|
activateButton = SubmitField('Activate MotM Vote')
|
||||||
|
|
||||||
|
|
||||||
|
class goalsAssistsForm(FlaskForm):
|
||||||
|
fixtureNumber = TextField('Fixture Number')
|
||||||
|
match = SelectField('Fixture')
|
||||||
|
homeTeam = TextField('Home Team')
|
||||||
|
awayTeam = TextField('Away Team')
|
||||||
|
playerNumber = TextField('Player Number')
|
||||||
|
playerName = TextField('Player Name')
|
||||||
|
assists = SelectField('Assists:', choices=[(0, '0'), (1, '1'), (2, '2'), (3, '3'), (4, '4')])
|
||||||
|
goals = SelectField('Goals:', choices=[(0, '0'), (1, '1'), (2, '2'), (3, '3'), (4, '4')])
|
||||||
|
submit = SubmitField('Submit')
|
||||||
376
motm_app/main.py
Normal file
376
motm_app/main.py
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
# encoding=utf-8
|
||||||
|
import pymysql
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import hashlib, uuid
|
||||||
|
import datetime
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from app import app, randomUrlSuffix
|
||||||
|
from flask import Flask, flash, render_template, request, redirect, url_for, jsonify
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_bootstrap import Bootstrap
|
||||||
|
from flask_basicauth import BasicAuth
|
||||||
|
from wtforms import StringField, PasswordField, BooleanField
|
||||||
|
from wtforms.fields.html5 import DateField
|
||||||
|
from wtforms.validators import InputRequired, Email, Length
|
||||||
|
from forms import motmForm, adminSettingsForm2, goalsAssistsForm
|
||||||
|
from db_config import sql_write, sql_write_static, sql_read, sql_read_static
|
||||||
|
from tables import matchSquadTable
|
||||||
|
from readSettings import mySettings
|
||||||
|
|
||||||
|
app.config['BASIC_AUTH_USERNAME'] = 'admin'
|
||||||
|
app.config['BASIC_AUTH_PASSWORD'] = 'letmein'
|
||||||
|
basic_auth = BasicAuth(app)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
"""Main index page for MOTM system"""
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== PUBLIC VOTING SECTION ====================
|
||||||
|
|
||||||
|
@app.route('/motm/<randomUrlSuffix>')
|
||||||
|
def motm_vote(randomUrlSuffix):
|
||||||
|
"""Public voting page for Man of the Match and Dick of the Day"""
|
||||||
|
sql = "SELECT playerNumber, playerForenames, playerSurname, playerNickname FROM _hkfcD_matchSquad ORDER BY RAND()"
|
||||||
|
sql2 = "SELECT nextClub, nextTeam, nextDate, oppoLogo, hkfcLogo, currMotM, currDotD, nextFixture FROM hkfcDAdminSettings"
|
||||||
|
rows = sql_read(sql)
|
||||||
|
nextInfo = sql_read_static(sql2)
|
||||||
|
nextClub = nextInfo[0]['nextClub']
|
||||||
|
nextTeam = nextInfo[0]['nextTeam']
|
||||||
|
nextFixture = nextInfo[0]['nextFixture']
|
||||||
|
hkfcLogo = nextInfo[0]['hkfcLogo']
|
||||||
|
oppoLogo = nextInfo[0]['oppoLogo']
|
||||||
|
currMotM = nextInfo[0]['currMotM']
|
||||||
|
currDotD = nextInfo[0]['currDotD']
|
||||||
|
oppo = nextTeam
|
||||||
|
sql3 = "SELECT hockeyResults2021.hockeyFixtures.date, hockeyResults.hkfcDAdminSettings.nextFixture FROM hockeyResults2021.hockeyFixtures INNER JOIN hockeyResults.hkfcDAdminSettings ON hockeyResults2021.hockeyFixtures.fixtureNumber = hockeyResults.hkfcDAdminSettings.nextFixture"
|
||||||
|
nextMatchDate = sql_read(sql3)
|
||||||
|
nextDate = nextMatchDate[0]['date']
|
||||||
|
formatDate = datetime.strftime(nextDate, '%A, %d %B %Y')
|
||||||
|
|
||||||
|
sql3 = "SELECT playerPictureURL FROM _HKFC_players INNER JOIN hockeyResults.hkfcDAdminSettings ON _HKFC_players.playerNumber=hockeyResults.hkfcDAdminSettings.currMotM"
|
||||||
|
sql4 = "SELECT playerPictureURL FROM _HKFC_players INNER JOIN hockeyResults.hkfcDAdminSettings ON _HKFC_players.playerNumber=hockeyResults.hkfcDAdminSettings.currDotD"
|
||||||
|
motm = sql_read(sql3)
|
||||||
|
dotd = sql_read(sql4)
|
||||||
|
motmURL = motm[0]['playerPictureURL']
|
||||||
|
dotdURL = dotd[0]['playerPictureURL']
|
||||||
|
|
||||||
|
sql5 = "SELECT comment FROM _motmComments INNER JOIN hockeyResults.hkfcDAdminSettings ON _motmComments.matchDate=hockeyResults.hkfcDAdminSettings.nextDate ORDER BY RAND() LIMIT 1"
|
||||||
|
comment = sql_read(sql5)
|
||||||
|
if comment == "":
|
||||||
|
comment = "No comments added yet"
|
||||||
|
form = motmForm()
|
||||||
|
sql6 = "SELECT motmUrlSuffix FROM hockeyResults.hkfcDAdminSettings WHERE userid='admin'"
|
||||||
|
urlSuff = sql_read_static(sql6)
|
||||||
|
randomSuff = urlSuff[0]['motmUrlSuffix']
|
||||||
|
print(randomSuff)
|
||||||
|
if randomSuff == randomUrlSuffix:
|
||||||
|
return render_template('motm_vote.html', data=rows, comment=comment, formatDate=formatDate, matchNumber=nextFixture, oppo=oppo, hkfcLogo=hkfcLogo, oppoLogo=oppoLogo, dotdURL=dotdURL, motmURL=motmURL, form=form)
|
||||||
|
else:
|
||||||
|
return render_template('error.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/motm/comments', methods=['GET', 'POST'])
|
||||||
|
def match_comments():
|
||||||
|
"""Display and allow adding match comments"""
|
||||||
|
sql = "SELECT nextClub, nextTeam, nextDate, oppoLogo, hkfcLogo FROM hkfcDAdminSettings"
|
||||||
|
row = sql_read_static(sql)
|
||||||
|
_oppo = row[0]['nextClub']
|
||||||
|
commentDate = row[0]['nextDate'].strftime('%Y-%m-%d')
|
||||||
|
_matchDate = row[0]['nextDate'].strftime('%Y_%m_%d')
|
||||||
|
hkfcLogo = row[0]['hkfcLogo']
|
||||||
|
oppoLogo = row[0]['oppoLogo']
|
||||||
|
if request.method == 'POST':
|
||||||
|
_comment = request.form['matchComment']
|
||||||
|
if _comment != 'Optional comments added here':
|
||||||
|
_fixed_comment = _comment.replace("'", "\\'")
|
||||||
|
sql3 = "INSERT INTO _motmComments (matchDate, opposition, comment) VALUES ('" + commentDate + "', '" + _oppo + "', '" + _fixed_comment + "')"
|
||||||
|
sql_write(sql3)
|
||||||
|
sql = "SELECT comment FROM _motmComments WHERE matchDate='" + _matchDate + "' ORDER BY RAND()"
|
||||||
|
comments = sql_read(sql)
|
||||||
|
return render_template('match_comments.html', comments=comments, hkfcLogo=hkfcLogo, oppoLogo=oppoLogo)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/motm/vote-thanks', methods=['POST'])
|
||||||
|
def vote_thanks():
|
||||||
|
"""Process MOTM/DotD votes and comments"""
|
||||||
|
try:
|
||||||
|
_motm = request.form['motmVote']
|
||||||
|
_dotd = request.form['dotdVote']
|
||||||
|
_comments = request.form['motmComment']
|
||||||
|
_fixed_comments = _comments.replace("'", "\\'")
|
||||||
|
_matchDate = request.form['matchNumber']
|
||||||
|
_oppo = request.form['oppo']
|
||||||
|
|
||||||
|
if _motm and _dotd and request.method == 'POST':
|
||||||
|
sql = "INSERT INTO _hkfc_d_motm (playerNumber, playerName, motmTotal, motm_" + _matchDate + ") SELECT playerNumber, playerNickname, '1', '1' FROM _HKFC_players WHERE playerNumber='" + _motm + "' ON DUPLICATE KEY UPDATE motmTotal = motmTotal + 1, motm_" + _matchDate + " = motm_" + _matchDate + " + 1"
|
||||||
|
sql2 = "INSERT INTO _hkfc_d_motm (playerNumber, playerName, dotdTotal, dotd_" + _matchDate + ") SELECT playerNumber, playerNickname, '1', '1' FROM _HKFC_players WHERE playerNumber='" + _dotd + "' ON DUPLICATE KEY UPDATE dotdTotal = dotdTotal + 1, dotd_" + _matchDate + " = dotd_" + _matchDate + " + 1"
|
||||||
|
if _comments == "":
|
||||||
|
print("No comment")
|
||||||
|
elif _comments == "Optional comments added here":
|
||||||
|
print("No comment")
|
||||||
|
else:
|
||||||
|
sql3 = "INSERT INTO _motmComments (_matchDate, opposition, comment) VALUES ('" + _matchDate + "', '" + _oppo + "', '" + _fixed_comments + "')"
|
||||||
|
sql_write(sql3)
|
||||||
|
sql_write(sql)
|
||||||
|
sql_write(sql2)
|
||||||
|
return render_template('vote_thanks.html')
|
||||||
|
else:
|
||||||
|
return 'Ouch ... something went wrong here'
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
finally:
|
||||||
|
print('Votes cast')
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== ADMIN SECTION ====================
|
||||||
|
|
||||||
|
@app.route('/admin/motm', methods=['GET', 'POST'])
|
||||||
|
@basic_auth.required
|
||||||
|
def motm_admin():
|
||||||
|
"""Admin page for managing MOTM settings"""
|
||||||
|
form = adminSettingsForm2()
|
||||||
|
prevFixture = mySettings('prevFixture')
|
||||||
|
if prevFixture is None:
|
||||||
|
prevFixture = '1'
|
||||||
|
else:
|
||||||
|
prevFixture = str(prevFixture)
|
||||||
|
if request.method == 'POST':
|
||||||
|
if form.saveButton.data:
|
||||||
|
print('Saved')
|
||||||
|
else:
|
||||||
|
print('Activated')
|
||||||
|
_nextTeam = request.form['nextOppoTeam']
|
||||||
|
_nextFixture = request.form['nextMatch']
|
||||||
|
_currMotM = request.form['currMotM']
|
||||||
|
_currDotD = request.form['currDotD']
|
||||||
|
sql1 = "SELECT club FROM _clubTeams WHERE displayName='" + _nextTeam + "'"
|
||||||
|
_nextClubName = sql_read_static(sql1)
|
||||||
|
_nextClub = _nextClubName[0]['club']
|
||||||
|
sql = "UPDATE hkfcDAdminSettings SET nextFixture='" + _nextFixture + "', nextClub='" + _nextClub + "', nextTeam='" + _nextTeam + "', currMotM=" + _currMotM + ", currDotD=" + _currDotD + ""
|
||||||
|
sql_write_static(sql)
|
||||||
|
sql2 = "UPDATE hkfcDAdminSettings INNER JOIN mensHockeyClubs ON hkfcDAdminSettings.nextClub = mensHockeyClubs.hockeyClub SET hkfcDAdminSettings.oppoLogo = mensHockeyClubs.logoURL WHERE mensHockeyClubs.hockeyClub='" + _nextClub + "'"
|
||||||
|
sql_write_static(sql2)
|
||||||
|
if form.saveButton.data:
|
||||||
|
flash('Settings saved!')
|
||||||
|
urlSuffix = randomUrlSuffix(8)
|
||||||
|
print(urlSuffix)
|
||||||
|
sql3 = "UPDATE hkfcDAdminSettings SET motmUrlSuffix='" + urlSuffix + "' WHERE userid='admin'"
|
||||||
|
sql_write_static(sql3)
|
||||||
|
flash('MotM URL https://hockey.ervine.cloud/motm/'+urlSuffix)
|
||||||
|
elif form.activateButton.data:
|
||||||
|
sql4 = "ALTER TABLE _hkfc_d_motm ADD COLUMN motm_" + _nextFixture + " smallint DEFAULT 0, ADD COLUMN dotd_" + _nextFixture + " smallint DEFAULT 0, ADD COLUMN assists_" + _nextFixture + " smallint DEFAULT 0, ADD COLUMN goals_" + _nextFixture + " smallint DEFAULT 0 "
|
||||||
|
sql_write(sql4)
|
||||||
|
sql5 = "SELECT motmUrlSuffix FROM hkfcDAdminSettings WHERE userid='admin'"
|
||||||
|
tempSuffix = sql_read_static(sql5)
|
||||||
|
currSuffix = tempSuffix[0]['motmUrlSuffix']
|
||||||
|
print(currSuffix)
|
||||||
|
flash('Man of the Match vote is now activated')
|
||||||
|
flash('MotM URL https://hockey.ervine.cloud/motm/'+currSuffix)
|
||||||
|
else:
|
||||||
|
flash('Something went wrong - check with Smithers')
|
||||||
|
|
||||||
|
sql7 = "SELECT date, homeTeam, awayTeam, venue, fixtureNumber FROM hockeyFixtures WHERE homeTeam='HKFC D' OR awayTeam='HKFC D'"
|
||||||
|
matches = sql_read(sql7)
|
||||||
|
form.nextMatch.choices = [(match['fixtureNumber'], match['date']) for match in matches]
|
||||||
|
sql4 = "SELECT hockeyClub FROM mensHockeyClubs ORDER BY hockeyClub"
|
||||||
|
sql5 = "SELECT nextClub, oppoLogo FROM hkfcDAdminSettings"
|
||||||
|
sql6 = "SELECT playerNumber, playerForenames, playerSurname FROM _hkfcD_matchSquad_" + prevFixture + " ORDER BY playerForenames"
|
||||||
|
clubs = sql_read_static(sql4)
|
||||||
|
settings = sql_read_static(sql5)
|
||||||
|
players = sql_read(sql6)
|
||||||
|
form.nextOppoClub.choices = [(oppo['hockeyClub'], oppo['hockeyClub']) for oppo in clubs]
|
||||||
|
form.currMotM.choices = [(player['playerNumber'], player['playerForenames'] + " " + player['playerSurname']) for player in players]
|
||||||
|
form.currDotD.choices = [(player['playerNumber'], player['playerForenames'] + " " + player['playerSurname']) for player in players]
|
||||||
|
clubLogo = settings[0]['oppoLogo']
|
||||||
|
|
||||||
|
return render_template('motm_admin.html', form=form, nextOppoLogo=clubLogo)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/squad', methods=['GET'])
|
||||||
|
@basic_auth.required
|
||||||
|
def match_squad():
|
||||||
|
"""Admin page for managing match squad"""
|
||||||
|
sql1 = "SELECT team from _clubTeams WHERE club='HKFC' ORDER BY team"
|
||||||
|
sql2 = "SELECT playerTeam, playerForenames, playerSurname, playerNickname, playerNumber FROM _HKFC_players"
|
||||||
|
teams = sql_read(sql1)
|
||||||
|
players = sql_read(sql2)
|
||||||
|
return render_template('match_squad.html', teams=teams, players=players)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/squad/submit', methods=['POST'])
|
||||||
|
@basic_auth.required
|
||||||
|
def match_squad_submit():
|
||||||
|
"""Process squad selection"""
|
||||||
|
_playerNumbers = request.form.getlist('playerNumber')
|
||||||
|
for _playerNumber in _playerNumbers:
|
||||||
|
sql = "INSERT INTO _hkfcD_matchSquad (playerNumber, playerForenames, playerSurname, playerNickname) SELECT playerNumber, playerForenames, playerSurname, playerNickname FROM _HKFC_players WHERE playerNumber='" + _playerNumber + "'"
|
||||||
|
sql_write(sql)
|
||||||
|
sql2 = "SELECT playerNumber, playerForenames, playerSurname, playerNickname FROM _hkfcD_matchSquad"
|
||||||
|
players = sql_read(sql2)
|
||||||
|
table = matchSquadTable(players)
|
||||||
|
table.border = True
|
||||||
|
table.classes = ['table-striped', 'table-condensed', 'table-hover']
|
||||||
|
return render_template('match_squad_selected.html', table=table)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/squad/list')
|
||||||
|
@basic_auth.required
|
||||||
|
def match_squad_list():
|
||||||
|
"""Display current squad list"""
|
||||||
|
sql = "SELECT playerNumber, playerForenames, playerSurname, playerNickname FROM _hkfcD_matchSquad"
|
||||||
|
players = sql_read(sql)
|
||||||
|
table = matchSquadTable(players)
|
||||||
|
table.border = True
|
||||||
|
table.classes = ['table-striped', 'table-condensed', 'table-hover']
|
||||||
|
return render_template('match_squad_selected.html', table=table)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/squad/remove', methods=['POST'])
|
||||||
|
@basic_auth.required
|
||||||
|
def delPlayerFromSquad():
|
||||||
|
"""Remove player from squad"""
|
||||||
|
_playerNumber = request.args['playerNumber']
|
||||||
|
sql = "DELETE FROM _hkfcD_matchSquad WHERE playerNumber=" + _playerNumber + ""
|
||||||
|
sql_write(sql)
|
||||||
|
return render_template('player_removed.html', number=_playerNumber)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/squad/reset')
|
||||||
|
@basic_auth.required
|
||||||
|
def matchSquadReset():
|
||||||
|
"""Reset squad for new match"""
|
||||||
|
_matchNumber = str(mySettings('fixture'))
|
||||||
|
print(_matchNumber)
|
||||||
|
sql1 = "RENAME TABLE _hkfcD_matchSquad TO _hkfcD_matchSquad_" + _matchNumber + ""
|
||||||
|
sql2 = "CREATE TABLE _hkfcD_matchSquad (playerNumber smallint UNIQUE, playerForenames varchar(50), playerSurname varchar(30), playerNickname varchar(30) NOT NULL, PRIMARY KEY (playerNumber))"
|
||||||
|
sql3 = "UPDATE hkfcDAdminSettings SET prevFixture='" + _matchNumber + "'"
|
||||||
|
sql_write(sql1)
|
||||||
|
sql_write(sql2)
|
||||||
|
sql_write_static(sql3)
|
||||||
|
return render_template('match_squad_reset.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/stats', methods=['GET', 'POST'])
|
||||||
|
@basic_auth.required
|
||||||
|
def stats_admin():
|
||||||
|
"""Admin page for managing goals and assists statistics"""
|
||||||
|
form = goalsAssistsForm()
|
||||||
|
sql = "SELECT date, homeTeam, awayTeam, venue, fixtureNumber FROM hockeyFixtures WHERE homeTeam='HKFC D' OR awayTeam='HKFC D'"
|
||||||
|
matches = sql_read(sql)
|
||||||
|
form.match.choices = [(match['fixtureNumber'], match['date']) for match in matches]
|
||||||
|
sql2 = "SELECT playerNumber, playerNickname FROM _hkfcD_matchSquad"
|
||||||
|
players = sql_read(sql2)
|
||||||
|
return render_template('goals_assists_admin.html', data=players, form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/stats/submit', methods=['POST'])
|
||||||
|
@basic_auth.required
|
||||||
|
def goalsAssistsSubmit():
|
||||||
|
"""Process goals and assists statistics"""
|
||||||
|
try:
|
||||||
|
data = request.form
|
||||||
|
playerName = request.form.getlist('playerName')
|
||||||
|
playerNumber = request.form.getlist('playerNumber')
|
||||||
|
assists = request.form.getlist('assists')
|
||||||
|
goals = request.form.getlist('goals')
|
||||||
|
match = request.form['match']
|
||||||
|
for idx, player in enumerate(playerNumber):
|
||||||
|
sql = "INSERT INTO _hkfc_d_motm (playerNumber, playerName, assistsTotal, goalsTotal, assists_" + match + ", goals_" + match + ") SELECT playerNumber, playerNickname, '" + assists[idx] + "', '" + goals[idx] + "', '" + assists[idx] + "', '" + goals[idx] + "' FROM _HKFC_players WHERE playerNumber='" + player + "' ON DUPLICATE KEY UPDATE assistsTotal = assistsTotal + " + assists[idx] + ", goalsTotal = goalsTotal + " + goals[idx] + ", assists_" + match + " = " + assists[idx] + ", goals_" + match + " = " + goals[idx] + ""
|
||||||
|
sql_write(sql)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
finally:
|
||||||
|
return render_template('goals_thanks.html', data=data)
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== API ENDPOINTS ====================
|
||||||
|
|
||||||
|
@app.route('/admin/api/team/<club>')
|
||||||
|
def admin_team_lookup(club):
|
||||||
|
"""API endpoint for team lookup by club"""
|
||||||
|
sql = "SELECT team FROM _clubTeams WHERE club='" + club + "'"
|
||||||
|
myteams = sql_read(sql)
|
||||||
|
return jsonify(myteams)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/api/logo/<club>')
|
||||||
|
def get_logo(club):
|
||||||
|
"""API endpoint for club logo lookup"""
|
||||||
|
sql = "SELECT logoURL FROM mensHockeyClubs WHERE hockeyClub='" + club + "'"
|
||||||
|
clubLogo = sql_read(sql)
|
||||||
|
return jsonify(clubLogo)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/api/fixture/<fixture>')
|
||||||
|
def admin_fixture_lookup(fixture):
|
||||||
|
"""API endpoint for fixture team lookup"""
|
||||||
|
sql = "SELECT homeTeam, awayTeam FROM hockeyFixtures WHERE fixtureNumber='" + fixture + "'"
|
||||||
|
myteams = sql_read(sql)
|
||||||
|
if myteams[0]['homeTeam'].startswith("HKFC"):
|
||||||
|
nextOppo = myteams[0]['awayTeam']
|
||||||
|
else:
|
||||||
|
nextOppo = myteams[0]['homeTeam']
|
||||||
|
return jsonify(nextOppo)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/api/fixture/<fixture>/logo')
|
||||||
|
def admin_fixture_logo_lookup(fixture):
|
||||||
|
"""API endpoint for fixture logo lookup"""
|
||||||
|
sql = "SELECT homeTeam, awayTeam FROM hockeyFixtures WHERE fixtureNumber='" + fixture + "'"
|
||||||
|
myteams = sql_read(sql)
|
||||||
|
if myteams[0]['homeTeam'].startswith("HKFC D"):
|
||||||
|
nextOppo = myteams[0]['awayTeam']
|
||||||
|
else:
|
||||||
|
nextOppo = myteams[0]['homeTeam']
|
||||||
|
sql2 = "SELECT club FROM _clubTeams WHERE displayName ='" + nextOppo + "'"
|
||||||
|
clubs = sql_read_static(sql2)
|
||||||
|
clubName = clubs[0]['club']
|
||||||
|
sql3 = "SELECT logoUrl FROM mensHockeyClubs WHERE hockeyClub ='" + clubName + "'"
|
||||||
|
logo = sql_read_static(sql3)
|
||||||
|
clubLogo = logo[0]['logoUrl']
|
||||||
|
return jsonify(clubLogo)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/vote-results')
|
||||||
|
def vote_results():
|
||||||
|
"""API endpoint for voting results"""
|
||||||
|
_matchDate = str(mySettings('fixture'))
|
||||||
|
print(_matchDate)
|
||||||
|
sql = "SELECT playerName, motm_" + _matchDate + ", dotd_" + _matchDate + " FROM _hkfc_d_motm WHERE (motm_" + _matchDate + " > '0') OR (dotd_" + _matchDate + " > '0')"
|
||||||
|
print(sql)
|
||||||
|
rows = sql_read(sql)
|
||||||
|
print(rows)
|
||||||
|
return json.dumps(rows)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/poty-results')
|
||||||
|
def poty_results():
|
||||||
|
"""API endpoint for Player of the Year results"""
|
||||||
|
sql = "SELECT playerName, motmTotal, dotdTotal FROM _hkfc_d_motm WHERE (motmTotal > '0') OR (dotdTotal > '0')"
|
||||||
|
print(sql)
|
||||||
|
rows = sql_read(sql)
|
||||||
|
return json.dumps(rows)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/voting')
|
||||||
|
@basic_auth.required
|
||||||
|
def voting_chart():
|
||||||
|
"""Admin page for viewing voting charts"""
|
||||||
|
matchDate = mySettings('fixture')
|
||||||
|
return render_template('vote_chart.html', _matchDate=matchDate)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/poty')
|
||||||
|
@basic_auth.required
|
||||||
|
def poty_chart():
|
||||||
|
"""Admin page for Player of the Year chart"""
|
||||||
|
return render_template('poty_chart.html')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||||
16
motm_app/readSettings.py
Normal file
16
motm_app/readSettings.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# encoding=utf-8
|
||||||
|
import pymysql
|
||||||
|
import os
|
||||||
|
from db_config import sql_read_static
|
||||||
|
|
||||||
|
def mySettings(setting):
|
||||||
|
try:
|
||||||
|
sql = "SELECT " + setting + " FROM hkfcDAdminSettings WHERE userid='admin'"
|
||||||
|
rows = sql_read_static(sql)
|
||||||
|
if rows:
|
||||||
|
return rows[0][setting]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return None
|
||||||
11
motm_app/requirements.txt
Normal file
11
motm_app/requirements.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
Flask
|
||||||
|
Werkzeug
|
||||||
|
email-validator
|
||||||
|
flask_table
|
||||||
|
flask-mysql
|
||||||
|
flask_login
|
||||||
|
Flask-BasicAuth
|
||||||
|
Flask-Bootstrap
|
||||||
|
flask_wtf
|
||||||
|
wtforms_components
|
||||||
|
pymysql
|
||||||
6
motm_app/static/css/bootstrap-theme.min.css
vendored
Normal file
6
motm_app/static/css/bootstrap-theme.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
6
motm_app/static/css/bootstrap.min.css
vendored
Normal file
6
motm_app/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
motm_app/static/js/bootstrap.min.js
vendored
Normal file
7
motm_app/static/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
9
motm_app/tables.py
Normal file
9
motm_app/tables.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from flask_table import Table, Col, LinkCol, ButtonCol
|
||||||
|
|
||||||
|
|
||||||
|
class matchSquadTable(Table):
|
||||||
|
playerNumber = Col('Player Number')
|
||||||
|
playerNickname = Col('Nickname')
|
||||||
|
playerSurname = Col('Surname')
|
||||||
|
playerForenames = Col('Forenames')
|
||||||
|
delete = ButtonCol('Delete', 'motm.delPlayerFromSquad', url_kwargs=dict(playerNumber='playerNumber'), button_attrs={"type" : "submit", "class" : "btn btn-danger"})
|
||||||
19
motm_app/templates/error.html
Normal file
19
motm_app/templates/error.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Error - Invalid URL</title>
|
||||||
|
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||||
|
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h1>Error</h1>
|
||||||
|
<p>Invalid voting URL. Please check the link and try again.</p>
|
||||||
|
<a class="btn btn-primary" href="/" role="button">Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
69
motm_app/templates/goals_assists_admin.html
Normal file
69
motm_app/templates/goals_assists_admin.html
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Goals and Assists Admin</title>
|
||||||
|
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||||
|
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>Goals and Assists Administration</h3>
|
||||||
|
<form method="post" action="/admin/stats/submit">
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon" id="basic-addon1">Match:</span>
|
||||||
|
{{ form.match(class_="form-control") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Player Name</th>
|
||||||
|
<th>Goals</th>
|
||||||
|
<th>Assists</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for player in data %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="hidden" name="playerName" value="{{ player.playerNickname }}">
|
||||||
|
<input type="hidden" name="playerNumber" value="{{ player.playerNumber }}">
|
||||||
|
{{ player.playerNickname }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select name="goals" class="form-control">
|
||||||
|
<option value="0">0</option>
|
||||||
|
<option value="1">1</option>
|
||||||
|
<option value="2">2</option>
|
||||||
|
<option value="3">3</option>
|
||||||
|
<option value="4">4</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select name="assists" class="form-control">
|
||||||
|
<option value="0">0</option>
|
||||||
|
<option value="1">1</option>
|
||||||
|
<option value="2">2</option>
|
||||||
|
<option value="3">3</option>
|
||||||
|
<option value="4">4</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-success">Submit</button>
|
||||||
|
<a class="btn btn-danger" href="/" role="button">Cancel</a>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
motm_app/templates/goals_thanks.html
Normal file
19
motm_app/templates/goals_thanks.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Goals and Assists Submitted</title>
|
||||||
|
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||||
|
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3>Goals and Assists have been recorded</h3>
|
||||||
|
<a class="btn btn-primary" href="/admin/stats" role="button">Add More Stats</a>
|
||||||
|
<a class="btn btn-danger" href="/" role="button">Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
65
motm_app/templates/index.html
Normal file
65
motm_app/templates/index.html
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>HKFC Men's D Team - MOTM System</title>
|
||||||
|
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||||
|
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h1>HKFC Men's D Team - Man of the Match System</h1>
|
||||||
|
<div class="jumbotron">
|
||||||
|
<h2>Welcome to the MOTM Voting System</h2>
|
||||||
|
<p>This system allows players to vote for Man of the Match and Dick of the Day, while providing admin tools for managing matches and squads.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>Player Section</h3>
|
||||||
|
<div class="list-group">
|
||||||
|
<a href="/motm/comments" class="list-group-item">
|
||||||
|
<h4 class="list-group-item-heading">Match Comments</h4>
|
||||||
|
<p class="list-group-item-text">View comments from recent matches</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>Admin Section</h3>
|
||||||
|
<div class="list-group">
|
||||||
|
<a href="/admin/motm" class="list-group-item">
|
||||||
|
<h4 class="list-group-item-heading">MOTM Admin</h4>
|
||||||
|
<p class="list-group-item-text">Manage match settings and activate voting</p>
|
||||||
|
</a>
|
||||||
|
<a href="/admin/squad" class="list-group-item">
|
||||||
|
<h4 class="list-group-item-heading">Squad Management</h4>
|
||||||
|
<p class="list-group-item-text">Add players to match squads</p>
|
||||||
|
</a>
|
||||||
|
<a href="/admin/squad/list" class="list-group-item">
|
||||||
|
<h4 class="list-group-item-heading">View Squad</h4>
|
||||||
|
<p class="list-group-item-text">View current match squad</p>
|
||||||
|
</a>
|
||||||
|
<a href="/admin/stats" class="list-group-item">
|
||||||
|
<h4 class="list-group-item-heading">Goals & Assists</h4>
|
||||||
|
<p class="list-group-item-text">Record goals and assists statistics</p>
|
||||||
|
</a>
|
||||||
|
<a href="/admin/voting" class="list-group-item">
|
||||||
|
<h4 class="list-group-item-heading">Voting Results</h4>
|
||||||
|
<p class="list-group-item-text">View current match voting results</p>
|
||||||
|
</a>
|
||||||
|
<a href="/admin/poty" class="list-group-item">
|
||||||
|
<h4 class="list-group-item-heading">Player of the Year</h4>
|
||||||
|
<p class="list-group-item-text">View season totals and Player of the Year standings</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
31
motm_app/templates/match_comments.html
Normal file
31
motm_app/templates/match_comments.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>HKFC Men's D Team - Match Comments</title>
|
||||||
|
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||||
|
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>Match Comments</h3>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<img src="{{ hkfcLogo }}" height="100"></img>
|
||||||
|
<img src="{{ oppoLogo }}" height="100"></img>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-8">
|
||||||
|
{% for comment in comments %}
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-body">
|
||||||
|
{{ comment.comment }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-primary" href="/" role="button">Home</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
200
motm_app/templates/match_squad.html
Normal file
200
motm_app/templates/match_squad.html
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<html>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Raleway" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#squadForm {
|
||||||
|
background-color: #ffffff;
|
||||||
|
margin: 100px auto;
|
||||||
|
font-family: Raleway;
|
||||||
|
padding: 40px;
|
||||||
|
width: 40%;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: 10px;
|
||||||
|
width: 10%;
|
||||||
|
font-size: 17px;
|
||||||
|
font-family: Raleway;
|
||||||
|
border: 1px solid #aaaaaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark input boxes that gets an error on validation: */
|
||||||
|
input.invalid {
|
||||||
|
background-color: #ffdddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide all steps by default: */
|
||||||
|
.tab {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 17px;
|
||||||
|
font-family: Raleway;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#prevBtn {
|
||||||
|
background-color: #bbbbbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make circles that indicate the steps of the form: */
|
||||||
|
.step {
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
margin: 0 2px;
|
||||||
|
background-color: #bbbbbb;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step.active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark the steps that are finished and valid: */
|
||||||
|
.step.finish {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<form id="squadForm" action="/admin/squad/submit" method="post">
|
||||||
|
|
||||||
|
<h1>Add Players to Squad</h1>
|
||||||
|
|
||||||
|
<!-- One "tab" for each step in the form: -->
|
||||||
|
{% for team in teams %}
|
||||||
|
<div class="tab">{{ team.team }} Team Players
|
||||||
|
<p>
|
||||||
|
{% for item in players %}
|
||||||
|
{% if item.playerTeam == team.team %}
|
||||||
|
<label><input type="checkbox" name="playerNumber" value="{{ item.playerNumber }}">{{ item.playerNumber }} {{ item.playerForenames }} {{ item.playerSurname }}</label>
|
||||||
|
<br/>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div style="overflow:auto;">
|
||||||
|
<div style="float:right;">
|
||||||
|
<button type="button" id="prevBtn" onclick="nextPrev(-1)">Previous</button>
|
||||||
|
<button type="button" id="nextBtn" onclick="nextPrev(1)">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Circles which indicates the steps of the form: -->
|
||||||
|
<div style="text-align:center;margin-top:40px;">
|
||||||
|
<span class="step"></span>
|
||||||
|
<span class="step"></span>
|
||||||
|
<span class="step"></span>
|
||||||
|
<span class="step"></span>
|
||||||
|
<span class="step"></span>
|
||||||
|
<span class="step"></span>
|
||||||
|
<span class="step"></span>
|
||||||
|
<span class="step"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var currentTab = 0; // Current tab is set to be the first tab (0)
|
||||||
|
showTab(currentTab); // Display the crurrent tab
|
||||||
|
|
||||||
|
function showTab(n) {
|
||||||
|
// This function will display the specified tab of the form...
|
||||||
|
var x = document.getElementsByClassName("tab");
|
||||||
|
x[n].style.display = "block";
|
||||||
|
//... and fix the Previous/Next buttons:
|
||||||
|
if (n == 0) {
|
||||||
|
document.getElementById("prevBtn").style.display = "none";
|
||||||
|
} else {
|
||||||
|
document.getElementById("prevBtn").style.display = "inline";
|
||||||
|
}
|
||||||
|
if (n == (x.length - 1)) {
|
||||||
|
document.getElementById("nextBtn").innerHTML = "Submit";
|
||||||
|
} else {
|
||||||
|
document.getElementById("nextBtn").innerHTML = "Next";
|
||||||
|
}
|
||||||
|
//... and run a function that will display the correct step indicator:
|
||||||
|
fixStepIndicator(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextPrev(n) {
|
||||||
|
// This function will figure out which tab to display
|
||||||
|
var x = document.getElementsByClassName("tab");
|
||||||
|
// Exit the function if any field in the current tab is invalid:
|
||||||
|
// JDE ** Need to validate for this to work? ** if (n == 1 && !validateForm()) return false;
|
||||||
|
// Hide the current tab:
|
||||||
|
x[currentTab].style.display = "none";
|
||||||
|
// Increase or decrease the current tab by 1:
|
||||||
|
currentTab = currentTab + n;
|
||||||
|
// if you have reached the end of the form...
|
||||||
|
if (currentTab >= x.length) {
|
||||||
|
// ... the form gets submitted:
|
||||||
|
document.getElementById("squadForm").submit();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Otherwise, display the correct tab:
|
||||||
|
showTab(currentTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateForm() {
|
||||||
|
// This function deals with validation of the form fields
|
||||||
|
var x, y, i, valid = true;
|
||||||
|
x = document.getElementsByClassName("tab");
|
||||||
|
y = x[currentTab].getElementsByTagName("input");
|
||||||
|
// A loop that checks every input field in the current tab:
|
||||||
|
for (i = 0; i < y.length; i++) {
|
||||||
|
// If a field is empty...
|
||||||
|
if (y[i].value == "") {
|
||||||
|
// add an "invalid" class to the field:
|
||||||
|
y[i].className += " invalid";
|
||||||
|
// and set the current valid status to false
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the valid status is true, mark the step as finished and valid:
|
||||||
|
if (valid) {
|
||||||
|
document.getElementsByClassName("step")[currentTab].className += " finish";
|
||||||
|
}
|
||||||
|
return valid; // return the valid status
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixStepIndicator(n) {
|
||||||
|
// This function removes the "active" class of all steps...
|
||||||
|
var i, x = document.getElementsByClassName("step");
|
||||||
|
for (i = 0; i < x.length; i++) {
|
||||||
|
x[i].className = x[i].className.replace(" active", "");
|
||||||
|
}
|
||||||
|
//... and adds the "active" class on the current step:
|
||||||
|
x[n].className += " active";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
motm_app/templates/match_squad_reset.html
Normal file
19
motm_app/templates/match_squad_reset.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Squad Reset</title>
|
||||||
|
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||||
|
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3>Match squad has been reset for the next match</h3>
|
||||||
|
<a class="btn btn-primary" href="/admin/squad" role="button">Add Players to New Squad</a>
|
||||||
|
<a class="btn btn-danger" href="/" role="button">Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
motm_app/templates/match_squad_selected.html
Normal file
24
motm_app/templates/match_squad_selected.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>HKFC Men's D Team - Match Squad Selected</title>
|
||||||
|
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||||
|
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>Match Squad</h3>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{{ table }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-primary" href="/admin/squad" role="button">Add More Players</a>
|
||||||
|
<a class="btn btn-info" href="/admin/squad/list" role="button">View Squad List</a>
|
||||||
|
<a class="btn btn-warning" href="/admin/squad/reset" role="button">Reset Squad</a>
|
||||||
|
<a class="btn btn-danger" href="/" role="button">Cancel</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
105
motm_app/templates/motm_admin.html
Normal file
105
motm_app/templates/motm_admin.html
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>HKFC Men's D Team - MotM and DotD vote admin</title>
|
||||||
|
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||||
|
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<h2>HKFC Men's D Team MotM and DotD online vote admin page</h2>
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-warning alert-dismissible" role="alert">
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<body onload="myFunction()">
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<p>
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
<b>HKFC D Next Opponent:</b>
|
||||||
|
<br/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<form class="col-sm-6" method="post" action="/admin/motm">
|
||||||
|
<div class = "row">
|
||||||
|
<div class = "col-sm-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon" id="basic-addon1">Date:</span>
|
||||||
|
{{ form.nextMatch(class_="form-control") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</br>
|
||||||
|
<div class = "row">
|
||||||
|
<div class = "col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon" id="basic-addon1">Opposition</span>
|
||||||
|
{{ form.nextOppoTeam(class_="form-control") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class = "row">
|
||||||
|
<div class = "col-sm-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon" id="basic-addon1">Current Man of the Match:</span>
|
||||||
|
{{ form.currMotM(class_="form-control") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class = "col-sm-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon" id="basic-addon1">Current Dick of the Day:</span>
|
||||||
|
{{ form.currDotD(class_="form-control") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{{ form.saveButton(class_="btn btn-success") }}
|
||||||
|
{{ form.activateButton(class_="btn btn-primary") }}
|
||||||
|
<a class="btn btn-danger" href="/" role="button">Cancel</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<img src="{{ nextOppoLogo }}" height="90" id="nextOppoLogo"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var match_select = document.getElementById("nextMatch");
|
||||||
|
var club_logo = document.getElementById("nextOppoLogo");
|
||||||
|
var team_select = document.getElementById("nextOppoTeam");
|
||||||
|
|
||||||
|
match_select.onchange = function() {myFunction()};
|
||||||
|
|
||||||
|
function myFunction() {
|
||||||
|
|
||||||
|
match = match_select.value;
|
||||||
|
|
||||||
|
fetch('/admin/api/fixture/' + match).then(function(response) {
|
||||||
|
response.json().then(function(team) {
|
||||||
|
var optionHTML = '';
|
||||||
|
optionHTML += team;
|
||||||
|
team_select.value = optionHTML;
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
fetch('/admin/api/fixture/' + match + '/logo').then(function(response) {
|
||||||
|
response.json().then(function(logo) {
|
||||||
|
var HTML = '';
|
||||||
|
HTML += logo;
|
||||||
|
club_logo.src = HTML;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
83
motm_app/templates/motm_vote.html
Normal file
83
motm_app/templates/motm_vote.html
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>HKFC Men's D Team - MotM and DotD online vote</title>
|
||||||
|
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||||
|
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<h3>HKFC Men's D Team MotM and DotD online vote</h3>
|
||||||
|
<h5>{{ formatDate }}</h5>
|
||||||
|
<h4><img src="{{ hkfcLogo }}" height="150"></img> <b> </b> <img src="{{ oppoLogo }}" height="140"></img></h4>
|
||||||
|
<body>
|
||||||
|
<p><b>Randomly selected comment from the match:</b>
|
||||||
|
<br/>
|
||||||
|
{% for item in comment %}
|
||||||
|
<i>{{ item.comment }}</i>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
<dl>
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<form class="col-sm-6" method="post" action="/motm/vote-thanks" id="motmForm" accept-charset="utf-8">
|
||||||
|
<input type="hidden" id="matchNumber" name="matchNumber" value="{{ matchNumber }}">
|
||||||
|
<input type="hidden" id="oppo" name="oppo" value="{{ oppo }}">
|
||||||
|
<div class = "row">
|
||||||
|
<div class = "col-sm-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon" id="basic-addon1">Man of the Match</span>
|
||||||
|
<select class="form-control" name="motmVote" required>
|
||||||
|
{% for item in data %}
|
||||||
|
{% if item.playerNickname != "" %}
|
||||||
|
<option value={{ item.playerNumber }}>{{ item.playerNickname }}</option>
|
||||||
|
{% else %}
|
||||||
|
<option value={{ item.playerNumber }}>{{ item.playerSurname }}, {{ item.playerForenames }}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class = "col-sm-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon" id="basic-addon1">Dick of the Day</span>
|
||||||
|
<select class="form-control" name="dotdVote" required>
|
||||||
|
{% for item in data %}
|
||||||
|
{% if item.playerNickname != "" %}
|
||||||
|
<option value={{ item.playerNumber }}>{{ item.playerNickname }}</option>
|
||||||
|
{% else %}
|
||||||
|
<option value={{ item.playerNumber }}>{{ item.playerSurname }}, {{ item.playerForenames }}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class = "row">
|
||||||
|
<div class = "col-sm-6">
|
||||||
|
<div class = "input-group">
|
||||||
|
<span class = "input-group-addon" id = "basic-addon1">Match comments</span>
|
||||||
|
<textarea rows = "4" cols = "80" name = "motmComment" form = "motmForm">Optional comments added here</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class = "row">
|
||||||
|
<h3>Rogues Gallery</h3>
|
||||||
|
<div class = "col-sm-4">
|
||||||
|
<h4>Current Man of the Match</h4>
|
||||||
|
<img src="{{ motmURL }}" height="200"></img>
|
||||||
|
</div>
|
||||||
|
<div class = "col-sm-4">
|
||||||
|
<h4>Current Dick of the Day</h4>
|
||||||
|
<img src="{{ dotdURL }}" height="200"></img>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-success">Submit</button>
|
||||||
|
<a class="btn btn-danger" href="/" role="button">Cancel</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
motm_app/templates/player_removed.html
Normal file
19
motm_app/templates/player_removed.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Player Removed</title>
|
||||||
|
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||||
|
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3>Player {{ number }} has been removed from the squad</h3>
|
||||||
|
<a class="btn btn-primary" href="/admin/squad/list" role="button">Back to Squad List</a>
|
||||||
|
<a class="btn btn-danger" href="/" role="button">Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
56
motm_app/templates/poty_chart.html
Normal file
56
motm_app/templates/poty_chart.html
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
||||||
|
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||||
|
<div id="chart_div" style="width: 800px; height: 1000px;"></div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
|
||||||
|
google.charts.load('current', {
|
||||||
|
packages: ['corechart']
|
||||||
|
}).then(function () {
|
||||||
|
// create chart
|
||||||
|
var container = $('#chart_div').get(0);
|
||||||
|
var chart = new google.visualization.ColumnChart(container);
|
||||||
|
var options = {
|
||||||
|
legend: {
|
||||||
|
position: 'top'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// create data table
|
||||||
|
var data = new google.visualization.DataTable();
|
||||||
|
data.addColumn('string', 'Player');
|
||||||
|
data.addColumn('number', 'MotM Total');
|
||||||
|
data.addColumn('number', 'DotD Total');
|
||||||
|
|
||||||
|
// get data
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/poty-results',
|
||||||
|
dataType: 'json'
|
||||||
|
}).done(function (jsonData) {
|
||||||
|
loadData(jsonData);
|
||||||
|
}).fail(function (jqXHR, textStatus, errorThrown) {
|
||||||
|
var jsonData = [{"motmTotal": 5, "playerName": "ERVINE Jonathan Desmond", "dotdTotal": 2}, {"motmTotal": 3, "playerName": "MCDONAGH Jerome Michael", "dotdTotal": 1}];
|
||||||
|
loadData(jsonData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// load json data
|
||||||
|
function loadData(jsonData) {
|
||||||
|
$.each(jsonData, function(index, row) {
|
||||||
|
data.addRow([
|
||||||
|
row.playerName,
|
||||||
|
row.motmTotal,
|
||||||
|
row.dotdTotal
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
drawChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw chart
|
||||||
|
$(window).resize(drawChart);
|
||||||
|
function drawChart() {
|
||||||
|
chart.draw(data, options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
56
motm_app/templates/vote_chart.html
Normal file
56
motm_app/templates/vote_chart.html
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
||||||
|
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||||
|
<div id="chart_div" style="width: 800px; height: 1000px;"></div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
|
||||||
|
google.charts.load('current', {
|
||||||
|
packages: ['corechart']
|
||||||
|
}).then(function () {
|
||||||
|
// create chart
|
||||||
|
var container = $('#chart_div').get(0);
|
||||||
|
var chart = new google.visualization.ColumnChart(container);
|
||||||
|
var options = {
|
||||||
|
legend: {
|
||||||
|
position: 'top'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// create data table
|
||||||
|
var data = new google.visualization.DataTable();
|
||||||
|
data.addColumn('string', 'Player');
|
||||||
|
data.addColumn('number', 'MotM');
|
||||||
|
data.addColumn('number', 'DotD');
|
||||||
|
|
||||||
|
// get data
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/vote-results',
|
||||||
|
dataType: 'json'
|
||||||
|
}).done(function (jsonData) {
|
||||||
|
loadData(jsonData);
|
||||||
|
}).fail(function (jqXHR, textStatus, errorThrown) {
|
||||||
|
var jsonData = [{"motm_{{ _matchDate }}": 1, "playerName": "ERVINE Jonathan Desmond", "dotd_{{ _matchDate }}": 0}, {"motm_{{ _matchDate }}": 0, "playerName": "MCDONAGH Jerome Michael", "dotd_{{ _matchDate }}": 1}];
|
||||||
|
loadData(jsonData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// load json data
|
||||||
|
function loadData(jsonData) {
|
||||||
|
$.each(jsonData, function(index, row) {
|
||||||
|
data.addRow([
|
||||||
|
row.playerName,
|
||||||
|
row.motm_{{ _matchDate }},
|
||||||
|
row.dotd_{{ _matchDate }}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
drawChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw chart
|
||||||
|
$(window).resize(drawChart);
|
||||||
|
function drawChart() {
|
||||||
|
chart.draw(data, options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
19
motm_app/templates/vote_thanks.html
Normal file
19
motm_app/templates/vote_thanks.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>HKFC Men's D Team - MotM and DotD vote</title>
|
||||||
|
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
|
||||||
|
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<h2>Thanks for submitting the MotM and DotD votes</h2>
|
||||||
|
<body>
|
||||||
|
Smithers' army of Internet monkeys will now go about adding up the votes ...
|
||||||
|
<p>
|
||||||
|
<img src="https://storage.googleapis.com/hk-hockey-data/images/monkey-typing-simpsons.jpg"></img>
|
||||||
|
</p>
|
||||||
|
<a class="btn btn-primary" href="/" role="button">Home</a>
|
||||||
|
<a class="btn btn-info" href="/motm/comments" role="button">Comments</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
41
motm_app/test_app.py
Normal file
41
motm_app/test_app.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Simple test script to verify the MOTM Flask application can start without errors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Add the current directory to Python path
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
try:
|
||||||
|
from main import app
|
||||||
|
print("✓ MOTM Flask application imported successfully")
|
||||||
|
|
||||||
|
# Test that we can create the app context
|
||||||
|
with app.app_context():
|
||||||
|
print("✓ Flask application context created successfully")
|
||||||
|
|
||||||
|
# Test that routes are registered
|
||||||
|
routes = [rule.rule for rule in app.url_map.iter_rules()]
|
||||||
|
print(f"✓ Found {len(routes)} registered routes")
|
||||||
|
|
||||||
|
# Check for key routes
|
||||||
|
key_routes = ['/', '/motm/', '/admin/motm', '/admin/squad']
|
||||||
|
for route in key_routes:
|
||||||
|
if any(route in r for r in routes):
|
||||||
|
print(f"✓ Key route {route} is registered")
|
||||||
|
else:
|
||||||
|
print(f"⚠ Key route {route} not found")
|
||||||
|
|
||||||
|
print("\n🎉 MOTM application test completed successfully!")
|
||||||
|
print("You can now run 'python main.py' to start the application.")
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"❌ Import error: {e}")
|
||||||
|
print("Make sure all dependencies are installed: pip install -r requirements.txt")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error: {e}")
|
||||||
|
sys.exit(1)
|
||||||
@ -3,5 +3,4 @@ routes = Blueprint('routes', __name__)
|
|||||||
|
|
||||||
from .dashboard import *
|
from .dashboard import *
|
||||||
from ._matches import *
|
from ._matches import *
|
||||||
from ._hkfcD_motm import *
|
|
||||||
from ._convenor import *
|
from ._convenor import *
|
||||||
|
|||||||
@ -68,12 +68,6 @@ class convenorSquadListTable(Table):
|
|||||||
edit = ButtonCol('Edit', 'routes.convenorEditPlayer', url_kwargs=dict(playerNumber='playerNumber'), button_attrs={"type" : "submit", "class" : "btn btn-primary"})
|
edit = ButtonCol('Edit', 'routes.convenorEditPlayer', url_kwargs=dict(playerNumber='playerNumber'), button_attrs={"type" : "submit", "class" : "btn btn-primary"})
|
||||||
delete = ButtonCol('Delete', 'routes.convenorDeletePlayer', url_kwargs=dict(playerNumber='playerNumber'), button_attrs={"type" : "submit", "class" : "btn btn-danger"})
|
delete = ButtonCol('Delete', 'routes.convenorDeletePlayer', url_kwargs=dict(playerNumber='playerNumber'), button_attrs={"type" : "submit", "class" : "btn btn-danger"})
|
||||||
|
|
||||||
class matchSquadTable(Table):
|
|
||||||
playerNumber = Col('Player Number')
|
|
||||||
playerNickname = Col('Nickname')
|
|
||||||
playerSurname = Col('Surname')
|
|
||||||
playerForenames = Col('Forenames')
|
|
||||||
delete = ButtonCol('Delete', 'routes.delPlayerFromSquad', url_kwargs=dict(playerNumber='playerNumber'), button_attrs={"type" : "submit", "class" : "btn btn-danger"})
|
|
||||||
|
|
||||||
class convenorFixtureList(Table):
|
class convenorFixtureList(Table):
|
||||||
date = Col('Date')
|
date = Col('Date')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user