Select Git revision
gen_plotly.py
-
Jiří Kalvoda authoredJiří Kalvoda authored
__init__.py 7.09 KiB
from flask import Flask, request, g, session
import flask.logging
import flask.wrappers
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
import locale
import logging
import os
import tempfile
from typing import Optional
import werkzeug.exceptions
import werkzeug.formparser
import mo
import mo.config as config
import mo.db as db
import mo.ext.assets
import mo.jobs
import mo.rights
import mo.users
import mo.util
# Ohýbáme Flask, aby uploadované soubory ukládal do adresáře podle našeho přání,
# aby se pak daly zařadit mezi datové soubory prostým hardlinkováním. Za tímto účelem
# subclassujeme Request, aby použil subclassovaný FormDataParser, který použije naši
# stream factory místo defaultní.
def mo_stream_factory(total_content_length, filename, content_type, content_length=None):
return tempfile.NamedTemporaryFile(dir=mo.util.data_dir('tmp'), prefix='upload-')
class FormDataParser(werkzeug.formparser.FormDataParser):
def __init__(self,
stream_factory=None,
charset='utf-8',
errors='replace',
max_form_memory_size=None,
max_content_length=None,
cls=None,
silent=True):
super().__init__(mo_stream_factory, charset, errors, max_form_memory_size, max_content_length, cls, silent)
class Request(flask.wrappers.Request):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_data_parser_class = FormDataParser
# Můžeme zvýšit maximální velikost souboru pro dávkové uploady
custom_max_content_length: Optional[int] = None
# Původně atribut ve werkzeug.BaseRequest, předefinován na property ve flask.Request
@property
def max_content_length(self):
return self.custom_max_content_length or mo.config.MAX_CONTENT_LENGTH
# Flask interpretuje relativní cesty všelijak, tak mu vyrobíme absolutní
mo.config.DATA_DIR = os.path.abspath(mo.config.DATA_DIR)
static_dir = os.path.abspath('static')
# Aplikační objekt
app = Flask(__name__, static_folder=static_dir)
app.config.from_object(config)
app.request_class = Request
db.flask_db = SQLAlchemy(app, metadata=db.metadata)
Bootstrap(app) # make bootstrap libs accessible for the app
# Budeme používat české locale
locale.setlocale(locale.LC_COLLATE, 'cs_CZ.UTF-8')
# Incializace logování
def setup_logging():
# Logování v Pythonu je vymyšlené hezky, ale bohužel spousta modulů má tendenci
# konfigurovat si u svých loggerů náhodné handlery. Pokud chceme, aby bylo logování
# aspoň trochu jednotné, musíme jim to trochu předrátovat.
# Nastavíme root logger, aby logoval po našem do flaskového logu
log_handler = flask.logging.default_handler
log_formatter = logging.Formatter(fmt='%(asctime)-15s.%(msecs)03d %(levelname)-5.5s [%(process)s] (%(name)s) %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
log_handler.setFormatter(log_formatter)
root_logger = logging.getLogger()
root_logger.addHandler(log_handler)
# app.logger se jmenuje "mo.web" a propaguje se do root loggeru,
# takže nechceme, aby měl svůj handler.
app.logger.removeHandler(log_handler)
app.logger.setLevel(logging.DEBUG)
# sqlalchemy má logger, ke kterému si při prvním vypsání hlášky přidá handler,
# není-li tam zatím žádný. Tak přidáme dummy handler. Vše se propaguje do root loggeru.
sqla_logger = logging.getLogger('sqlalchemy.engine.base.Engine')
sqla_logger.addHandler(logging.NullHandler())
# werkzeug má vlastní handler s vlastním formátem hlášek, který už obsahuje timestamp,
# takže vypneme propagování
wz_logger = logging.getLogger('werkzeug')
wz_logger.propagate = False
setup_logging()
# Incializace assetového mechanismu
mo.ext.assets.Assets(app, url_prefix='/assets', asset_dir=static_dir)
app.assets.add_assets([
'bootstrap.min.css',
'mo.css',
'js/news-reloader.js',
'js/osmo.js',
])
# Inicializace požadavků a nucená autorizace
class NeedLoginError(werkzeug.exceptions.Forbidden):
description = 'Need to log in'
def need_login():
if not g.user:
raise NeedLoginError()
def init_request():
path = request.path
# XXX: Když celá aplikace běží v adresáři, request.path je relativní ke kořeni aplikace, ne celého webu
if path.startswith('/static/') or path.startswith('/assets/'):
# Pro statické soubory v development nasazení nepotřebujeme nastavovat
# nic dalšího (v ostrém nasazení je servíruje uwsgi)
return
if 'uid' in session:
user = mo.users.user_by_uid(session['uid'])
if not user:
# Uživatel mezitím přestal existovat
app.logger.error('Zrušena session pro neexistujícího uživatele uid=%s', session['uid'])
return mo.web.acct.logout()
else:
user = None
g.user = user
if have_uwsgi:
uwsgi.set_logvar('osmo_uid', str(user.user_id) if user else '-')
mo.now = mo.util.get_now()
g.now = mo.now # for templates
mo.util.current_log_user = user
# K některým podstromům je nutné mít speciální oprávnění
if path.startswith('/org/'):
if not user:
raise NeedLoginError()
if not (user.is_org or user.is_admin):
raise werkzeug.exceptions.Forbidden()
g.gatekeeper = mo.rights.Gatekeeper(user)
elif path.startswith('/user/'):
if not user:
raise NeedLoginError()
app.before_request(init_request)
### UWSGI glue ###
# Čas od času se probudíme a spustíme garbage collector:
# - projdeme joby pro případ, že by se ztratil signál
# - expirujeme zastaralé joby
# - expirujeme zastaralé registrační tokeny
@app.cli.command('gc')
def gc():
"""Run garbage collector."""
mo.now = mo.util.get_now()
mo.jobs.process_jobs()
mo.users.expire_reg_requests()
try:
import uwsgi
from uwsgidecorators import timer, signal
@timer(config.GC_PERIOD, target='mule')
def mule_timer(signum):
# app.logger.debug('Mule: Timer tick')
with app.app_context():
garbage_collect()
# Obykle při vložení jobu dostaneme signál.
@signal(42, target='mule')
def mule_signal(signum):
app.logger.debug('Mule: Přijat signál')
with app.app_context():
mo.now = mo.util.get_now()
mo.jobs.process_jobs()
def wake_up_mule():
app.logger.debug('Mule: Posíláme signál')
uwsgi.signal(42)
have_uwsgi = True
mo.jobs.send_notify = wake_up_mule
except ImportError:
app.logger.warn('Nenalezeno UWSGI, takže automatické notifikace nepoběží.')
have_uwsgi = False
# Většina webu je v samostatných modulech
import mo.web.api
import mo.web.acct
import mo.web.doc
import mo.web.jinja
import mo.web.menu
import mo.web.misc
import mo.web.org
import mo.web.org_contest
import mo.web.org_jobs
import mo.web.org_log
import mo.web.org_place
import mo.web.org_round
import mo.web.org_score
import mo.web.org_users
import mo.web.user