Files
digiserver-v2/app/utils/group_player_management.py
ske087 53ab7fa4ab Add models and utils with type hints and optimizations
Models (6 + 1 association table):
- User: Authentication with bcrypt, admin role check, last_login tracking
- Player: Digital signage devices with auth codes, status tracking, online detection
- Group: Player/content organization with statistics properties
- Content: Media files with type detection, file size helpers, position ordering
- ServerLog: Audit trail with class methods for logging levels
- PlayerFeedback: Player status updates with error tracking
- group_content: Many-to-many association table for groups and content

Model improvements:
- Added type hints to all methods and properties
- Added database indexes on frequently queried columns (username, auth_code, group_id, player_id, position, level, timestamp, status)
- Added comprehensive docstrings
- Added helper properties (is_online, is_admin, file_size_mb, etc.)
- Added relationship back_populates for bidirectional navigation
- Added timestamps (created_at, updated_at, last_seen, uploaded_at)

Utils (4 modules):
- logger.py: Logging utility with level-based functions (info, warning, error)
- uploads.py: File upload handling with progress tracking, video optimization
- group_player_management.py: Player/group status tracking and bulk operations
- pptx_converter.py: PowerPoint to PDF conversion using LibreOffice

Utils improvements:
- Full type hints on all functions
- Comprehensive error handling
- Progress tracking for long-running operations
- Video optimization (H.264, 30fps, max 1080p, 8Mbps)
- Helper functions for time formatting and statistics
- Proper logging of all operations

Performance optimizations:
- Database indexes on all foreign keys and frequently filtered columns
- Lazy loading for relationships where appropriate
- Efficient queries with proper ordering
- Helper properties to avoid repeated calculations

Ready for template migration and testing
2025-11-12 10:26:19 +02:00

210 lines
5.9 KiB
Python

"""Group and player management utilities."""
from typing import Dict, List, Optional
from datetime import datetime, timedelta
from app.extensions import db
from app.models import Player, Group, PlayerFeedback
from app.utils.logger import log_action
def get_player_status_info(player_id: int) -> Dict:
"""Get comprehensive status information for a player.
Args:
player_id: Player ID to query
Returns:
Dictionary with status information
"""
player = Player.query.get(player_id)
if not player:
return {
'online': False,
'status': 'unknown',
'last_seen': None,
'latest_feedback': None
}
# Check if player is online (seen in last 5 minutes)
is_online = False
if player.last_seen:
delta = datetime.utcnow() - player.last_seen
is_online = delta.total_seconds() < 300
# Get latest feedback
latest_feedback = PlayerFeedback.query.filter_by(player_id=player_id)\
.order_by(PlayerFeedback.timestamp.desc())\
.first()
return {
'online': is_online,
'status': player.status,
'last_seen': player.last_seen.isoformat() if player.last_seen else None,
'last_seen_ago': _format_time_ago(player.last_seen) if player.last_seen else 'Never',
'latest_feedback': {
'status': latest_feedback.status,
'message': latest_feedback.message,
'error': latest_feedback.error,
'timestamp': latest_feedback.timestamp.isoformat()
} if latest_feedback else None
}
def get_group_statistics(group_id: int) -> Dict:
"""Get statistics for a group.
Args:
group_id: Group ID to query
Returns:
Dictionary with group statistics
"""
group = Group.query.get(group_id)
if not group:
return {
'total_players': 0,
'online_players': 0,
'total_content': 0,
'error_count': 0
}
total_players = group.player_count
total_content = group.content_count
# Count online players
online_players = 0
error_count = 0
five_min_ago = datetime.utcnow() - timedelta(minutes=5)
for player in group.players:
if player.last_seen and player.last_seen >= five_min_ago:
online_players += 1
if player.status == 'error':
error_count += 1
return {
'group_id': group_id,
'group_name': group.name,
'total_players': total_players,
'online_players': online_players,
'offline_players': total_players - online_players,
'total_content': total_content,
'error_count': error_count
}
def assign_player_to_group(player_id: int, group_id: Optional[int]) -> bool:
"""Assign a player to a group or unassign if group_id is None.
Args:
player_id: Player ID to assign
group_id: Group ID to assign to, or None to unassign
Returns:
True if successful, False otherwise
"""
try:
player = Player.query.get(player_id)
if not player:
log_action('error', f'Player {player_id} not found')
return False
old_group_id = player.group_id
player.group_id = group_id
db.session.commit()
if group_id:
group = Group.query.get(group_id)
log_action('info', f'Player "{player.name}" assigned to group "{group.name}"')
else:
log_action('info', f'Player "{player.name}" unassigned from group')
return True
except Exception as e:
db.session.rollback()
log_action('error', f'Error assigning player to group: {str(e)}')
return False
def bulk_assign_players_to_group(player_ids: List[int], group_id: Optional[int]) -> int:
"""Assign multiple players to a group.
Args:
player_ids: List of player IDs to assign
group_id: Group ID to assign to, or None to unassign
Returns:
Number of players successfully assigned
"""
count = 0
try:
for player_id in player_ids:
player = Player.query.get(player_id)
if player:
player.group_id = group_id
count += 1
db.session.commit()
if group_id:
group = Group.query.get(group_id)
log_action('info', f'Bulk assigned {count} players to group "{group.name}"')
else:
log_action('info', f'Bulk unassigned {count} players from groups')
return count
except Exception as e:
db.session.rollback()
log_action('error', f'Error bulk assigning players: {str(e)}')
return 0
def get_online_players_count() -> int:
"""Get count of online players (seen in last 5 minutes).
Returns:
Number of online players
"""
five_min_ago = datetime.utcnow() - timedelta(minutes=5)
return Player.query.filter(Player.last_seen >= five_min_ago).count()
def get_players_by_status(status: str) -> List[Player]:
"""Get all players with a specific status.
Args:
status: Status to filter by
Returns:
List of Player instances
"""
return Player.query.filter_by(status=status).all()
def _format_time_ago(dt: datetime) -> str:
"""Format datetime as 'time ago' string.
Args:
dt: Datetime to format
Returns:
Formatted string like '5 minutes ago'
"""
delta = datetime.utcnow() - dt
seconds = delta.total_seconds()
if seconds < 60:
return f'{int(seconds)} seconds ago'
elif seconds < 3600:
return f'{int(seconds / 60)} minutes ago'
elif seconds < 86400:
return f'{int(seconds / 3600)} hours ago'
else:
return f'{int(seconds / 86400)} days ago'