Files
digiserver-v2/app/app.py

217 lines
6.5 KiB
Python

"""
DigiServer v2 - Application Factory
Modern Flask application with blueprint architecture
"""
import os
from flask import Flask, render_template
from dotenv import load_dotenv
from app.config import DevelopmentConfig, ProductionConfig, TestingConfig
from app.extensions import db, bcrypt, login_manager, migrate, cache
# Load environment variables
load_dotenv()
def create_app(config_name=None):
"""
Application factory pattern
Args:
config_name: Configuration environment (development, production, testing)
Returns:
Flask application instance
"""
# Set instance path to absolute path
instance_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'instance'))
app = Flask(__name__, instance_path=instance_path, instance_relative_config=True)
# Load configuration
if config_name == 'production':
config = ProductionConfig
elif config_name == 'testing':
config = TestingConfig
else:
config = DevelopmentConfig
app.config.from_object(config)
# Initialize extensions
db.init_app(app)
bcrypt.init_app(app)
login_manager.init_app(app)
migrate.init_app(app, db)
cache.init_app(app)
# Configure Flask-Login
configure_login_manager(app)
# Register components
register_blueprints(app)
register_error_handlers(app)
register_commands(app)
register_context_processors(app)
register_template_filters(app)
return app
def register_blueprints(app):
"""Register application blueprints"""
from app.blueprints.main import main_bp
from app.blueprints.auth import auth_bp
from app.blueprints.admin import admin_bp
from app.blueprints.players import players_bp
from app.blueprints.groups import groups_bp
from app.blueprints.content import content_bp
from app.blueprints.playlist import playlist_bp
from app.blueprints.api import api_bp
# Register blueprints (using URL prefixes from blueprint definitions)
app.register_blueprint(main_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(admin_bp)
app.register_blueprint(players_bp)
app.register_blueprint(groups_bp)
app.register_blueprint(content_bp)
app.register_blueprint(playlist_bp)
app.register_blueprint(api_bp)
def configure_login_manager(app):
"""Configure Flask-Login"""
from app.models.user import User
login_manager.login_view = 'auth.login'
login_manager.login_message = 'Please log in to access this page.'
login_manager.login_message_category = 'info'
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
def register_error_handlers(app):
"""Register error handlers"""
@app.errorhandler(404)
def not_found(error):
return render_template('errors/404.html'), 404
@app.errorhandler(403)
def forbidden(error):
return render_template('errors/403.html'), 403
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('errors/500.html'), 500
@app.errorhandler(413)
def request_entity_too_large(error):
return render_template('errors/413.html'), 413
@app.errorhandler(408)
def request_timeout(error):
return render_template('errors/408.html'), 408
def register_commands(app):
"""Register CLI commands"""
import click
@app.cli.command('init-db')
def init_db():
"""Initialize the database"""
db.create_all()
click.echo('Database initialized.')
@app.cli.command('create-admin')
@click.option('--username', default='admin', help='Admin username')
@click.option('--password', prompt=True, hide_input=True, help='Admin password')
def create_admin(username, password):
"""Create an admin user"""
from app.models.user import User
# Check if user exists
if User.query.filter_by(username=username).first():
click.echo(f'User {username} already exists.')
return
# Create admin user
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
admin = User(username=username, password=hashed_password, role='admin')
db.session.add(admin)
db.session.commit()
click.echo(f'Admin user {username} created successfully.')
@app.cli.command('seed-db')
def seed_db():
"""Seed database with sample data (development only)"""
if app.config['ENV'] == 'production':
click.echo('Cannot seed database in production.')
return
click.echo('Seeding database with sample data...')
# Add your seeding logic here
click.echo('Database seeded successfully.')
def register_context_processors(app):
"""Register context processors for templates"""
from flask_login import current_user
@app.context_processor
def inject_config():
"""Inject configuration variables into all templates"""
return {
'server_version': app.config['SERVER_VERSION'],
'build_date': app.config['BUILD_DATE'],
'logo_exists': os.path.exists(
os.path.join(app.root_path, app.config['UPLOAD_FOLDERLOGO'], 'logo.png')
)
}
@app.context_processor
def inject_user_theme():
"""Inject user theme preference"""
theme = 'light'
if current_user.is_authenticated and hasattr(current_user, 'theme'):
theme = current_user.theme
return {'theme': theme}
def register_template_filters(app):
"""Register custom Jinja2 template filters"""
from datetime import datetime, timezone
@app.template_filter('localtime')
def localtime_filter(dt, format='%Y-%m-%d %H:%M'):
"""Convert UTC datetime to local time and format it.
Args:
dt: datetime object in UTC
format: strftime format string
Returns:
Formatted datetime string in local timezone
"""
if dt is None:
return ''
# If datetime is naive (no timezone), assume it's UTC
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
# Convert to local time
local_dt = dt.astimezone()
return local_dt.strftime(format)
# For backwards compatibility and direct running
if __name__ == '__main__':
app = create_app()
app.run(host='0.0.0.0', port=5000, debug=True)