From 35fa3f51c6a899bf4c247e9a7217eff3561a283a Mon Sep 17 00:00:00 2001 From: Sander Ferdinand Date: Thu, 12 Jul 2018 02:39:49 +0200 Subject: [PATCH] Fixed a caching bug, moved summary/stats to Redis and included code for a WOW->BTC->USD calculator --- wowfunding/bin/utils.py | 87 ++++++++++++++++++++- wowfunding/bin/utils_request.py | 40 ++-------- wowfunding/cache.py | 5 +- wowfunding/factory.py | 4 - wowfunding/orm/orm.py | 12 +++ wowfunding/routes.py | 4 +- wowfunding/templates/proposal/proposal.html | 21 ++++- 7 files changed, 128 insertions(+), 45 deletions(-) diff --git a/wowfunding/bin/utils.py b/wowfunding/bin/utils.py index 2931a43..62cbe5d 100644 --- a/wowfunding/bin/utils.py +++ b/wowfunding/bin/utils.py @@ -1,8 +1,93 @@ -from flask.json import JSONEncoder from datetime import datetime, date +import requests +from flask import g +from flask.json import JSONEncoder + +import settings + def json_encoder(obj): if isinstance(obj, (datetime, date)): return obj.isoformat() raise TypeError ("Type %s not serializable" % type(obj)) + + +class Summary: + @staticmethod + def fetch_prices(): + if hasattr(g, 'wowfunding_prices') and g.wow_prices: + return g.wow_prices + + from wowfunding.factory import cache + cache_key = 'wowfunding_prices' + data = cache.get(cache_key) + if data: + return data + data = { + 'wow-btc': price_tradeogre_wow_btc(), + 'btc-usd': price_cmc_btc_usd() + } + + cache.set(cache_key, data=data, expiry=7200) + g.wow_prices = data + return data + + @staticmethod + def fetch_stats(purge=False): + from wowfunding.factory import db_session + from wowfunding.orm.orm import Proposal, User, Comment + from wowfunding.factory import cache + cache_key = 'wowfunding_stats' + data = cache.get(cache_key) + if data and not purge: + return data + + categories = settings.FUNDING_CATEGORIES + statuses = settings.FUNDING_STATUSES.keys() + + for cat in categories: + q = db_session.query(Proposal) + q = q.filter(Proposal.category == cat) + res = q.count() + data.setdefault('cats', {}) + data['cats'][cat] = res + + for status in statuses: + q = db_session.query(Proposal) + q = q.filter(Proposal.status == status) + res = q.count() + data.setdefault('statuses', {}) + data['statuses'][status] = res + + data.setdefault('users', {}) + data['users']['count'] = db_session.query(User.id).count() + cache.set(cache_key, data=data, expiry=300) + return data + + +def price_cmc_btc_usd(): + headers = {'User-Agent': 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0'} + try: + r = requests.get('https://api.coinmarketcap.com/v2/ticker/1/?convert=USD', headers=headers) + r.raise_for_status() + return r.json().get('data', {}).get('quotes', {}).get('USD', {}).get('price') + except: + return + + +def price_tradeogre_wow_btc(): + headers = {'User-Agent': 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0'} + try: + r = requests.get('https://tradeogre.com/api/v1/ticker/BTC-WOW', headers=headers) + r.raise_for_status() + return float(r.json().get('high')) + except: + return + + +def wow_to_usd(wows: float, usd_per_btc: float, btc_per_wow: float): + try: + return round(usd_per_btc / (1.0 / (wows * btc_per_wow)), 2) + except: + pass \ No newline at end of file diff --git a/wowfunding/bin/utils_request.py b/wowfunding/bin/utils_request.py index 529a271..d63501a 100644 --- a/wowfunding/bin/utils_request.py +++ b/wowfunding/bin/utils_request.py @@ -2,59 +2,33 @@ from datetime import datetime from flask import session, g import settings -from wowfunding.factory import app, db_session, summary_data +from wowfunding.bin.utils import Summary +from wowfunding.factory import app, db_session from wowfunding.orm.orm import Proposal, User, Comment @app.context_processor def templating(): - global summary_data from flask.ext.login import current_user recent_comments = db_session.query(Comment).order_by(Comment.date_added.desc()).limit(3).all() + summary_data = Summary.fetch_stats() return dict(logged_in=current_user.is_authenticated, current_user=current_user, funding_categories=settings.FUNDING_CATEGORIES, funding_statuses=settings.FUNDING_STATUSES, - summary_data=summary_data[1], + summary_data=summary_data, recent_comments=recent_comments) -def fetch_summary(purge=False): - global summary_data - if summary_data and not purge: - if (datetime.now() - summary_data[0]).total_seconds() <= 120: - return - - data = {} - categories = settings.FUNDING_CATEGORIES - statuses = settings.FUNDING_STATUSES.keys() - - for cat in categories: - q = db_session.query(Proposal) - q = q.filter(Proposal.category == cat) - res = q.count() - data.setdefault('cats', {}) - data['cats'][cat] = res - - for status in statuses: - q = db_session.query(Proposal) - q = q.filter(Proposal.status == status) - res = q.count() - data.setdefault('statuses', {}) - data['statuses'][status] = res - - data.setdefault('users', {}) - data['users']['count'] = db_session.query(User.id).count() - summary_data = [datetime.now(), data] - - @app.before_request def before_request(): - fetch_summary() + pass @app.after_request def after_request(res): + if hasattr(g, 'wowfunding_prices'): + delattr(g, 'wowfunding_prices') res.headers.add('Accept-Ranges', 'bytes') if settings.DEBUG: res.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' diff --git a/wowfunding/cache.py b/wowfunding/cache.py index 52f4309..5ee62f1 100644 --- a/wowfunding/cache.py +++ b/wowfunding/cache.py @@ -13,7 +13,8 @@ def redis_args(): "port": settings.REDIS_PORT, 'socket_connect_timeout': 2, 'socket_timeout': 2, - 'retry_on_timeout': True + 'retry_on_timeout': True, + 'decode_responses': True } if settings.REDIS_PASSWD: args["password"] = settings.REDIS_PASSWD @@ -41,7 +42,7 @@ class JsonRedis(RedisSessionInterface): def __init__(self, key_prefix, use_signer=False, decode_responses=True): super(JsonRedis, self).__init__( - redis=redis.Redis(decode_responses=decode_responses, **redis_args()), + redis=redis.Redis(**redis_args()), key_prefix=key_prefix, use_signer=use_signer) diff --git a/wowfunding/factory.py b/wowfunding/factory.py index 586cc77..54d17b0 100644 --- a/wowfunding/factory.py +++ b/wowfunding/factory.py @@ -8,7 +8,6 @@ sentry = None cache = None db_session = None bcrypt = None -summary_data = [] def create_app(): @@ -53,8 +52,5 @@ def create_app(): from wowfunding import api from wowfunding.bin import utils_request - # generate some statistics - utils_request.fetch_summary() - app.app_context().push() return app diff --git a/wowfunding/orm/orm.py b/wowfunding/orm/orm.py index ed8e8df..055e663 100644 --- a/wowfunding/orm/orm.py +++ b/wowfunding/orm/orm.py @@ -146,6 +146,14 @@ class Proposal(base): return result + @property + def funds_target_usd(self): + from wowfunding.bin.utils import Summary, wow_to_usd + prices = Summary.fetch_prices() + if not prices: + return + return wow_to_usd(wows=self.funds_target, btc_per_wow=prices['wow-btc'], usd_per_btc=prices['btc-usd']) + @property def comment_count(self): from wowfunding.factory import db_session @@ -176,6 +184,7 @@ class Proposal(base): of this proposal. It uses Redis cache to not spam the wownerod too much. Returns a nice dictionary containing all relevant proposal funding info""" + from wowfunding.bin.utils import Summary, wow_to_usd from wowfunding.factory import cache, db_session rtn = {'sum': 0.0, 'txs': [], 'pct': 0.0} @@ -193,7 +202,10 @@ class Proposal(base): print('error; get_transfers; %d' % self.id) return rtn + prices = Summary.fetch_prices() for tx in data['txs']: + if prices: + tx['amount_usd'] = wow_to_usd(wows=tx['amount_human'], btc_per_wow=prices['wow-btc'], usd_per_btc=prices['btc-usd']) tx['datetime'] = datetime.fromtimestamp(tx['timestamp']) if data.get('sum', 0.0): diff --git a/wowfunding/routes.py b/wowfunding/routes.py index 1e692fe..304883a 100644 --- a/wowfunding/routes.py +++ b/wowfunding/routes.py @@ -160,8 +160,8 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category db_session.flush() # reset cached statistics - from wowfunding.bin import utils_request - utils_request.fetch_summary(purge=True) + from wowfunding.bin.utils import Summary + Summary.fetch_stats(purge=True) return make_response(jsonify({'url': url_for('proposal', pid=p.id)})) diff --git a/wowfunding/templates/proposal/proposal.html b/wowfunding/templates/proposal/proposal.html index 73508c1..c3e3d09 100644 --- a/wowfunding/templates/proposal/proposal.html +++ b/wowfunding/templates/proposal/proposal.html @@ -1,7 +1,6 @@ {% extends "base.html" %} {% block content %} -
{% include 'messages.html' %} @@ -35,6 +34,9 @@
Target: {{proposal.funds_target|round}} WOW + {% if proposal.funds_target_usd %} + ➞ {{proposal.funds_target_usd}} USD + {% endif %} {% endif %}

@@ -60,7 +62,13 @@ Target - {{proposal.funds_target|round}} WOW + + {{proposal.funds_target|round}} WOW + {% if proposal.funds_target_usd %} + ➞ {{proposal.funds_target_usd}} USD + {% endif %} + + Progress @@ -143,7 +151,14 @@ Blockheight: {{tx['height']}}
{{tx['txid'][:32]}}... - + {{tx['amount_human']|round(2)}} WOW + + + {{tx['amount_human']|round(2)}} WOW + {% if 'amount_usd' in tx %} + + ➞ $ {{tx['amount_usd']}} + + {% endif %} + {% endfor %}