diff --git a/.env b/.env index 98b1a80..597478a 100644 --- a/.env +++ b/.env @@ -1,3 +1,4 @@ POSTGRES_DB=league POSTGRES_USER=league POSTGRES_PASSWORD=league +CELERY_BROKER_URL=redis://cache:6379/0 diff --git a/app/autoapp.py b/app/autoapp.py index 048b485..2e3ad17 100755 --- a/app/autoapp.py +++ b/app/autoapp.py @@ -7,4 +7,8 @@ CONFIG = DevConfig if get_debug_flag() else ProdConfig -app = create_app(CONFIG) +flaskapp = create_app(CONFIG) + +# push app context so we can reference extensions +flaskapp.app_context().push() +celery = flaskapp.extensions['flask-celeryext'].celery diff --git a/app/league/admin/views.py b/app/league/admin/views.py index 7adbd96..1d06d91 100644 --- a/app/league/admin/views.py +++ b/app/league/admin/views.py @@ -84,6 +84,13 @@ def settings(): return render_template('admin/settings.html') +@blueprint.route('/aga-syncing') +@admin_required +def aga_syncing(): + """AGA syncing.""" + return render_template('admin/aga_syncing.html') + + @blueprint.route('/site_settings/', methods=['GET', 'POST']) @admin_required def manage_site_settings(): diff --git a/app/league/api/views.py b/app/league/api/views.py index 82836c5..70f57e1 100644 --- a/app/league/api/views.py +++ b/app/league/api/views.py @@ -5,6 +5,7 @@ from flask import Blueprint, jsonify, request, url_for from flask_login import login_required +from league import tasks from league.api.forms import GameCreateForm, GameUpdateForm from league.extensions import csrf_protect, messenger from league.models import Color, Game, Player @@ -20,7 +21,8 @@ def _set_game_create_choices(game_create_form): Should allow up to one more than current maxima. """ max_season, max_episode = Game.get_max_season_ep() - game_create_form.season.choices = [(s, s) for s in range(1, max_season + 2)] + game_create_form.season.choices = [(s, s) for s in + range(1, max_season + 2)] game_create_form.episode.choices = [(e, e) for e in range(1, max_episode + 2)] @@ -86,10 +88,12 @@ def _slack_game_msg(game): return result.format(w_name=game.white.full_name, w_url=url_for('dashboard.get_player', - player_id=game.white.id, _external=True), + player_id=game.white.id, + _external=True), b_name=game.black.full_name, b_url=url_for('dashboard.get_player', - player_id=game.black.id, _external=True), + player_id=game.black.id, + _external=True), handicap=game.handicap, komi=game.komi, date_string=game.played_at, @@ -137,3 +141,20 @@ def delete_game(game_id): return '', 204 else: return '', 404 + + +@blueprint.route('/hello-world') +def hello_world(): + """Run and wait for Hello world task.""" + result = tasks.hello_world.delay() + + return result.wait(), 200 + + +@blueprint.route('/queue-aga-sync', methods=['POST']) +@login_required +def queue_aga_sync(): + """Queue AGA sync job.""" + tasks.sync_aga_data.delay() + + return 'Task queued!', 200 diff --git a/app/league/app.py b/app/league/app.py index bfba108..dbb9e4a 100644 --- a/app/league/app.py +++ b/app/league/app.py @@ -7,15 +7,15 @@ from league import admin, api, commands, dashboard, public from league.assets import assets -from league.extensions import (bcrypt, cache, csrf_protect, db, debug_toolbar, - login_manager, messenger, migrate) +from league.extensions import (bcrypt, cache, celery, csrf_protect, db, + debug_toolbar, login_manager, messenger, migrate) from league.public.forms import LoginForm from league.settings import ProdConfig def create_app(config_object=ProdConfig): """ - An application factory. + Create application using app factory. See: http://flask.pocoo.org/docs/patterns/appfactories/. @@ -38,6 +38,7 @@ def register_extensions(app): bcrypt.init_app(app) cache.init_app(app) db.init_app(app) + celery.init_app(app) csrf_protect.init_app(app) login_manager.init_app(app) debug_toolbar.init_app(app) diff --git a/app/league/extensions.py b/app/league/extensions.py index dc02ddb..6bedbf8 100644 --- a/app/league/extensions.py +++ b/app/league/extensions.py @@ -6,6 +6,7 @@ """ from flask_bcrypt import Bcrypt from flask_caching import Cache +from flask_celeryext import FlaskCeleryExt from flask_debugtoolbar import DebugToolbarExtension from flask_login import LoginManager from flask_migrate import Migrate @@ -15,6 +16,7 @@ from league.slack_messenger import SlackMessenger bcrypt = Bcrypt() +celery = FlaskCeleryExt() csrf_protect = CsrfProtect() login_manager = LoginManager() db = SQLAlchemy() diff --git a/app/league/settings.py b/app/league/settings.py index 359d2c1..852f4bd 100644 --- a/app/league/settings.py +++ b/app/league/settings.py @@ -26,6 +26,10 @@ class Config(object): 'GitHub.' ) } + CELERY_BROKER_URL = os.environ.get('CELERY_BROKER_URL', + 'redis://cache:6379/0') + CELERY_RESULT_BACKEND = os.environ.get('CELERY_RESULT_BACKEND', + 'rpc://queue') class ProdConfig(Config): @@ -59,6 +63,10 @@ class DevConfig(Config): ASSETS_DEBUG = True # Don't bundle/minify static assets CACHE_TYPE = 'simple' # Can be "memcached", "redis", etc. LEAGUE_ROOT_PASS = 'root' + CELERY_BROKER_URL = os.environ.get('CELERY_BROKER_URL', + 'redis://localhost:6379/0') + CELERY_RESULT_BACKEND = os.environ.get('CELERY_RESULT_BACKEND', + 'redis://localhost:6379/0') class TestConfig(Config): diff --git a/app/league/tasks.py b/app/league/tasks.py new file mode 100644 index 0000000..424cde3 --- /dev/null +++ b/app/league/tasks.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +"""Celery tasks.""" +import math + +from bs4 import BeautifulSoup +from celery import current_app +from celery.utils.log import get_task_logger +from requests import get + +from league.models import Player + +logger = get_task_logger(__name__) + + +@current_app.task +def hello_world(): + """Hello world task.""" + return 'Hello world!' + + +@current_app.task +def sync_aga_data(): + """Update all local AGA data by scraping the AGA servers.""" + for player in Player.get_players(): + url = 'http://www.usgo.org/ratings-lookup-id?PlayerID={}'.format( + player.aga_id) + data = get(url) + soup = BeautifulSoup(data.text, 'html.parser') + columns = [col.text for col in soup.findAll('tr')[1].findAll('td')] + logger.debug('AGA Data: {}'.format(columns)) + + if not int(columns[0]) == player.aga_id: + continue + if columns[2] == '': + continue + + rating = float(columns[2]) + if rating > 0: + rank = math.floor(rating) + else: + rank = math.ceil(rating) + + player.update(aga_rank=rank) + + return diff --git a/app/league/templates/admin/aga_syncing.html b/app/league/templates/admin/aga_syncing.html new file mode 100644 index 0000000..d7e4f5b --- /dev/null +++ b/app/league/templates/admin/aga_syncing.html @@ -0,0 +1,46 @@ + +{% extends "layout.html" %} +{% block content %} +