updated solution

This commit is contained in:
2025-07-31 16:37:54 +03:00
parent 756f9052b5
commit c8bbbebb48
31 changed files with 199 additions and 190 deletions
+21
View File
@@ -0,0 +1,21 @@
# .env - Flask environment variables
# Flask secret key (change this to something secure in production)
SECRET_KEY=Ana_Are_Multe_Mere-Si_Nu_Are_Pere
# Flask environment: development or production
FLASK_ENV=development
# Database location (optional, defaults to instance/dashboard.db)
# SQLALCHEMY_DATABASE_URI=sqlite:///instance/dashboard.db
# Default admin user credentials (used for auto-creation)
DEFAULT_USER=admin
DEFAULT_PASSWORD=1234
# Flask server settings
HOST=0.0.0.0
PORT=5000
# Maximum upload size (in bytes, 2GB)
MAX_CONTENT_LENGTH=2147483648
+1
View File
@@ -1,3 +1,4 @@
digiscreen/
.env
Binary file not shown.
Binary file not shown.
+25 -6
View File
@@ -7,9 +7,13 @@ from werkzeug.utils import secure_filename
from functools import wraps
from extensions import db, bcrypt, login_manager
from sqlalchemy import text
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# First import models
from models import User, Player, Content, Group, ServerLog
from models import User, Player, Group, Content, ServerLog, group_player
# Then import utilities that use the models
from flask_login import login_user, logout_user, login_required, current_user
@@ -47,8 +51,10 @@ app = Flask(__name__, instance_relative_config=True)
# Set the secret key from environment variable or use a default value
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'Ana_Are_Multe_Mere-Si_Nu_Are_Pere')
# Configure the database location to be in the instance folder
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(app.instance_path, 'dashboard.db')
instance_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'instance'))
os.makedirs(instance_dir, exist_ok=True)
db_path = os.path.join(instance_dir, 'dashboard.db')
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Set maximum content length to 1GB
@@ -56,6 +62,7 @@ app.config['MAX_CONTENT_LENGTH'] = 2048 * 2048 * 2048 # 2GB, adjust as needed
# Ensure the instance folder exists
os.makedirs(app.instance_path, exist_ok=True)
os.makedirs(instance_dir, exist_ok=True)
db.init_app(app)
bcrypt.init_app(app)
@@ -68,8 +75,9 @@ app.config['UPLOAD_FOLDERLOGO'] = UPLOAD_FOLDERLOGO
# Ensure the upload folder exists
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
os.makedirs(UPLOAD_FOLDERLOGO)
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
if not os.path.exists(UPLOAD_FOLDERLOGO):
os.makedirs(UPLOAD_FOLDERLOGO, exist_ok=True)
login_manager.login_view = 'login'
@@ -303,7 +311,8 @@ def add_player():
hostname = request.form['hostname']
password = bcrypt.generate_password_hash(request.form['password']).decode('utf-8')
quickconnect_password = bcrypt.generate_password_hash(request.form['quickconnect_password']).decode('utf-8')
add_player_util(username, hostname, password, quickconnect_password)
orientation = request.form.get('orientation', 'Landscape') # <-- Get orientation
add_player_util(username, hostname, password, quickconnect_password, orientation) # <-- Pass orientation
flash(f'Player "{username}" added successfully.', 'success')
return redirect(url_for('dashboard'))
return render_template('add_player.html')
@@ -637,6 +646,16 @@ def create_admin(username, password):
db.session.commit()
print(f"Admin user '{username}' created successfully.")
from models.create_default_user import create_default_user
with app.app_context():
try:
db.session.execute(db.select(User).limit(1))
except Exception as e:
print("Database not initialized or missing tables. Re-initializing...")
db.create_all()
# Always ensure default user exists
create_default_user(db, User, bcrypt)
# Add this at the end of app.py
if __name__ == '__main__':
-14
View File
@@ -1,14 +0,0 @@
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
# Create a minimal Flask app just for clearing the database
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///instance/dashboard.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
with app.app_context():
db.reflect() # This loads all tables from the database
db.drop_all()
print("Dropped all tables successfully.")
-20
View File
@@ -1,20 +0,0 @@
from app import app, db, User, bcrypt
# Create the default user
username = 'admin'
password = '1234'
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
with app.app_context():
# Delete the existing user if it exists
existing_user = User.query.filter_by(username=username).first()
if existing_user:
db.session.delete(existing_user)
db.session.commit()
# Add the new user to the database
default_user = User(username=username, password=hashed_password, role='admin')
db.session.add(default_user)
db.session.commit()
print(f"Default user '{username}' created with password '{password}'")
Binary file not shown.
-77
View File
@@ -1,77 +0,0 @@
from extensions import db
from flask_bcrypt import Bcrypt
from flask_login import UserMixin
import datetime # Add this import
bcrypt = Bcrypt()
# Add this new model
class ServerLog(db.Model):
id = db.Column(db.Integer, primary_key=True)
action = db.Column(db.String(255), nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.datetime.utcnow)
def __repr__(self):
return f"<ServerLog {self.action}>"
class Content(db.Model):
id = db.Column(db.Integer, primary_key=True)
file_name = db.Column(db.String(255), nullable=False)
duration = db.Column(db.Integer, nullable=False)
player_id = db.Column(db.Integer, db.ForeignKey('player.id'), nullable=False)
position = db.Column(db.Integer, default=0) # This field must exist
class Player(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(255), nullable=False)
hostname = db.Column(db.String(255), nullable=False)
password = db.Column(db.String(255), nullable=False)
quickconnect_password = db.Column(db.String(255), nullable=True)
playlist_version = db.Column(db.Integer, default=1) # Make sure this exists
locked_to_group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=True)
locked_to_group = db.relationship('Group', foreign_keys=[locked_to_group_id], backref='locked_players')
def verify_quickconnect_code(self, code):
return bcrypt.check_password_hash(self.quickconnect_password, code)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
role = db.Column(db.String(80), nullable=False)
theme = db.Column(db.String(80), default='light')
def set_password(self, password):
self.password = bcrypt.generate_password_hash(password).decode('utf-8')
def check_password(self, password):
return bcrypt.check_password_hash(self.password, password)
@property
def is_active(self):
return True
@property
def is_authenticated(self):
return True
@property
def is_anonymous(self):
return False
def get_id(self):
return str(self.id)
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False, unique=True)
players = db.relationship('Player', secondary='group_player', backref='groups')
playlist_version = db.Column(db.Integer, default=0) # Playlist version counter
# Association table for many-to-many relationship between Group and Player
group_player = db.Table('group_player',
db.Column('group_id', db.Integer, db.ForeignKey('group.id'), primary_key=True),
db.Column('player_id', db.Integer, db.ForeignKey('player.id'), primary_key=True)
)
# other models...
+5
View File
@@ -0,0 +1,5 @@
from .user import User
from .player import Player
from .group import Group, group_player
from .content import Content
from .server_log import ServerLog
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
# Ensure the instance directory exists (relative to project root)
instance_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'instance'))
os.makedirs(instance_dir, exist_ok=True)
# Set the correct database URI
db_path = os.path.join(instance_dir, 'dashboard.db')
print(f"Using database at: {db_path}")
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
with app.app_context():
db.reflect() # This loads all tables from the database
db.drop_all()
print("Dropped all tables successfully.")
+8
View File
@@ -0,0 +1,8 @@
from extensions import db
class Content(db.Model):
id = db.Column(db.Integer, primary_key=True)
file_name = db.Column(db.String(255), nullable=False)
duration = db.Column(db.Integer, nullable=False)
player_id = db.Column(db.Integer, db.ForeignKey('player.id'), nullable=False)
position = db.Column(db.Integer, default=0)
+18
View File
@@ -0,0 +1,18 @@
#from app import app, db, User, bcrypt
import os
def create_default_user(db, User, bcrypt):
username = os.getenv('DEFAULT_USER', 'admin')
password = os.getenv('DEFAULT_PASSWORD', '1234')
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
existing_user = User.query.filter_by(username=username).first()
if not existing_user:
default_user = User(username=username, password=hashed_password, role='admin')
db.session.add(default_user)
db.session.commit()
print(f"Default user '{username}' created with password '{password}'")
else:
print(f"Default user '{username}' already exists.")
#with app.app_context():
# create_default_user(db, User, bcrypt)
+13
View File
@@ -0,0 +1,13 @@
from extensions import db
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False, unique=True)
orientation = db.Column(db.String(16), nullable=False, default='Landscape') # <-- Add this line
players = db.relationship('Player', secondary='group_player', backref='groups')
playlist_version = db.Column(db.Integer, default=0)
group_player = db.Table('group_player',
db.Column('group_id', db.Integer, db.ForeignKey('group.id'), primary_key=True),
db.Column('player_id', db.Integer, db.ForeignKey('player.id'), primary_key=True)
)
View File
+18
View File
@@ -0,0 +1,18 @@
from extensions import db
from flask_bcrypt import Bcrypt
bcrypt = Bcrypt()
class Player(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(255), nullable=False)
hostname = db.Column(db.String(255), nullable=False)
password = db.Column(db.String(255), nullable=False)
quickconnect_password = db.Column(db.String(255), nullable=True)
playlist_version = db.Column(db.Integer, default=1)
locked_to_group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=True)
locked_to_group = db.relationship('Group', foreign_keys=[locked_to_group_id], backref='locked_players')
orientation = db.Column(db.String(16), nullable=False, default='Landscape') # <-- Add this line
def verify_quickconnect_code(self, code):
return bcrypt.check_password_hash(self.quickconnect_password, code)
+10
View File
@@ -0,0 +1,10 @@
from extensions import db
import datetime
class ServerLog(db.Model):
id = db.Column(db.Integer, primary_key=True)
action = db.Column(db.String(255), nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.datetime.utcnow)
def __repr__(self):
return f"<ServerLog {self.action}>"
+33
View File
@@ -0,0 +1,33 @@
from extensions import db
from flask_bcrypt import Bcrypt
from flask_login import UserMixin
bcrypt = Bcrypt()
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
role = db.Column(db.String(80), nullable=False)
theme = db.Column(db.String(80), default='light')
def set_password(self, password):
self.password = bcrypt.generate_password_hash(password).decode('utf-8')
def check_password(self, password):
return bcrypt.check_password_hash(self.password, password)
@property
def is_active(self):
return True
@property
def is_authenticated(self):
return True
@property
def is_anonymous(self):
return False
def get_id(self):
return str(self.id)
+11
View File
@@ -60,6 +60,17 @@
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 col-12">
<div class="mb-3">
<label for="orientation" class="form-label">Orientation</label>
<select class="form-control {{ 'dark-mode' if theme == 'dark' else '' }}" id="orientation" name="orientation" required>
<option value="Landscape" selected>Landscape</option>
<option value="Portret">Portret</option>
</select>
</div>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary">Add Player</button>
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary mt-3">Back to Dashboard</a>
-60
View File
@@ -1,60 +0,0 @@
import requests
import os
# Replace with the actual server IP address or domain name, hostname, and quick connect code
server_ip = 'http://localhost:5000'
hostname = 'rpi-tv11'
quickconnect_code = '8887779'
# Construct the URL for the playlist API
url = f'{server_ip}/api/playlists'
params = {
'hostname': hostname,
'quickconnect_code': quickconnect_code
}
# Make the GET request to the API
response = requests.get(url, params=params)
# Print the raw response content and status code for debugging
print(f'Status Code: {response.status_code}')
print(f'Response Content: {response.text}')
# Check if the request was successful
if response.status_code == 200:
try:
# Parse the JSON response
response_data = response.json()
playlist = response_data.get('playlist', [])
playlist_version = response_data.get('playlist_version', None)
print(f'Playlist Version: {playlist_version}')
print(f'Playlist: {playlist}')
# Define the local folder for saving files
local_folder = './static/resurse'
if not os.path.exists(local_folder):
os.makedirs(local_folder)
# Download each file in the playlist
for media in playlist:
file_name = media.get('file_name', '')
file_url = media.get('url', '')
duration = media.get('duration', 10) # Default duration if not provided
local_file_path = os.path.join(local_folder, file_name)
print(f'Downloading {file_name} from {file_url}...')
try:
file_response = requests.get(file_url, timeout=10)
if file_response.status_code == 200:
with open(local_file_path, 'wb') as file:
file.write(file_response.content)
print(f'Successfully downloaded {file_name} to {local_file_path}')
else:
print(f'Failed to download {file_name}. Status Code: {file_response.status_code}')
except requests.exceptions.RequestException as e:
print(f'Error downloading {file_name}: {e}')
except requests.exceptions.JSONDecodeError as e:
print(f'Failed to parse JSON response: {e}')
else:
print(f'Failed to retrieve playlist. Status Code: {response.status_code}')
Binary file not shown.
Binary file not shown.
+15 -13
View File
@@ -8,28 +8,29 @@ from utils.logger import (
log_content_duration_changed, log_content_added
)
def create_group(name, player_ids):
def create_group(name, player_ids, orientation='Landscape'):
"""
Create a new group with the given name and add selected players to it.
Clears individual playlists of players and locks them to the group.
Create a new group with the given name, orientation, and add selected players.
Only players with the same orientation can be added.
"""
new_group = Group(name=name)
# Check all players have the same orientation
for player_id in player_ids:
player = Player.query.get(player_id)
if player and player.orientation != orientation:
raise ValueError(f"Player '{player.username}' has orientation '{player.orientation}', which does not match group orientation '{orientation}'.")
new_group = Group(name=name, orientation=orientation)
db.session.add(new_group)
db.session.flush() # Get the group ID
# Add players to the group and lock them
for player_id in player_ids:
player = Player.query.get(player_id)
if player:
# Add player to group
new_group.players.append(player)
# Delete player's individual playlist
Content.query.filter_by(player_id=player.id).delete()
# Lock player to this group
player.locked_to_group_id = new_group.id
db.session.commit()
log_group_created(name)
return new_group
@@ -106,7 +107,7 @@ def delete_group(group_id):
db.session.commit()
log_group_deleted(group_name)
def add_player(username, hostname, password, quickconnect_password):
def add_player(username, hostname, password, quickconnect_password, orientation='Landscape'):
"""
Add a new player with the given details.
"""
@@ -120,7 +121,8 @@ def add_player(username, hostname, password, quickconnect_password):
username=username,
hostname=hostname,
password=hashed_password,
quickconnect_password=hashed_quickconnect
quickconnect_password=hashed_quickconnect,
orientation=orientation
)
db.session.add(new_player)