402 lines
15 KiB
Python
402 lines
15 KiB
Python
"""Groups blueprint for group management and player assignments."""
|
|
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
|
|
from flask_login import login_required
|
|
from typing import List, Dict
|
|
|
|
from app.extensions import db, cache
|
|
from app.models import Group, Player, Content
|
|
from app.utils.logger import log_action
|
|
from app.utils.group_player_management import get_player_status_info, get_group_statistics
|
|
|
|
groups_bp = Blueprint('groups', __name__, url_prefix='/groups')
|
|
|
|
|
|
@groups_bp.route('/')
|
|
@login_required
|
|
def groups_list():
|
|
"""Display list of all groups."""
|
|
try:
|
|
groups = Group.query.order_by(Group.name).all()
|
|
|
|
# Get statistics for each group
|
|
group_stats = {}
|
|
for group in groups:
|
|
stats = get_group_statistics(group.id)
|
|
group_stats[group.id] = stats
|
|
|
|
return render_template('groups/groups_list.html',
|
|
groups=groups,
|
|
group_stats=group_stats)
|
|
except Exception as e:
|
|
log_action('error', f'Error loading groups list: {str(e)}')
|
|
flash('Error loading groups list.', 'danger')
|
|
return redirect(url_for('main.dashboard'))
|
|
|
|
|
|
@groups_bp.route('/create', methods=['GET', 'POST'])
|
|
@login_required
|
|
def create_group():
|
|
"""Create a new group."""
|
|
if request.method == 'GET':
|
|
available_content = Content.query.order_by(Content.filename).all()
|
|
return render_template('groups/create_group.html', available_content=available_content)
|
|
|
|
try:
|
|
name = request.form.get('name', '').strip()
|
|
description = request.form.get('description', '').strip()
|
|
content_ids = request.form.getlist('content_ids')
|
|
|
|
# Validation
|
|
if not name or len(name) < 3:
|
|
flash('Group name must be at least 3 characters long.', 'warning')
|
|
return redirect(url_for('groups.create_group'))
|
|
|
|
# Check if group name exists
|
|
existing_group = Group.query.filter_by(name=name).first()
|
|
if existing_group:
|
|
flash(f'Group "{name}" already exists.', 'warning')
|
|
return redirect(url_for('groups.create_group'))
|
|
|
|
# Create group
|
|
new_group = Group(
|
|
name=name,
|
|
description=description or None
|
|
)
|
|
|
|
# Add content to group
|
|
if content_ids:
|
|
for content_id in content_ids:
|
|
content = Content.query.get(int(content_id))
|
|
if content:
|
|
new_group.contents.append(content)
|
|
|
|
db.session.add(new_group)
|
|
db.session.commit()
|
|
|
|
log_action('info', f'Group "{name}" created with {len(content_ids)} content items')
|
|
flash(f'Group "{name}" created successfully.', 'success')
|
|
|
|
return redirect(url_for('groups.groups_list'))
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
log_action('error', f'Error creating group: {str(e)}')
|
|
flash('Error creating group. Please try again.', 'danger')
|
|
return redirect(url_for('groups.create_group'))
|
|
|
|
|
|
@groups_bp.route('/<int:group_id>/edit', methods=['GET', 'POST'])
|
|
@login_required
|
|
def edit_group(group_id: int):
|
|
"""Edit group details."""
|
|
group = Group.query.get_or_404(group_id)
|
|
|
|
if request.method == 'GET':
|
|
available_content = Content.query.order_by(Content.filename).all()
|
|
return render_template('groups/edit_group.html',
|
|
group=group,
|
|
available_content=available_content)
|
|
|
|
try:
|
|
name = request.form.get('name', '').strip()
|
|
description = request.form.get('description', '').strip()
|
|
content_ids = request.form.getlist('content_ids')
|
|
|
|
# Validation
|
|
if not name or len(name) < 3:
|
|
flash('Group name must be at least 3 characters long.', 'warning')
|
|
return redirect(url_for('groups.edit_group', group_id=group_id))
|
|
|
|
# Check if group name exists (excluding current group)
|
|
existing_group = Group.query.filter(Group.name == name, Group.id != group_id).first()
|
|
if existing_group:
|
|
flash(f'Group name "{name}" is already in use.', 'warning')
|
|
return redirect(url_for('groups.edit_group', group_id=group_id))
|
|
|
|
# Update group
|
|
group.name = name
|
|
group.description = description or None
|
|
|
|
# Update content
|
|
group.contents = []
|
|
if content_ids:
|
|
for content_id in content_ids:
|
|
content = Content.query.get(int(content_id))
|
|
if content:
|
|
group.contents.append(content)
|
|
|
|
db.session.commit()
|
|
|
|
# Clear cache for all players in this group
|
|
for player in group.players:
|
|
cache.delete_memoized('get_player_playlist', player.id)
|
|
|
|
log_action('info', f'Group "{name}" (ID: {group_id}) updated')
|
|
flash(f'Group "{name}" updated successfully.', 'success')
|
|
|
|
return redirect(url_for('groups.groups_list'))
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
log_action('error', f'Error updating group: {str(e)}')
|
|
flash('Error updating group. Please try again.', 'danger')
|
|
return redirect(url_for('groups.edit_group', group_id=group_id))
|
|
|
|
|
|
@groups_bp.route('/<int:group_id>/delete', methods=['POST'])
|
|
@login_required
|
|
def delete_group(group_id: int):
|
|
"""Delete a group."""
|
|
try:
|
|
group = Group.query.get_or_404(group_id)
|
|
group_name = group.name
|
|
|
|
# Unassign players from group
|
|
for player in group.players:
|
|
player.group_id = None
|
|
cache.delete_memoized('get_player_playlist', player.id)
|
|
|
|
db.session.delete(group)
|
|
db.session.commit()
|
|
|
|
log_action('info', f'Group "{group_name}" (ID: {group_id}) deleted')
|
|
flash(f'Group "{group_name}" deleted successfully.', 'success')
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
log_action('error', f'Error deleting group: {str(e)}')
|
|
flash('Error deleting group. Please try again.', 'danger')
|
|
|
|
return redirect(url_for('groups.groups_list'))
|
|
|
|
|
|
@groups_bp.route('/<int:group_id>/manage')
|
|
@login_required
|
|
def manage_group(group_id: int):
|
|
"""Manage group with player status cards and content."""
|
|
try:
|
|
group = Group.query.get_or_404(group_id)
|
|
|
|
# Get all players in this group
|
|
players = group.players.order_by(Player.name).all()
|
|
|
|
# Get player status for each player
|
|
player_statuses = {}
|
|
for player in players:
|
|
status_info = get_player_status_info(player.id)
|
|
player_statuses[player.id] = status_info
|
|
|
|
# Get group content
|
|
contents = group.contents.order_by(Content.position).all()
|
|
|
|
# Get available players (not in this group)
|
|
available_players = Player.query.filter(
|
|
(Player.group_id == None) | (Player.group_id != group_id)
|
|
).order_by(Player.name).all()
|
|
|
|
# Get available content (not in this group)
|
|
all_content = Content.query.order_by(Content.filename).all()
|
|
|
|
return render_template('groups/manage_group.html',
|
|
group=group,
|
|
players=players,
|
|
player_statuses=player_statuses,
|
|
contents=contents,
|
|
available_players=available_players,
|
|
all_content=all_content)
|
|
except Exception as e:
|
|
log_action('error', f'Error loading manage group page: {str(e)}')
|
|
flash('Error loading manage group page.', 'danger')
|
|
return redirect(url_for('groups.groups_list'))
|
|
|
|
|
|
@groups_bp.route('/<int:group_id>/fullscreen')
|
|
def group_fullscreen(group_id: int):
|
|
"""Display group fullscreen view with all player status cards."""
|
|
try:
|
|
group = Group.query.get_or_404(group_id)
|
|
|
|
# Get all players in this group
|
|
players = group.players.order_by(Player.name).all()
|
|
|
|
# Get player status for each player
|
|
player_statuses = {}
|
|
for player in players:
|
|
status_info = get_player_status_info(player.id)
|
|
player_statuses[player.id] = status_info
|
|
|
|
return render_template('groups/group_fullscreen.html',
|
|
group=group,
|
|
players=players,
|
|
player_statuses=player_statuses)
|
|
except Exception as e:
|
|
log_action('error', f'Error loading group fullscreen: {str(e)}')
|
|
return "Error loading group fullscreen", 500
|
|
|
|
|
|
@groups_bp.route('/<int:group_id>/add-player', methods=['POST'])
|
|
@login_required
|
|
def add_player_to_group(group_id: int):
|
|
"""Add a player to a group."""
|
|
try:
|
|
group = Group.query.get_or_404(group_id)
|
|
player_id = request.form.get('player_id')
|
|
|
|
if not player_id:
|
|
flash('No player selected.', 'warning')
|
|
return redirect(url_for('groups.manage_group', group_id=group_id))
|
|
|
|
player = Player.query.get_or_404(int(player_id))
|
|
player.group_id = group_id
|
|
db.session.commit()
|
|
|
|
# Clear cache
|
|
cache.delete_memoized('get_player_playlist', player.id)
|
|
|
|
log_action('info', f'Player "{player.name}" added to group "{group.name}"')
|
|
flash(f'Player "{player.name}" added to group successfully.', 'success')
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
log_action('error', f'Error adding player to group: {str(e)}')
|
|
flash('Error adding player to group. Please try again.', 'danger')
|
|
|
|
return redirect(url_for('groups.manage_group', group_id=group_id))
|
|
|
|
|
|
@groups_bp.route('/<int:group_id>/remove-player/<int:player_id>', methods=['POST'])
|
|
@login_required
|
|
def remove_player_from_group(group_id: int, player_id: int):
|
|
"""Remove a player from a group."""
|
|
try:
|
|
player = Player.query.get_or_404(player_id)
|
|
|
|
if player.group_id != group_id:
|
|
flash('Player is not in this group.', 'warning')
|
|
return redirect(url_for('groups.manage_group', group_id=group_id))
|
|
|
|
player_name = player.name
|
|
player.group_id = None
|
|
db.session.commit()
|
|
|
|
# Clear cache
|
|
cache.delete_memoized('get_player_playlist', player_id)
|
|
|
|
log_action('info', f'Player "{player_name}" removed from group {group_id}')
|
|
flash(f'Player "{player_name}" removed from group successfully.', 'success')
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
log_action('error', f'Error removing player from group: {str(e)}')
|
|
flash('Error removing player from group. Please try again.', 'danger')
|
|
|
|
return redirect(url_for('groups.manage_group', group_id=group_id))
|
|
|
|
|
|
@groups_bp.route('/<int:group_id>/add-content', methods=['POST'])
|
|
@login_required
|
|
def add_content_to_group(group_id: int):
|
|
"""Add content to a group."""
|
|
try:
|
|
group = Group.query.get_or_404(group_id)
|
|
content_ids = request.form.getlist('content_ids')
|
|
|
|
if not content_ids:
|
|
flash('No content selected.', 'warning')
|
|
return redirect(url_for('groups.manage_group', group_id=group_id))
|
|
|
|
# Add content
|
|
added_count = 0
|
|
for content_id in content_ids:
|
|
content = Content.query.get(int(content_id))
|
|
if content and content not in group.contents:
|
|
group.contents.append(content)
|
|
added_count += 1
|
|
|
|
db.session.commit()
|
|
|
|
# Clear cache for all players in this group
|
|
for player in group.players:
|
|
cache.delete_memoized('get_player_playlist', player.id)
|
|
|
|
log_action('info', f'{added_count} content items added to group "{group.name}"')
|
|
flash(f'{added_count} content items added successfully.', 'success')
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
log_action('error', f'Error adding content to group: {str(e)}')
|
|
flash('Error adding content to group. Please try again.', 'danger')
|
|
|
|
return redirect(url_for('groups.manage_group', group_id=group_id))
|
|
|
|
|
|
@groups_bp.route('/<int:group_id>/remove-content/<int:content_id>', methods=['POST'])
|
|
@login_required
|
|
def remove_content_from_group(group_id: int, content_id: int):
|
|
"""Remove content from a group."""
|
|
try:
|
|
group = Group.query.get_or_404(group_id)
|
|
content = Content.query.get_or_404(content_id)
|
|
|
|
if content not in group.contents:
|
|
flash('Content is not in this group.', 'warning')
|
|
return redirect(url_for('groups.manage_group', group_id=group_id))
|
|
|
|
group.contents.remove(content)
|
|
db.session.commit()
|
|
|
|
# Clear cache for all players in this group
|
|
for player in group.players:
|
|
cache.delete_memoized('get_player_playlist', player.id)
|
|
|
|
log_action('info', f'Content "{content.filename}" removed from group "{group.name}"')
|
|
flash('Content removed from group successfully.', 'success')
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
log_action('error', f'Error removing content from group: {str(e)}')
|
|
flash('Error removing content from group. Please try again.', 'danger')
|
|
|
|
return redirect(url_for('groups.manage_group', group_id=group_id))
|
|
|
|
|
|
@groups_bp.route('/<int:group_id>/reorder-content', methods=['POST'])
|
|
@login_required
|
|
def reorder_group_content(group_id: int):
|
|
"""Reorder content within a group."""
|
|
try:
|
|
group = Group.query.get_or_404(group_id)
|
|
content_order = request.json.get('order', [])
|
|
|
|
# Update positions
|
|
for idx, content_id in enumerate(content_order):
|
|
content = Content.query.get(content_id)
|
|
if content and content in group.contents:
|
|
content.position = idx
|
|
|
|
db.session.commit()
|
|
|
|
# Clear cache for all players in this group
|
|
for player in group.players:
|
|
cache.delete_memoized('get_player_playlist', player.id)
|
|
|
|
log_action('info', f'Content reordered for group "{group.name}"')
|
|
return jsonify({'success': True})
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
log_action('error', f'Error reordering group content: {str(e)}')
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
@groups_bp.route('/<int:group_id>/stats')
|
|
@login_required
|
|
def group_stats(group_id: int):
|
|
"""Get group statistics as JSON."""
|
|
try:
|
|
stats = get_group_statistics(group_id)
|
|
return jsonify(stats)
|
|
except Exception as e:
|
|
log_action('error', f'Error getting group stats: {str(e)}')
|
|
return jsonify({'error': str(e)}), 500
|