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)
|
||||
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):
|
||||
teamName = SelectField("HKFC team to display")
|
||||
@ -99,28 +92,4 @@ class adminSettingsForm(FlaskForm):
|
||||
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')
|
||||
# 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.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'])
|
||||
def login():
|
||||
@ -44,7 +31,7 @@ def login():
|
||||
rows = sql_write(sql)
|
||||
print(rows)
|
||||
print(rows[0])
|
||||
return redirect(url_for('/hkfc-d/voting'))
|
||||
return redirect(url_for('dashboard'))
|
||||
else:
|
||||
return 'Something went wrong'
|
||||
# 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 ._matches import *
|
||||
from ._hkfcD_motm 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"})
|
||||
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):
|
||||
date = Col('Date')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user