Fix up voting and start adding club logos
This commit is contained in:
parent
7f0d171e21
commit
131f17947c
60
motm_app/POSTGRESQL_SETUP.md
Normal file
60
motm_app/POSTGRESQL_SETUP.md
Normal file
@ -0,0 +1,60 @@
|
||||
# PostgreSQL Database Setup
|
||||
|
||||
This application is now configured to use PostgreSQL as the default database instead of SQLite.
|
||||
|
||||
## Database Configuration
|
||||
|
||||
The application connects to a PostgreSQL database with the following settings:
|
||||
- **Host**: icarus.ipa.champion
|
||||
- **Port**: 5432
|
||||
- **Database**: motm
|
||||
- **Username**: motm_user
|
||||
- **Password**: q7y7f7Lv*sODJZ2wGiv0Wq5a
|
||||
|
||||
## Running the Application
|
||||
|
||||
### Linux/macOS
|
||||
```bash
|
||||
./run_motm.sh
|
||||
```
|
||||
|
||||
### Windows
|
||||
```cmd
|
||||
run_motm.bat
|
||||
```
|
||||
|
||||
### Manual Setup
|
||||
If you need to run the application manually, set these environment variables:
|
||||
|
||||
```bash
|
||||
export DATABASE_TYPE=postgresql
|
||||
export POSTGRES_HOST=icarus.ipa.champion
|
||||
export POSTGRES_PORT=5432
|
||||
export POSTGRES_DATABASE=motm
|
||||
export POSTGRES_USER=motm_user
|
||||
export POSTGRES_PASSWORD='q7y7f7Lv*sODJZ2wGiv0Wq5a'
|
||||
```
|
||||
|
||||
## Database Status
|
||||
|
||||
The PostgreSQL database contains the following data:
|
||||
- **Players**: 3 players in the `_hkfc_players` table
|
||||
- **Match Squad**: 3 players currently selected for the match squad
|
||||
- **Admin Settings**: Configured with next match details
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. **Connection Failed**: Check if the PostgreSQL server is accessible
|
||||
2. **Empty Data**: The database contains sample data - if you see empty tables, check the connection
|
||||
3. **Permission Errors**: Ensure the `motm_user` has proper database permissions
|
||||
|
||||
## Testing Database Connection
|
||||
|
||||
Run the test script to verify everything is working:
|
||||
```bash
|
||||
python3 test_match_squad.py
|
||||
```
|
||||
|
||||
This will test the database connection and display current data.
|
||||
@ -1,6 +1,6 @@
|
||||
# encoding=utf-8
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import BooleanField, StringField, PasswordField, IntegerField, TextAreaField, SubmitField, RadioField, SelectField, DateField
|
||||
from wtforms import BooleanField, StringField, PasswordField, IntegerField, TextAreaField, SubmitField, RadioField, SelectField, DateField, FieldList
|
||||
from wtforms_components import read_only
|
||||
from wtforms import validators, ValidationError
|
||||
from wtforms.validators import InputRequired, Email, Length
|
||||
@ -106,10 +106,19 @@ class ClubForm(FlaskForm):
|
||||
class TeamForm(FlaskForm):
|
||||
"""Form for adding/editing teams."""
|
||||
|
||||
club = StringField('Club', validators=[InputRequired()])
|
||||
club = SelectField('Club', validators=[InputRequired()], choices=[], coerce=str)
|
||||
team = StringField('Team', validators=[InputRequired()])
|
||||
display_name = StringField('Display Name', validators=[InputRequired()])
|
||||
league = StringField('League', validators=[InputRequired()])
|
||||
league = SelectField('League', validators=[InputRequired()], choices=[
|
||||
('', 'Select League'),
|
||||
('Premier Division', 'Premier Division'),
|
||||
('1st Division', '1st Division'),
|
||||
('2nd Division', '2nd Division'),
|
||||
('3rd Division', '3rd Division'),
|
||||
('4th Division', '4th Division'),
|
||||
('5th Division', '5th Division'),
|
||||
('6th Division', '6th Division')
|
||||
], coerce=str)
|
||||
|
||||
# Action buttons
|
||||
save_team = SubmitField('Save Team')
|
||||
@ -126,3 +135,16 @@ class DataImportForm(FlaskForm):
|
||||
# Action buttons
|
||||
import_data = SubmitField('Import Data')
|
||||
cancel = SubmitField('Cancel')
|
||||
|
||||
|
||||
class ClubSelectionForm(FlaskForm):
|
||||
"""Form for selecting which clubs to import."""
|
||||
|
||||
# This will be populated dynamically with club checkboxes
|
||||
selected_clubs = FieldList(BooleanField('Select Club'), min_entries=0)
|
||||
|
||||
# Action buttons
|
||||
import_selected = SubmitField('Import Selected Clubs')
|
||||
select_all = SubmitField('Select All')
|
||||
select_none = SubmitField('Select None')
|
||||
cancel = SubmitField('Cancel')
|
||||
|
||||
136
motm_app/main.py
136
motm_app/main.py
@ -27,7 +27,7 @@ from flask_basicauth import BasicAuth
|
||||
from wtforms import StringField, PasswordField, BooleanField
|
||||
from wtforms import DateField
|
||||
from wtforms.validators import InputRequired, Email, Length
|
||||
from forms import motmForm, adminSettingsForm2, goalsAssistsForm, DatabaseSetupForm, PlayerForm, ClubForm, TeamForm, DataImportForm
|
||||
from forms import motmForm, adminSettingsForm2, goalsAssistsForm, DatabaseSetupForm, PlayerForm, ClubForm, TeamForm, DataImportForm, ClubSelectionForm
|
||||
from db_config import sql_write, sql_write_static, sql_read, sql_read_static
|
||||
from sqlalchemy import text
|
||||
from tables import matchSquadTable
|
||||
@ -75,6 +75,13 @@ def motm_vote(randomUrlSuffix):
|
||||
currMotM = nextInfo[0]['currmotm']
|
||||
currDotD = nextInfo[0]['currdotd']
|
||||
oppo = nextTeam
|
||||
|
||||
# Get opponent club logo from clubs table
|
||||
if nextClub:
|
||||
sql_club_logo = text("SELECT logo_url FROM clubs WHERE hockey_club = :club_name")
|
||||
club_logo_result = sql_read(sql_club_logo, {'club_name': nextClub})
|
||||
if club_logo_result and club_logo_result[0]['logo_url']:
|
||||
oppoLogo = club_logo_result[0]['logo_url']
|
||||
# Get match date from admin settings
|
||||
if nextInfo and nextInfo[0]['nextdate']:
|
||||
nextDate = nextInfo[0]['nextdate']
|
||||
@ -123,14 +130,14 @@ def motm_vote(randomUrlSuffix):
|
||||
@app.route('/motm/comments', methods=['GET', 'POST'])
|
||||
def match_comments():
|
||||
"""Display and allow adding match comments"""
|
||||
sql = text("SELECT nextClub, nextTeam, nextDate, oppoLogo, hkfcLogo FROM motmadminsettings")
|
||||
sql = text("SELECT nextClub, nextTeam, nextdate, oppoLogo, hkfcLogo FROM motmadminsettings")
|
||||
row = sql_read_static(sql)
|
||||
if not row:
|
||||
return render_template('error.html', message="Database not initialized. Please go to Database Setup to initialize the database.")
|
||||
|
||||
_oppo = row[0]['nextClub']
|
||||
commentDate = row[0]['nextDate'].strftime('%Y-%m-%d')
|
||||
_matchDate = row[0]['nextDate'].strftime('%Y_%m_%d')
|
||||
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':
|
||||
@ -257,7 +264,7 @@ def motm_admin():
|
||||
|
||||
# Only update currMotM and currDotD if they were provided
|
||||
if _currMotM and _currMotM != '0' and _currDotD and _currDotD != '0':
|
||||
sql = text("UPDATE motmadminsettings SET nextDate = :next_date, nextClub = :next_club, nextTeam = :next_team, currMotM = :curr_motm, currDotD = :curr_dotd")
|
||||
sql = text("UPDATE motmadminsettings SET nextdate = :next_date, nextClub = :next_club, nextTeam = :next_team, currMotM = :curr_motm, currDotD = :curr_dotd")
|
||||
sql_write_static(sql, {
|
||||
'next_date': _nextMatchDate,
|
||||
'next_club': _nextClub,
|
||||
@ -267,7 +274,7 @@ def motm_admin():
|
||||
})
|
||||
else:
|
||||
# Don't update currMotM and currDotD if not provided
|
||||
sql = text("UPDATE motmadminsettings SET nextDate = :next_date, nextClub = :next_club, nextTeam = :next_team")
|
||||
sql = text("UPDATE motmadminsettings SET nextdate = :next_date, nextClub = :next_club, nextTeam = :next_team")
|
||||
sql_write_static(sql, {
|
||||
'next_date': _nextMatchDate,
|
||||
'next_club': _nextClub,
|
||||
@ -290,7 +297,7 @@ def motm_admin():
|
||||
print(urlSuffix)
|
||||
sql3 = text("UPDATE motmadminsettings SET motmurlsuffix = :url_suffix WHERE userid = 'admin'")
|
||||
sql_write_static(sql3, {'url_suffix': urlSuffix})
|
||||
flash('MotM URL https://hockey.ervine.cloud/motm/'+urlSuffix)
|
||||
flash('MotM URL https://motm.ervine.cloud/motm/'+urlSuffix)
|
||||
elif form.activateButton.data:
|
||||
# Generate a fixture number based on the date
|
||||
_nextFixture = _nextMatchDate.replace('-', '')
|
||||
@ -309,7 +316,7 @@ def motm_admin():
|
||||
currSuffix = tempSuffix[0]['motmurlsuffix']
|
||||
print(currSuffix)
|
||||
flash('Man of the Match vote is now activated')
|
||||
flash('MotM URL https://hockey.ervine.cloud/motm/'+currSuffix)
|
||||
flash('MotM URL https://motm.ervine.cloud/motm/'+currSuffix)
|
||||
else:
|
||||
flash('Something went wrong - check with Smithers')
|
||||
|
||||
@ -554,6 +561,11 @@ def add_team():
|
||||
"""Add a new team"""
|
||||
form = TeamForm()
|
||||
|
||||
# Populate club choices
|
||||
sql_clubs = text("SELECT hockey_club FROM clubs ORDER BY hockey_club")
|
||||
clubs = sql_read(sql_clubs)
|
||||
form.club.choices = [(club['hockey_club'], club['hockey_club']) for club in clubs]
|
||||
|
||||
if form.validate_on_submit():
|
||||
if form.save_team.data:
|
||||
# Check if team already exists
|
||||
@ -586,6 +598,11 @@ def edit_team(team_id):
|
||||
"""Edit an existing team"""
|
||||
form = TeamForm()
|
||||
|
||||
# Populate club choices
|
||||
sql_clubs = text("SELECT hockey_club FROM clubs ORDER BY hockey_club")
|
||||
clubs = sql_read(sql_clubs)
|
||||
form.club.choices = [(club['hockey_club'], club['hockey_club']) for club in clubs]
|
||||
|
||||
if request.method == 'GET':
|
||||
# Load team data
|
||||
sql = text("SELECT id, club, team, display_name, league FROM teams WHERE id = :team_id")
|
||||
@ -801,6 +818,107 @@ def data_import():
|
||||
return render_template('data_import.html', form=form)
|
||||
|
||||
|
||||
@app.route('/admin/import/clubs/select', methods=['GET', 'POST'])
|
||||
@basic_auth.required
|
||||
def club_selection():
|
||||
"""Club selection page for importing specific clubs"""
|
||||
form = ClubSelectionForm()
|
||||
|
||||
# Fetch clubs from the website
|
||||
try:
|
||||
clubs = get_hk_hockey_clubs()
|
||||
if not clubs:
|
||||
# Fallback to predefined clubs if scraping fails
|
||||
clubs = [
|
||||
{'name': 'Hong Kong Football Club', 'abbreviation': 'HKFC', 'teams': ['A', 'B', 'C', 'F', 'G', 'H'], 'convenor': None, 'email': None},
|
||||
{'name': 'Kowloon Cricket Club', 'abbreviation': 'KCC', 'teams': ['A', 'B', 'C', 'D', 'E'], 'convenor': None, 'email': None},
|
||||
{'name': 'United Services Recreation Club', 'abbreviation': 'USRC', 'teams': ['A', 'B', 'C'], 'convenor': None, 'email': None},
|
||||
{'name': 'Valley Fort Sports Club', 'abbreviation': 'Valley', 'teams': ['A', 'B', 'C', 'D', 'E', 'F'], 'convenor': None, 'email': None},
|
||||
{'name': 'South China Sports Club', 'abbreviation': 'SSSC', 'teams': ['C'], 'convenor': None, 'email': None},
|
||||
{'name': 'Dragons Hockey Club', 'abbreviation': 'Dragons', 'teams': ['A', 'B'], 'convenor': None, 'email': None},
|
||||
{'name': 'Kai Tak Sports Club', 'abbreviation': 'Kai Tak', 'teams': ['B', 'C'], 'convenor': None, 'email': None},
|
||||
{'name': 'Royal Hong Kong Regiment Officers and Businessmen Association', 'abbreviation': 'RHOBA', 'teams': ['A', 'B', 'C'], 'convenor': None, 'email': None},
|
||||
{'name': 'Elite Hockey Club', 'abbreviation': 'Elite', 'teams': ['B', 'C'], 'convenor': None, 'email': None},
|
||||
{'name': 'Aquila Hockey Club', 'abbreviation': 'Aquila', 'teams': ['A', 'B'], 'convenor': None, 'email': None},
|
||||
{'name': 'Hong Kong Jockey Club', 'abbreviation': 'HKJ', 'teams': ['B', 'C'], 'convenor': None, 'email': None},
|
||||
{'name': 'Sirius Hockey Club', 'abbreviation': 'Sirius', 'teams': ['A', 'B'], 'convenor': None, 'email': None},
|
||||
{'name': 'Shaheen Hockey Club', 'abbreviation': 'Shaheen', 'teams': ['B'], 'convenor': None, 'email': None},
|
||||
{'name': 'Diocesan Boys School', 'abbreviation': 'Diocesan', 'teams': ['B'], 'convenor': None, 'email': None},
|
||||
{'name': 'Rhino Hockey Club', 'abbreviation': 'Rhino', 'teams': ['B'], 'convenor': None, 'email': None},
|
||||
{'name': 'Khalsa Hockey Club', 'abbreviation': 'Khalsa', 'teams': ['C'], 'convenor': None, 'email': None},
|
||||
{'name': 'Hong Kong Cricket Club', 'abbreviation': 'HKCC', 'teams': ['C', 'D'], 'convenor': None, 'email': None},
|
||||
{'name': 'Hong Kong Police Force', 'abbreviation': 'Police', 'teams': ['A'], 'convenor': None, 'email': None},
|
||||
{'name': 'Recreio Hockey Club', 'abbreviation': 'Recreio', 'teams': ['A', 'B'], 'convenor': None, 'email': None},
|
||||
{'name': 'Correctional Services Department', 'abbreviation': 'CSD', 'teams': ['A'], 'convenor': None, 'email': None},
|
||||
{'name': 'Dutch Hockey Club', 'abbreviation': 'Dutch', 'teams': ['B'], 'convenor': None, 'email': None},
|
||||
{'name': 'Hong Kong University Hockey Club', 'abbreviation': 'HKUHC', 'teams': ['A'], 'convenor': None, 'email': None},
|
||||
{'name': 'Kaitiaki Hockey Club', 'abbreviation': 'Kaitiaki', 'teams': ['A'], 'convenor': None, 'email': None},
|
||||
{'name': 'Antlers Hockey Club', 'abbreviation': 'Antlers', 'teams': ['C'], 'convenor': None, 'email': None},
|
||||
{'name': 'Marcellin Hockey Club', 'abbreviation': 'Marcellin', 'teams': ['A'], 'convenor': None, 'email': None},
|
||||
{'name': 'Skyers Hockey Club', 'abbreviation': 'Skyers', 'teams': ['A'], 'convenor': None, 'email': None},
|
||||
{'name': 'JR Hockey Club', 'abbreviation': 'JR', 'teams': ['A'], 'convenor': None, 'email': None},
|
||||
{'name': 'International University of Hong Kong', 'abbreviation': 'IUHK', 'teams': ['A'], 'convenor': None, 'email': None},
|
||||
{'name': '144 United Hockey Club', 'abbreviation': '144U', 'teams': ['A'], 'convenor': None, 'email': None},
|
||||
{'name': 'Hong Kong University', 'abbreviation': 'HKU', 'teams': ['A'], 'convenor': None, 'email': None},
|
||||
]
|
||||
except Exception as e:
|
||||
flash(f'Error fetching clubs: {str(e)}', 'error')
|
||||
clubs = []
|
||||
|
||||
if request.method == 'POST':
|
||||
if form.select_all.data:
|
||||
# Select all clubs
|
||||
selected_clubs = [club['name'] for club in clubs]
|
||||
return render_template('club_selection.html', form=form, clubs=clubs, selected_clubs=selected_clubs)
|
||||
|
||||
elif form.select_none.data:
|
||||
# Select no clubs
|
||||
return render_template('club_selection.html', form=form, clubs=clubs, selected_clubs=[])
|
||||
|
||||
elif form.import_selected.data:
|
||||
# Import selected clubs
|
||||
selected_clubs = request.form.getlist('selected_clubs')
|
||||
|
||||
if not selected_clubs:
|
||||
flash('No clubs selected for import!', 'error')
|
||||
return render_template('club_selection.html', form=form, clubs=clubs, selected_clubs=[])
|
||||
|
||||
imported_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
for club_name in selected_clubs:
|
||||
# Find the club data
|
||||
club_data = next((club for club in clubs if club['name'] == club_name), None)
|
||||
if not club_data:
|
||||
continue
|
||||
|
||||
# Check if club already exists
|
||||
sql_check = text("SELECT hockey_club FROM clubs WHERE hockey_club = :club_name")
|
||||
existing = sql_read(sql_check, {'club_name': club_name})
|
||||
|
||||
if existing:
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# Import the club
|
||||
logo_url = f"/static/images/clubs/{club_name.lower().replace(' ', '_').replace('.', '').replace(',', '')}_logo.png"
|
||||
sql = text("INSERT INTO clubs (hockey_club, logo_url) VALUES (:club_name, :logo_url)")
|
||||
sql_write(sql, {'club_name': club_name, 'logo_url': logo_url})
|
||||
imported_count += 1
|
||||
|
||||
if imported_count > 0:
|
||||
flash(f'Successfully imported {imported_count} clubs!', 'success')
|
||||
if skipped_count > 0:
|
||||
flash(f'Skipped {skipped_count} clubs that already exist.', 'info')
|
||||
|
||||
return redirect(url_for('club_management'))
|
||||
|
||||
elif form.cancel.data:
|
||||
return redirect(url_for('data_import'))
|
||||
|
||||
return render_template('club_selection.html', form=form, clubs=clubs, selected_clubs=[])
|
||||
|
||||
|
||||
@app.route('/admin/squad/submit', methods=['POST'])
|
||||
@basic_auth.required
|
||||
def match_squad_submit():
|
||||
@ -1162,7 +1280,7 @@ def voting_chart():
|
||||
date_result = sql_read_static(sql_date)
|
||||
|
||||
if date_result:
|
||||
matchDate = date_result[0]['nextDate'].replace('-', '')
|
||||
matchDate = str(date_result[0]['nextdate']).replace('-', '')
|
||||
else:
|
||||
matchDate = '20251012' # Default fallback
|
||||
|
||||
|
||||
@ -1,5 +1,16 @@
|
||||
@echo off
|
||||
echo 🐍 Starting MOTM Application...
|
||||
|
||||
REM Set PostgreSQL environment variables
|
||||
set DATABASE_TYPE=postgresql
|
||||
set POSTGRES_HOST=icarus.ipa.champion
|
||||
set POSTGRES_PORT=5432
|
||||
set POSTGRES_DATABASE=motm
|
||||
set POSTGRES_USER=motm_user
|
||||
set POSTGRES_PASSWORD=q7y7f7Lv*sODJZ2wGiv0Wq5a
|
||||
|
||||
echo 📊 Using PostgreSQL database: %POSTGRES_DATABASE% on %POSTGRES_HOST%
|
||||
|
||||
call venv\Scripts\activate.bat
|
||||
python.exe main.py
|
||||
pause
|
||||
|
||||
11
motm_app/run_motm.sh
Normal file → Executable file
11
motm_app/run_motm.sh
Normal file → Executable file
@ -1,5 +1,16 @@
|
||||
#!/bin/bash
|
||||
echo "🐍 Starting MOTM Application..."
|
||||
|
||||
# Set PostgreSQL environment variables
|
||||
export DATABASE_TYPE=postgresql
|
||||
export POSTGRES_HOST=icarus.ipa.champion
|
||||
export POSTGRES_PORT=5432
|
||||
export POSTGRES_DATABASE=motm
|
||||
export POSTGRES_USER=motm_user
|
||||
export POSTGRES_PASSWORD='q7y7f7Lv*sODJZ2wGiv0Wq5a'
|
||||
|
||||
echo "📊 Using PostgreSQL database: $POSTGRES_DATABASE on $POSTGRES_HOST"
|
||||
|
||||
source venv/bin/activate
|
||||
python main.py
|
||||
|
||||
|
||||
4
motm_app/static/images/clubs/placeholder_logo.svg
Normal file
4
motm_app/static/images/clubs/placeholder_logo.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="120" height="80" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="120" height="80" fill="#f8f9fa" stroke="#dee2e6" stroke-width="2"/>
|
||||
<text x="60" y="45" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#6c757d">Club Logo</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 277 B |
1
motm_app/static/images/default_player.png
Normal file
1
motm_app/static/images/default_player.png
Normal file
@ -0,0 +1 @@
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
@ -30,11 +30,11 @@ class matchSquadTable:
|
||||
html += ' <tbody>\n'
|
||||
for item in self.items:
|
||||
html += ' <tr>\n'
|
||||
html += f' <td>{item.get("playerNumber", "")}</td>\n'
|
||||
html += f' <td>{item.get("playerNickname", "")}</td>\n'
|
||||
html += f' <td>{item.get("playerSurname", "")}</td>\n'
|
||||
html += f' <td>{item.get("playerForenames", "")}</td>\n'
|
||||
html += f' <td><form method="post" action="/admin/squad/remove?playerNumber={item.get("playerNumber", "")}"><button type="submit" class="btn btn-danger">Delete</button></form></td>\n'
|
||||
html += f' <td>{item.get("playernumber", "")}</td>\n'
|
||||
html += f' <td>{item.get("playernickname", "")}</td>\n'
|
||||
html += f' <td>{item.get("playersurname", "")}</td>\n'
|
||||
html += f' <td>{item.get("playerforenames", "")}</td>\n'
|
||||
html += f' <td><form method="post" action="/admin/squad/remove?playerNumber={item.get("playernumber", "")}"><button type="submit" class="btn btn-danger">Delete</button></form></td>\n'
|
||||
html += ' </tr>\n'
|
||||
html += ' </tbody>\n'
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.logo_url.label(class="form-label") }}
|
||||
{{ form.logo_url(class="form-control") }}
|
||||
{{ form.logo_url(class="form-control", id="logoUrl", onchange="previewLogo()") }}
|
||||
{% if form.logo_url.errors %}
|
||||
<div class="text-danger">
|
||||
{% for error in form.logo_url.errors %}
|
||||
@ -52,6 +52,15 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
<small class="form-text text-muted">Enter the full URL to the club's logo image</small>
|
||||
|
||||
<!-- Logo Preview -->
|
||||
<div id="logoPreview" class="mt-2" style="display: none;">
|
||||
<label class="form-label small">Preview:</label>
|
||||
<div class="border rounded p-2 bg-light">
|
||||
<img id="previewImage" src="" alt="Logo preview" style="max-height: 80px; max-width: 120px;" onerror="this.style.display='none'; document.getElementById('previewError').style.display='block';" onload="document.getElementById('previewError').style.display='none';">
|
||||
<div id="previewError" class="text-muted small" style="display: none;">Unable to load image</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
@ -70,5 +79,25 @@
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
function previewLogo() {
|
||||
const urlInput = document.getElementById('logoUrl');
|
||||
const previewDiv = document.getElementById('logoPreview');
|
||||
const previewImg = document.getElementById('previewImage');
|
||||
|
||||
if (urlInput.value.trim()) {
|
||||
previewImg.src = urlInput.value;
|
||||
previewDiv.style.display = 'block';
|
||||
} else {
|
||||
previewDiv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Preview logo on page load if URL is already filled
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
previewLogo();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.club.label(class="form-label") }}
|
||||
{{ form.club(class="form-control") }}
|
||||
{{ form.club(class="form-select") }}
|
||||
{% if form.club.errors %}
|
||||
<div class="text-danger">
|
||||
{% for error in form.club.errors %}
|
||||
@ -39,7 +39,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<small class="form-text text-muted">Enter the club name (e.g., HKFC, KCC, USRC)</small>
|
||||
<small class="form-text text-muted">Select the club from the list of available clubs</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@ -70,7 +70,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.league.label(class="form-label") }}
|
||||
{{ form.league(class="form-control") }}
|
||||
{{ form.league(class="form-select") }}
|
||||
{% if form.league.errors %}
|
||||
<div class="text-danger">
|
||||
{% for error in form.league.errors %}
|
||||
@ -78,7 +78,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<small class="form-text text-muted">Enter the league/division (e.g., Premier Division, Division 1)</small>
|
||||
<small class="form-text text-muted">Select the league/division from the list</small>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
|
||||
@ -77,6 +77,7 @@
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Club Name</th>
|
||||
<th>Logo</th>
|
||||
<th>Logo URL</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
@ -87,9 +88,20 @@
|
||||
<td>{{ club.id }}</td>
|
||||
<td>{{ club.hockey_club }}</td>
|
||||
<td>
|
||||
<a href="{{ club.logo_url }}" target="_blank" class="text-decoration-none">
|
||||
{{ club.logo_url }}
|
||||
</a>
|
||||
{% if club.logo_url %}
|
||||
<img src="{{ club.logo_url }}" alt="{{ club.hockey_club }} logo" style="max-height: 40px; max-width: 60px;" onerror="this.style.display='none'">
|
||||
{% else %}
|
||||
<span class="text-muted">No logo</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if club.logo_url %}
|
||||
<a href="{{ club.logo_url }}" target="_blank" class="text-decoration-none small">
|
||||
{{ club.logo_url[:50] }}{% if club.logo_url|length > 50 %}...{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="text-muted">No URL</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="/admin/clubs/edit/{{ club.id }}" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||
|
||||
174
motm_app/templates/club_selection.html
Normal file
174
motm_app/templates/club_selection.html
Normal file
@ -0,0 +1,174 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Select Clubs to Import - HKFC Men's C Team</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Select Clubs to Import</h3>
|
||||
<p class="mb-0 text-muted">Choose which clubs you want to import from the Hong Kong Hockey Association</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ 'danger' if category == 'error' else 'success' }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<div class="alert alert-info">
|
||||
<h5>Club Selection</h5>
|
||||
<p>Select the clubs you want to import. You can choose all clubs or select specific ones based on your needs.</p>
|
||||
<p><strong>Note:</strong> Only new clubs will be imported. Existing clubs will be skipped to prevent duplicates.</p>
|
||||
</div>
|
||||
|
||||
{% if clubs %}
|
||||
<form method="POST" id="clubSelectionForm">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="d-flex gap-2 mb-3">
|
||||
<button type="submit" name="select_all" class="btn btn-outline-primary btn-sm" formnovalidate>
|
||||
Select All
|
||||
</button>
|
||||
<button type="submit" name="select_none" class="btn btn-outline-secondary btn-sm" formnovalidate>
|
||||
Select None
|
||||
</button>
|
||||
<span class="text-muted align-self-center">
|
||||
<span id="selectedCount">0</span> of {{ clubs|length }} clubs selected
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{% for club in clubs %}
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input club-checkbox"
|
||||
type="checkbox"
|
||||
name="selected_clubs"
|
||||
value="{{ club.name }}"
|
||||
id="club_{{ loop.index }}"
|
||||
{% if club.name in selected_clubs %}checked{% endif %}>
|
||||
<label class="form-check-label w-100" for="club_{{ loop.index }}">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h6 class="mb-1">{{ club.name }}</h6>
|
||||
{% if club.abbreviation %}
|
||||
<small class="text-muted">{{ club.abbreviation }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-end">
|
||||
{% if club.teams %}
|
||||
<small class="badge bg-info">{{ club.teams|length }} teams</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if club.convenor %}
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-person"></i> {{ club.convenor }}
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if club.email %}
|
||||
<div>
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-envelope"></i> {{ club.email }}
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end mt-4">
|
||||
<button type="submit" name="cancel" class="btn btn-secondary me-md-2" formnovalidate>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" name="import_selected" class="btn btn-primary" id="importButton" disabled>
|
||||
Import Selected Clubs
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="alert alert-warning">
|
||||
<h5>No clubs found</h5>
|
||||
<p>Unable to fetch clubs from the Hong Kong Hockey Association website. This might be due to:</p>
|
||||
<ul>
|
||||
<li>Network connectivity issues</li>
|
||||
<li>Website structure changes</li>
|
||||
<li>Server maintenance</li>
|
||||
</ul>
|
||||
<p>Please try again later or contact the administrator.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="/admin/import" class="btn btn-outline-secondary">Back to Data Import</a>
|
||||
<a href="/admin" class="btn btn-outline-secondary">Back to Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// Update selected count and enable/disable import button
|
||||
function updateSelection() {
|
||||
const checkboxes = document.querySelectorAll('.club-checkbox');
|
||||
const selectedCount = document.querySelectorAll('.club-checkbox:checked').length;
|
||||
const importButton = document.getElementById('importButton');
|
||||
const countSpan = document.getElementById('selectedCount');
|
||||
|
||||
countSpan.textContent = selectedCount;
|
||||
importButton.disabled = selectedCount === 0;
|
||||
}
|
||||
|
||||
// Add event listeners to checkboxes
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const checkboxes = document.querySelectorAll('.club-checkbox');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', updateSelection);
|
||||
});
|
||||
updateSelection(); // Initial update
|
||||
});
|
||||
|
||||
// Handle select all/none buttons
|
||||
document.getElementById('clubSelectionForm').addEventListener('submit', function(e) {
|
||||
if (e.submitter.name === 'select_all') {
|
||||
e.preventDefault();
|
||||
document.querySelectorAll('.club-checkbox').forEach(checkbox => {
|
||||
checkbox.checked = true;
|
||||
});
|
||||
updateSelection();
|
||||
} else if (e.submitter.name === 'select_none') {
|
||||
e.preventDefault();
|
||||
document.querySelectorAll('.club-checkbox').forEach(checkbox => {
|
||||
checkbox.checked = false;
|
||||
});
|
||||
updateSelection();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -44,6 +44,14 @@
|
||||
<small class="form-text text-muted d-block">
|
||||
Import 30+ hockey clubs including HKFC, KCC, USRC, Valley, SSSC, Dragons, etc.
|
||||
</small>
|
||||
<div class="mt-2">
|
||||
<a href="/admin/import/clubs/select" class="btn btn-outline-primary btn-sm">
|
||||
Select Specific Clubs
|
||||
</a>
|
||||
<small class="text-muted d-block mt-1">
|
||||
Choose which clubs to import instead of importing all
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.logo_url.label(class="form-label") }}
|
||||
{{ form.logo_url(class="form-control") }}
|
||||
{{ form.logo_url(class="form-control", id="logoUrl", onchange="previewLogo()") }}
|
||||
{% if form.logo_url.errors %}
|
||||
<div class="text-danger">
|
||||
{% for error in form.logo_url.errors %}
|
||||
@ -52,6 +52,15 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
<small class="form-text text-muted">Enter the full URL to the club's logo image</small>
|
||||
|
||||
<!-- Logo Preview -->
|
||||
<div id="logoPreview" class="mt-2" style="display: none;">
|
||||
<label class="form-label small">Preview:</label>
|
||||
<div class="border rounded p-2 bg-light">
|
||||
<img id="previewImage" src="" alt="Logo preview" style="max-height: 80px; max-width: 120px;" onerror="this.style.display='none'; document.getElementById('previewError').style.display='block';" onload="document.getElementById('previewError').style.display='none';">
|
||||
<div id="previewError" class="text-muted small" style="display: none;">Unable to load image</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
@ -70,5 +79,25 @@
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
function previewLogo() {
|
||||
const urlInput = document.getElementById('logoUrl');
|
||||
const previewDiv = document.getElementById('logoPreview');
|
||||
const previewImg = document.getElementById('previewImage');
|
||||
|
||||
if (urlInput.value.trim()) {
|
||||
previewImg.src = urlInput.value;
|
||||
previewDiv.style.display = 'block';
|
||||
} else {
|
||||
previewDiv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Preview logo on page load if URL is already filled
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
previewLogo();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.club.label(class="form-label") }}
|
||||
{{ form.club(class="form-control") }}
|
||||
{{ form.club(class="form-select") }}
|
||||
{% if form.club.errors %}
|
||||
<div class="text-danger">
|
||||
{% for error in form.club.errors %}
|
||||
@ -39,7 +39,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<small class="form-text text-muted">Enter the club name (e.g., HKFC, KCC, USRC)</small>
|
||||
<small class="form-text text-muted">Select the club from the list of available clubs</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@ -70,7 +70,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.league.label(class="form-label") }}
|
||||
{{ form.league(class="form-control") }}
|
||||
{{ form.league(class="form-select") }}
|
||||
{% if form.league.errors %}
|
||||
<div class="text-danger">
|
||||
{% for error in form.league.errors %}
|
||||
@ -78,7 +78,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<small class="form-text text-muted">Enter the league/division (e.g., Premier Division, Division 1)</small>
|
||||
<small class="form-text text-muted">Select the league/division from the list</small>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
|
||||
@ -30,10 +30,10 @@
|
||||
<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>
|
||||
{% if item.playernickname != "" %}
|
||||
<option value={{ item.playernumber }}>{{ item.playernickname }}</option>
|
||||
{% else %}
|
||||
<option value={{ item.playerNumber }}>{{ item.playerSurname }}, {{ item.playerForenames }}</option>
|
||||
<option value={{ item.playernumber }}>{{ item.playersurname }}, {{ item.playerforenames }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
@ -44,10 +44,10 @@
|
||||
<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>
|
||||
{% if item.playernickname != "" %}
|
||||
<option value={{ item.playernumber }}>{{ item.playernickname }}</option>
|
||||
{% else %}
|
||||
<option value={{ item.playerNumber }}>{{ item.playerSurname }}, {{ item.playerForenames }}</option>
|
||||
<option value={{ item.playernumber }}>{{ item.playersurname }}, {{ item.playerforenames }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<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>
|
||||
<img src="http://icarus.ipa.champion:9000/hockey-app/assets/simpsons-monkeys.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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user