Refactor change...
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
TV_APP V1.0.0 - App Package
|
||||
|
||||
This package contains support modules for the main Flask application.
|
||||
All routes are in tv_app.py in the project root.
|
||||
"""
|
||||
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Configuration for TV_APP V1.0.0
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class Config:
|
||||
"""Base configuration class"""
|
||||
SECRET_KEY = os.getenv('SECRET_KEY', 'your-secret-key-for-sessions')
|
||||
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
|
||||
HOST = os.getenv('HOST', '0.0.0.0')
|
||||
PORT = int(os.getenv('PORT', 5000))
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
"""
|
||||
Data models and business logic for TV_APP
|
||||
Handles all calculations and data structure creation
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import random
|
||||
|
||||
NUM_CAMERAS = 6
|
||||
|
||||
|
||||
class Tournament:
|
||||
"""Tournament creation and management"""
|
||||
|
||||
@staticmethod
|
||||
def create_league(enabled_players, tournament_type):
|
||||
"""Create a new league with 5 tournaments"""
|
||||
league_id = f"league_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||
|
||||
league_data = {
|
||||
'league_id': league_id,
|
||||
'created_at': datetime.now().isoformat(),
|
||||
'tournament_type': tournament_type,
|
||||
'total_tournaments': 5,
|
||||
'current_tournament': 0,
|
||||
'participants': {},
|
||||
'completed_tournaments': [],
|
||||
'league_finished': False
|
||||
}
|
||||
|
||||
# Initialize participants
|
||||
for player in enabled_players:
|
||||
league_data['participants'][str(player['id'])] = {
|
||||
'name': player['name'],
|
||||
'joker_used': False,
|
||||
'tournament_results': [],
|
||||
'total_score': 0,
|
||||
'final_score': 0,
|
||||
'tournaments_participated': 0
|
||||
}
|
||||
|
||||
return league_data
|
||||
|
||||
@staticmethod
|
||||
def create_draft(enabled_players, tournament_type='20_targets', league_tournament_number=None):
|
||||
"""Create draft groups of 6 players and organize rounds"""
|
||||
if len(enabled_players) < 1:
|
||||
return None
|
||||
|
||||
# Shuffle players for random grouping
|
||||
players_copy = enabled_players.copy()
|
||||
random.shuffle(players_copy)
|
||||
|
||||
# Create groups of up to 6
|
||||
groups = []
|
||||
for i in range(0, len(players_copy), NUM_CAMERAS):
|
||||
group = players_copy[i:i + NUM_CAMERAS]
|
||||
groups.append(group)
|
||||
|
||||
# Create rounds
|
||||
rounds = []
|
||||
for i, group in enumerate(groups):
|
||||
rounds.append({
|
||||
'round_number': i + 1,
|
||||
'players': group,
|
||||
'status': 'pending' if i == 0 else 'waiting'
|
||||
})
|
||||
|
||||
tournament_data = {
|
||||
'rounds': rounds,
|
||||
'created_at': datetime.now().isoformat(),
|
||||
'total_players': len(enabled_players),
|
||||
'total_rounds': len(rounds),
|
||||
'current_round': 1,
|
||||
'tournament_type': tournament_type
|
||||
}
|
||||
|
||||
if league_tournament_number:
|
||||
tournament_data['league_tournament_number'] = league_tournament_number
|
||||
|
||||
return tournament_data
|
||||
|
||||
@staticmethod
|
||||
def create_results_structure(tournament_data):
|
||||
"""Create results structure for a tournament"""
|
||||
if not tournament_data:
|
||||
return None
|
||||
|
||||
tournament_type = tournament_data.get('tournament_type', '20_targets')
|
||||
|
||||
# Determine target count and shots per target
|
||||
if tournament_type == '40_targets':
|
||||
num_targets = 40
|
||||
shots_per_target = 2
|
||||
elif tournament_type == '4_targets':
|
||||
num_targets = 4
|
||||
shots_per_target = 5
|
||||
else: # 20_targets (default)
|
||||
num_targets = 20
|
||||
shots_per_target = 2
|
||||
|
||||
results = {
|
||||
'tournament_id': tournament_data.get('created_at', datetime.now().isoformat()),
|
||||
'tournament_type': tournament_type,
|
||||
'participants': {},
|
||||
'tournament_finished': False,
|
||||
'created_at': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
# Add league info if present
|
||||
if 'league_tournament_number' in tournament_data:
|
||||
results['league_tournament_number'] = tournament_data['league_tournament_number']
|
||||
|
||||
# Create structure for each participant
|
||||
all_players = []
|
||||
for round_data in tournament_data['rounds']:
|
||||
all_players.extend(round_data['players'])
|
||||
|
||||
for player in all_players:
|
||||
player_id = str(player['id'])
|
||||
|
||||
# Create target structure based on tournament type
|
||||
targets = {}
|
||||
for i in range(1, num_targets + 1):
|
||||
target = {}
|
||||
for j in range(1, shots_per_target + 1):
|
||||
target[f'shot{j}'] = None
|
||||
targets[str(i)] = target
|
||||
|
||||
results['participants'][player_id] = {
|
||||
'name': player['name'],
|
||||
'targets': targets,
|
||||
'total_score': 0,
|
||||
'completed': False
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
class Scoring:
|
||||
"""Score calculation and ranking"""
|
||||
|
||||
@staticmethod
|
||||
def calculate_total_score(targets):
|
||||
"""Calculate total score from targets, treating None as 0"""
|
||||
total = 0
|
||||
for target in targets.values():
|
||||
for shot_key, shot_value in target.items():
|
||||
if shot_key.startswith('shot') and shot_value is not None:
|
||||
total += shot_value
|
||||
return total
|
||||
|
||||
@staticmethod
|
||||
def is_participant_completed(targets):
|
||||
"""Check if a participant has completed all targets"""
|
||||
for target in targets.values():
|
||||
for shot_key, shot_value in target.items():
|
||||
if shot_key.startswith('shot') and shot_value is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def calculate_league_final_scores(league_data):
|
||||
"""Calculate final league scores using best 4 tournaments"""
|
||||
for participant_id, participant in league_data['participants'].items():
|
||||
tournament_scores = []
|
||||
|
||||
# Get all tournament scores where player participated
|
||||
for result in participant['tournament_results']:
|
||||
if result['participated']:
|
||||
tournament_scores.append(result['score'])
|
||||
|
||||
# Sort scores descending and take best 4
|
||||
tournament_scores.sort(reverse=True)
|
||||
best_scores = tournament_scores[:4] if len(tournament_scores) > 4 else tournament_scores
|
||||
|
||||
participant['final_score'] = sum(best_scores)
|
||||
participant['tournaments_participated'] = len(tournament_scores)
|
||||
|
||||
@staticmethod
|
||||
def get_league_final_rankings(league_data):
|
||||
"""Get final league rankings sorted by final score"""
|
||||
participants = []
|
||||
for player_id, data in league_data['participants'].items():
|
||||
# Calculate total 10s across all tournaments
|
||||
total_tens = sum(
|
||||
result.get('tens_count', 0) for result in data['tournament_results']
|
||||
if result.get('participated', False)
|
||||
)
|
||||
|
||||
participants.append({
|
||||
'id': player_id,
|
||||
'name': data['name'],
|
||||
'final_score': data['final_score'],
|
||||
'total_score': data['total_score'],
|
||||
'tournaments_participated': data['tournaments_participated'],
|
||||
'joker_used': data['joker_used'],
|
||||
'tournament_results': data['tournament_results'],
|
||||
'total_tens': total_tens
|
||||
})
|
||||
|
||||
# Sort by final score (best 4 tournaments) descending, then by total 10s
|
||||
participants.sort(key=lambda x: (x['final_score'], x['total_tens']), reverse=True)
|
||||
|
||||
# Add rankings
|
||||
for i, participant in enumerate(participants):
|
||||
participant['rank'] = i + 1
|
||||
|
||||
return participants
|
||||
|
||||
@staticmethod
|
||||
def calculate_current_league_standings(league_data):
|
||||
"""Calculate current league standings during active league"""
|
||||
participants = []
|
||||
for player_id, data in league_data['participants'].items():
|
||||
tournament_scores = []
|
||||
completed_tournaments = 0
|
||||
total_tens = 0
|
||||
|
||||
for result in data['tournament_results']:
|
||||
if result['participated']:
|
||||
tournament_scores.append(result['score'])
|
||||
completed_tournaments += 1
|
||||
total_tens += result.get('tens_count', 0)
|
||||
|
||||
# Current score is sum of all completed tournaments
|
||||
current_total = sum(tournament_scores)
|
||||
|
||||
# For display, show what the final score would be if we took best 4 now
|
||||
tournament_scores.sort(reverse=True)
|
||||
projected_final = sum(tournament_scores[:4]) if len(tournament_scores) >= 4 else sum(tournament_scores)
|
||||
|
||||
participants.append({
|
||||
'id': player_id,
|
||||
'name': data['name'],
|
||||
'current_total': current_total,
|
||||
'projected_final': projected_final,
|
||||
'tournaments_completed': completed_tournaments,
|
||||
'joker_used': data['joker_used'],
|
||||
'tournament_results': data['tournament_results'],
|
||||
'total_tens': total_tens
|
||||
})
|
||||
|
||||
# Sort by current total score (descending), then by total 10s
|
||||
participants.sort(key=lambda x: (x['current_total'], x['total_tens']), reverse=True)
|
||||
|
||||
# Add rankings
|
||||
for i, participant in enumerate(participants):
|
||||
participant['rank'] = i + 1
|
||||
|
||||
return participants
|
||||
|
||||
|
||||
class RoundManager:
|
||||
"""Tournament round management"""
|
||||
|
||||
@staticmethod
|
||||
def get_current_round_data(tournament_state):
|
||||
"""Get current round data from tournament"""
|
||||
if not tournament_state:
|
||||
return None
|
||||
|
||||
current_round_num = tournament_state.get('current_round', 1)
|
||||
|
||||
# Find the current round
|
||||
for round_data in tournament_state['rounds']:
|
||||
if round_data['round_number'] == current_round_num:
|
||||
return round_data
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_tournament_format_description(tournament_type):
|
||||
"""Get tournament format description"""
|
||||
formats = {
|
||||
'20_targets': '20 Targets, 2 Shots Per Target',
|
||||
'40_targets': '40 Targets, 2 Shots Per Target',
|
||||
'4_targets': '4 Targets, 5 Shots Per Target'
|
||||
}
|
||||
return formats.get(tournament_type, 'Unknown Format')
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
"""
|
||||
File I/O and data persistence for TV_APP
|
||||
Handles all JSON file operations and data archiving
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import glob
|
||||
from datetime import datetime
|
||||
|
||||
# File paths - organized in data directory
|
||||
SETTINGS_FILE = 'data/camera_settings.json'
|
||||
PLAYERS_FILE = 'data/players.json'
|
||||
TOURNAMENT_FILE = 'data/tournament_state.json'
|
||||
RESULTS_FILE = 'data/tournament_results.json'
|
||||
LEAGUE_FILE = 'data/league_state.json'
|
||||
ARCHIVE_DIR = 'data/tournament_archives'
|
||||
LEAGUE_ARCHIVE_DIR = 'data/league_archives'
|
||||
|
||||
# Default settings
|
||||
DEFAULT_SETTINGS = {
|
||||
'camera_titles': {
|
||||
'1': 'Camera 1',
|
||||
'2': 'Camera 2',
|
||||
'3': 'Camera 3',
|
||||
'4': 'Camera 4',
|
||||
'5': 'Camera 5',
|
||||
'6': 'Camera 6'
|
||||
},
|
||||
'display_options': {
|
||||
'show_titles': True,
|
||||
'title_size': 1.1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FileStorage:
|
||||
"""Handle JSON file read/write operations"""
|
||||
|
||||
@staticmethod
|
||||
def _ensure_directory(directory):
|
||||
"""Ensure directory exists"""
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
@staticmethod
|
||||
def _read_json(filepath):
|
||||
"""Read JSON file safely"""
|
||||
try:
|
||||
if os.path.exists(filepath):
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
print(f"Error reading {filepath}: {e}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _write_json(filepath, data):
|
||||
"""Write JSON file safely"""
|
||||
try:
|
||||
directory = os.path.dirname(filepath)
|
||||
FileStorage._ensure_directory(directory)
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
return True
|
||||
except IOError as e:
|
||||
print(f"Error writing {filepath}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
class SettingsStorage(FileStorage):
|
||||
"""Manage camera and display settings"""
|
||||
|
||||
@staticmethod
|
||||
def load_settings():
|
||||
"""Load settings from JSON file, create with defaults if not exists"""
|
||||
settings = FileStorage._read_json(SETTINGS_FILE)
|
||||
|
||||
if settings is None:
|
||||
return DEFAULT_SETTINGS.copy()
|
||||
|
||||
# Ensure all required keys exist (backwards compatibility)
|
||||
for key in DEFAULT_SETTINGS:
|
||||
if key not in settings:
|
||||
settings[key] = DEFAULT_SETTINGS[key]
|
||||
|
||||
for camera_id in DEFAULT_SETTINGS['camera_titles']:
|
||||
if camera_id not in settings.get('camera_titles', {}):
|
||||
settings['camera_titles'][camera_id] = DEFAULT_SETTINGS['camera_titles'][camera_id]
|
||||
|
||||
return settings
|
||||
|
||||
@staticmethod
|
||||
def save_settings(settings):
|
||||
"""Save settings to JSON file"""
|
||||
return FileStorage._write_json(SETTINGS_FILE, settings)
|
||||
|
||||
|
||||
class PlayerStorage(FileStorage):
|
||||
"""Manage player data"""
|
||||
|
||||
DEFAULT_PLAYERS = {
|
||||
'players': [
|
||||
{'id': i, 'name': f'Player {i}', 'enabled': True}
|
||||
for i in range(1, 7) # 6 cameras
|
||||
]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def load_players():
|
||||
"""Load players from JSON file, create with defaults if not exists"""
|
||||
players = FileStorage._read_json(PLAYERS_FILE)
|
||||
|
||||
if players is None:
|
||||
PlayerStorage.save_players(PlayerStorage.DEFAULT_PLAYERS)
|
||||
return PlayerStorage.DEFAULT_PLAYERS.copy()
|
||||
|
||||
return players
|
||||
|
||||
@staticmethod
|
||||
def save_players(players_data):
|
||||
"""Save players to JSON file"""
|
||||
return FileStorage._write_json(PLAYERS_FILE, players_data)
|
||||
|
||||
|
||||
class TournamentStorage(FileStorage):
|
||||
"""Manage tournament state"""
|
||||
|
||||
@staticmethod
|
||||
def load_tournament_state():
|
||||
"""Load tournament state from JSON file"""
|
||||
return FileStorage._read_json(TOURNAMENT_FILE)
|
||||
|
||||
@staticmethod
|
||||
def save_tournament_state(tournament_data):
|
||||
"""Save tournament state to JSON file"""
|
||||
return FileStorage._write_json(TOURNAMENT_FILE, tournament_data)
|
||||
|
||||
|
||||
class ResultsStorage(FileStorage):
|
||||
"""Manage tournament results"""
|
||||
|
||||
@staticmethod
|
||||
def load_results():
|
||||
"""Load results from JSON file"""
|
||||
return FileStorage._read_json(RESULTS_FILE)
|
||||
|
||||
@staticmethod
|
||||
def save_results(results_data):
|
||||
"""Save results to JSON file"""
|
||||
return FileStorage._write_json(RESULTS_FILE, results_data)
|
||||
|
||||
|
||||
class LeagueStorage(FileStorage):
|
||||
"""Manage league state"""
|
||||
|
||||
@staticmethod
|
||||
def load_league_state():
|
||||
"""Load league state from JSON file"""
|
||||
return FileStorage._read_json(LEAGUE_FILE)
|
||||
|
||||
@staticmethod
|
||||
def save_league_state(league_data):
|
||||
"""Save league state to JSON file"""
|
||||
return FileStorage._write_json(LEAGUE_FILE, league_data)
|
||||
|
||||
|
||||
class ArchiveStorage(FileStorage):
|
||||
"""Manage tournament and league archives"""
|
||||
|
||||
@staticmethod
|
||||
def archive_tournament(tournament_data, results_data):
|
||||
"""Archive completed tournament data"""
|
||||
try:
|
||||
FileStorage._ensure_directory(ARCHIVE_DIR)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
archive_filename = f"tournament_{timestamp}.json"
|
||||
archive_path = os.path.join(ARCHIVE_DIR, archive_filename)
|
||||
|
||||
archive_data = {
|
||||
'tournament': tournament_data,
|
||||
'results': results_data,
|
||||
'archived_at': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
success = FileStorage._write_json(archive_path, archive_data)
|
||||
if success:
|
||||
print(f"Tournament archived to: {archive_path}")
|
||||
return success
|
||||
except Exception as e:
|
||||
print(f"Error archiving tournament: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def archive_league(league_data):
|
||||
"""Archive completed league data"""
|
||||
try:
|
||||
FileStorage._ensure_directory(LEAGUE_ARCHIVE_DIR)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
archive_filename = f"league_{timestamp}.json"
|
||||
archive_path = os.path.join(LEAGUE_ARCHIVE_DIR, archive_filename)
|
||||
|
||||
archive_data = {
|
||||
'league': league_data,
|
||||
'archived_at': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
success = FileStorage._write_json(archive_path, archive_data)
|
||||
if success:
|
||||
print(f"League archived to: {archive_path}")
|
||||
return success
|
||||
except Exception as e:
|
||||
print(f"Error archiving league: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_archived_tournaments():
|
||||
"""Get list of standalone archived tournaments"""
|
||||
try:
|
||||
if not os.path.exists(ARCHIVE_DIR):
|
||||
return []
|
||||
|
||||
archives = []
|
||||
for file_path in glob.glob(os.path.join(ARCHIVE_DIR, "tournament_*.json")):
|
||||
try:
|
||||
data = FileStorage._read_json(file_path)
|
||||
if not data:
|
||||
continue
|
||||
|
||||
filename = os.path.basename(file_path)
|
||||
archived_at = data.get('archived_at', 'Unknown')
|
||||
tournament_data = data.get('tournament', {})
|
||||
results_data = data.get('results', {})
|
||||
|
||||
# Skip tournaments that are part of a league
|
||||
if tournament_data.get('league_tournament_number') or results_data.get('league_tournament_number'):
|
||||
continue
|
||||
|
||||
archive_info = {
|
||||
'filename': filename,
|
||||
'filepath': file_path,
|
||||
'archived_at': archived_at,
|
||||
'created_at': tournament_data.get('created_at', 'Unknown'),
|
||||
'tournament_type': tournament_data.get('tournament_type', '20_targets'),
|
||||
'total_players': tournament_data.get('total_players', 0),
|
||||
'total_rounds': tournament_data.get('total_rounds', 0),
|
||||
'tournament_finished': results_data.get('tournament_finished', False),
|
||||
'participants_count': len(results_data.get('participants', {}))
|
||||
}
|
||||
|
||||
archives.append(archive_info)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
print(f"Error reading archive {file_path}: {e}")
|
||||
continue
|
||||
|
||||
# Sort by archived date (newest first)
|
||||
archives.sort(key=lambda x: x['archived_at'], reverse=True)
|
||||
return archives
|
||||
except Exception as e:
|
||||
print(f"Error getting archived tournaments: {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_archived_leagues():
|
||||
"""Get list of all archived leagues"""
|
||||
try:
|
||||
if not os.path.exists(LEAGUE_ARCHIVE_DIR):
|
||||
return []
|
||||
|
||||
archives = []
|
||||
for file_path in glob.glob(os.path.join(LEAGUE_ARCHIVE_DIR, "league_*.json")):
|
||||
try:
|
||||
data = FileStorage._read_json(file_path)
|
||||
if not data:
|
||||
continue
|
||||
|
||||
filename = os.path.basename(file_path)
|
||||
archived_at = data.get('archived_at', 'Unknown')
|
||||
league_data = data.get('league', {})
|
||||
|
||||
archive_info = {
|
||||
'filename': filename,
|
||||
'filepath': file_path,
|
||||
'archived_at': archived_at,
|
||||
'created_at': league_data.get('created_at', 'Unknown'),
|
||||
'league_id': league_data.get('league_id', 'Unknown'),
|
||||
'tournament_type': league_data.get('tournament_type', '20_targets'),
|
||||
'total_tournaments': league_data.get('total_tournaments', 5),
|
||||
'participants_count': len(league_data.get('participants', {})),
|
||||
'league_finished': league_data.get('league_finished', False),
|
||||
'completed_tournaments': len(league_data.get('completed_tournaments', []))
|
||||
}
|
||||
|
||||
archives.append(archive_info)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
print(f"Error reading league archive {file_path}: {e}")
|
||||
continue
|
||||
|
||||
# Sort by archived date (newest first)
|
||||
archives.sort(key=lambda x: x['archived_at'], reverse=True)
|
||||
return archives
|
||||
except Exception as e:
|
||||
print(f"Error getting archived leagues: {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def load_archive_file(filepath):
|
||||
"""Load a specific archive file"""
|
||||
return FileStorage._read_json(filepath)
|
||||
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Utility functions for TV_APP
|
||||
Helper functions for translations, device detection, and common calculations
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from flask import request, session
|
||||
|
||||
DEFAULT_LANGUAGE = 'sl'
|
||||
|
||||
|
||||
def load_translations(language='sl'):
|
||||
"""Load translations for the specified language"""
|
||||
try:
|
||||
locale_file = os.path.join('locales', f'{language}.json')
|
||||
if os.path.exists(locale_file):
|
||||
with open(locale_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Error loading translations for {language}: {e}")
|
||||
|
||||
# Fallback to default language
|
||||
try:
|
||||
default_file = os.path.join('locales', f'{DEFAULT_LANGUAGE}.json')
|
||||
with open(default_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Error loading default translations: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
def get_current_language():
|
||||
"""Get current language from session or default"""
|
||||
return session.get('language', DEFAULT_LANGUAGE)
|
||||
|
||||
|
||||
def get_translations():
|
||||
"""Get translations for current language"""
|
||||
return load_translations(get_current_language())
|
||||
|
||||
|
||||
def is_mobile_device():
|
||||
"""Check if the request is coming from a mobile device"""
|
||||
user_agent = request.headers.get('User-Agent', '').lower()
|
||||
mobile_patterns = [
|
||||
r'android', r'iphone', r'ipad', r'ipod', r'blackberry',
|
||||
r'iemobile', r'opera mini', r'mobile', r'tablet'
|
||||
]
|
||||
return any(re.search(pattern, user_agent) for pattern in mobile_patterns)
|
||||
|
||||
|
||||
def calculate_tens_from_targets(targets):
|
||||
"""Calculate the number of 10s from a targets dictionary"""
|
||||
tens_count = 0
|
||||
if not targets:
|
||||
return 0
|
||||
|
||||
for target in targets.values():
|
||||
if isinstance(target, dict):
|
||||
for shot_key, shot_value in target.items():
|
||||
if shot_key.startswith('shot') and shot_value == 10:
|
||||
tens_count += 1
|
||||
|
||||
return tens_count
|
||||
|
||||
|
||||
def validate_player_data(player_data):
|
||||
"""Validate player data structure"""
|
||||
required_fields = ['id', 'name', 'enabled']
|
||||
return all(field in player_data for field in required_fields)
|
||||
|
||||
|
||||
def validate_settings(settings):
|
||||
"""Validate settings structure"""
|
||||
required_sections = ['camera_titles', 'display_options']
|
||||
return all(section in settings for section in required_sections)
|
||||
Reference in New Issue
Block a user