- Switch to integrated addresses (address+payment_id) -> `make_integrated_address @ RPC)
- Changed homegrown caching solution to use flask extension `flask_cache` instead - Remove detection of out-going (sent) funds, needs to be manually registered in table "payouts" now - Updated Flask version
This commit is contained in:
parent
c0b972edde
commit
401c26e85b
|
@ -5,7 +5,7 @@ from flask_yoloapi import endpoint, parameter
|
|||
import settings
|
||||
from funding.bin.utils import get_ip
|
||||
from funding.bin.qr import QrCodeGenerator
|
||||
from funding.factory import app, db_session
|
||||
from funding.factory import app, db
|
||||
from funding.orm.orm import Proposal, User
|
||||
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ class QrCodeGenerator:
|
|||
:param color_to: gradient to color
|
||||
:return:
|
||||
"""
|
||||
if len(address) != settings.COIN_ADDRESS_LENGTH:
|
||||
raise Exception('faulty address length')
|
||||
if len(address) not in settings.COIN_ADDRESS_LENGTH:
|
||||
raise Exception(f'faulty address length, should be: {" or ".join(map(str, settings.COIN_ADDRESS_LENGTH))}')
|
||||
|
||||
if not dest:
|
||||
dest = os.path.join(self.base, '%s.png' % address)
|
||||
|
|
|
@ -8,6 +8,7 @@ from flask import g, request
|
|||
from flask.json import JSONEncoder
|
||||
|
||||
import settings
|
||||
from funding.factory import cache
|
||||
|
||||
|
||||
def json_encoder(obj):
|
||||
|
@ -18,62 +19,50 @@ def json_encoder(obj):
|
|||
|
||||
class Summary:
|
||||
@staticmethod
|
||||
@cache.cached(timeout=300, key_prefix="fetch_prices")
|
||||
def fetch_prices():
|
||||
if hasattr(g, 'funding_prices') and g.coin_prices:
|
||||
return g.coin_prices
|
||||
from funding.factory import cache
|
||||
cache_key = 'funding_prices'
|
||||
data = cache.get(cache_key)
|
||||
if data:
|
||||
return data
|
||||
data = {
|
||||
return {
|
||||
'coin-btc': coin_btc_value(),
|
||||
'btc-usd': price_cmc_btc_usd()
|
||||
}
|
||||
cache.set(cache_key, data=data, expiry=1200)
|
||||
g.coin_prices = data
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def fetch_stats(purge=False):
|
||||
from funding.factory import db_session
|
||||
@cache.cached(timeout=300, key_prefix="funding_stats")
|
||||
def fetch_stats():
|
||||
from funding.factory import db
|
||||
from funding.orm.orm import Proposal, User, Comment
|
||||
from funding.factory import cache
|
||||
cache_key = 'funding_stats'
|
||||
data = cache.get(cache_key)
|
||||
if data and not purge:
|
||||
return data
|
||||
|
||||
data = {}
|
||||
categories = settings.FUNDING_CATEGORIES
|
||||
statuses = settings.FUNDING_STATUSES.keys()
|
||||
|
||||
for cat in categories:
|
||||
q = db_session.query(Proposal)
|
||||
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 = 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)
|
||||
data['users']['count'] = db.session.query(User.id).count()
|
||||
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:
|
||||
print('request coinmarketcap')
|
||||
r = requests.get('https://api.coinmarketcap.com/v2/ticker/1/?convert=USD', headers=headers)
|
||||
r = requests.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd', headers=headers)
|
||||
r.raise_for_status()
|
||||
return r.json().get('data', {}).get('quotes', {}).get('USD', {}).get('price')
|
||||
data = r.json()
|
||||
btc = next(c for c in data if c['symbol'] == 'btc')
|
||||
return btc['current_price']
|
||||
except:
|
||||
return
|
||||
|
||||
|
@ -81,7 +70,6 @@ def price_cmc_btc_usd():
|
|||
def coin_btc_value():
|
||||
headers = {'User-Agent': 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0'}
|
||||
try:
|
||||
print('request TO')
|
||||
r = requests.get('https://tradeogre.com/api/v1/ticker/BTC-WOW', headers=headers)
|
||||
r.raise_for_status()
|
||||
return float(r.json().get('high'))
|
||||
|
|
|
@ -2,16 +2,16 @@ from datetime import datetime
|
|||
from flask import session, g, request
|
||||
import settings
|
||||
from funding.bin.utils import Summary
|
||||
from funding.factory import app, db_session
|
||||
from funding.factory import app, db
|
||||
from funding.orm.orm import Proposal, User, Comment
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def templating():
|
||||
from flask.ext.login import current_user
|
||||
recent_comments = db_session.query(Comment).filter(Comment.automated == False).order_by(Comment.date_added.desc()).limit(8).all()
|
||||
from flask_login import current_user
|
||||
recent_comments = db.session.query(Comment).filter(Comment.automated == False).order_by(Comment.date_added.desc()).limit(8).all()
|
||||
summary_data = Summary.fetch_stats()
|
||||
newest_users = db_session.query(User).filter(User.admin == False).order_by(User.registered_on.desc()).limit(5).all()
|
||||
newest_users = db.session.query(User).filter(User.admin == False).order_by(User.registered_on.desc()).limit(5).all()
|
||||
return dict(logged_in=current_user.is_authenticated,
|
||||
current_user=current_user,
|
||||
funding_categories=settings.FUNDING_CATEGORIES,
|
||||
|
@ -28,8 +28,6 @@ def before_request():
|
|||
|
||||
@app.after_request
|
||||
def after_request(res):
|
||||
if hasattr(g, 'funding_prices'):
|
||||
delattr(g, 'funding_prices')
|
||||
res.headers.add('Accept-Ranges', 'bytes')
|
||||
|
||||
if request.full_path.startswith('/api/'):
|
||||
|
@ -45,7 +43,7 @@ def after_request(res):
|
|||
|
||||
@app.teardown_appcontext
|
||||
def shutdown_session(**kwargs):
|
||||
db_session.remove()
|
||||
db.session.remove()
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
|
|
|
@ -45,17 +45,3 @@ class JsonRedis(RedisSessionInterface):
|
|||
redis=redis.Redis(**redis_args()),
|
||||
key_prefix=key_prefix,
|
||||
use_signer=use_signer)
|
||||
|
||||
|
||||
class WowCache:
|
||||
def __init__(self):
|
||||
self._cache = redis.StrictRedis(**redis_args())
|
||||
|
||||
def get(self, key):
|
||||
try:
|
||||
return json.loads(self._cache.get(key))
|
||||
except:
|
||||
return {}
|
||||
|
||||
def set(self, key: str, data: dict, expiry=300):
|
||||
self._cache.set(key, json.dumps(data, default=json_encoder), ex=expiry)
|
||||
|
|
|
@ -1,40 +1,79 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import settings
|
||||
from werkzeug.contrib.fixers import ProxyFix
|
||||
from flask import Flask
|
||||
from flask_caching import Cache
|
||||
from flask_session import Session
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
app = None
|
||||
sentry = None
|
||||
cache = None
|
||||
db_session = None
|
||||
db = None
|
||||
bcrypt = None
|
||||
|
||||
|
||||
def _setup_cache(app: Flask):
|
||||
global cache
|
||||
|
||||
cache_config = {
|
||||
"CACHE_TYPE": "redis",
|
||||
"CACHE_DEFAULT_TIMEOUT": 60,
|
||||
"CACHE_KEY_PREFIX": "wow_cache_"
|
||||
}
|
||||
|
||||
app.config.from_mapping(cache_config)
|
||||
cache = Cache(app)
|
||||
|
||||
|
||||
def _setup_session(app: Flask):
|
||||
app.config['SESSION_TYPE'] = 'redis'
|
||||
app.config['SESSION_COOKIE_NAME'] = 'bar'
|
||||
Session(app) # defaults to timedelta(days=31)
|
||||
|
||||
|
||||
def _setup_db(app: Flask):
|
||||
global db
|
||||
|
||||
DB_URL = 'postgresql+psycopg2://{user}:{pw}@{url}/{db}'.format(
|
||||
user=settings.PSQL_USER,
|
||||
pw=settings.PSQL_PASS,
|
||||
url=settings.PSQL_HOST,
|
||||
db=settings.PSQL_DB)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
import funding.orm
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def create_app():
|
||||
global app
|
||||
global db_session
|
||||
global sentry
|
||||
global db
|
||||
global cache
|
||||
global bcrypt
|
||||
|
||||
from funding.orm.connect import create_session
|
||||
db_session = create_session()
|
||||
|
||||
app = Flask(__name__)
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app)
|
||||
app = Flask(import_name=__name__,
|
||||
static_folder='static',
|
||||
template_folder='templates')
|
||||
app.config.from_object(settings)
|
||||
app.config['PERMANENT_SESSION_LIFETIME'] = 2678400
|
||||
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False
|
||||
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 30
|
||||
app.secret_key = settings.SECRET
|
||||
|
||||
_setup_cache(app)
|
||||
_setup_session(app)
|
||||
_setup_db(app)
|
||||
|
||||
# flask-login
|
||||
from flask.ext.login import LoginManager
|
||||
from flask_login import LoginManager
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = 'login'
|
||||
|
||||
from flask.ext.bcrypt import Bcrypt
|
||||
from flask_bcrypt import Bcrypt
|
||||
bcrypt = Bcrypt(app)
|
||||
|
||||
@login_manager.user_loader
|
||||
|
@ -42,15 +81,10 @@ def create_app():
|
|||
from funding.orm.orm import User
|
||||
return User.query.get(int(_id))
|
||||
|
||||
# session init
|
||||
from funding.cache import JsonRedis, WowCache
|
||||
app.session_interface = JsonRedis(key_prefix=app.config['SESSION_PREFIX'], use_signer=False)
|
||||
cache = WowCache()
|
||||
|
||||
# import routes
|
||||
from funding import routes
|
||||
from funding import api
|
||||
from funding.bin import utils_request
|
||||
|
||||
app.app_context().push()
|
||||
return app
|
||||
return app
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
from datetime import datetime
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker, relationship
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
import settings
|
||||
|
||||
|
||||
def create_session():
|
||||
from funding.orm.orm import base
|
||||
engine = sa.create_engine(settings.SQLALCHEMY_DATABASE_URI, echo=False, encoding="latin")
|
||||
session = scoped_session(sessionmaker(autocommit=False,
|
||||
autoflush=False,
|
||||
bind=engine))
|
||||
base.query = session.query_property()
|
||||
base.metadata.create_all(bind=engine)
|
||||
return session
|
|
@ -1,19 +1,29 @@
|
|||
from datetime import datetime
|
||||
import string
|
||||
import random
|
||||
|
||||
import requests
|
||||
from sqlalchemy.orm import relationship, backref
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker, relationship
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.types import Float
|
||||
from sqlalchemy_json import MutableJson
|
||||
|
||||
import settings
|
||||
from funding.factory import db
|
||||
|
||||
base = declarative_base(name="Model")
|
||||
|
||||
class User(base):
|
||||
|
||||
class User(db.Model):
|
||||
__tablename__ = "users"
|
||||
id = sa.Column('user_id', sa.Integer, primary_key=True)
|
||||
username = sa.Column(sa.String(20), unique=True, index=True)
|
||||
password = sa.Column(sa.String(60))
|
||||
email = sa.Column(sa.String(50), unique=True, index=True)
|
||||
registered_on = sa.Column(sa.DateTime)
|
||||
admin = sa.Column(sa.Boolean, default=False)
|
||||
id = db.Column('user_id', db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(20), unique=True, index=True)
|
||||
password = db.Column(db.String(60))
|
||||
email = db.Column(db.String(50), unique=True, index=True)
|
||||
registered_on = db.Column(db.DateTime)
|
||||
admin = db.Column(db.Boolean, default=False)
|
||||
proposals = relationship('Proposal', back_populates="user")
|
||||
comments = relationship("Comment", back_populates="user")
|
||||
|
||||
|
@ -48,7 +58,7 @@ class User(base):
|
|||
|
||||
@classmethod
|
||||
def add(cls, username, password, email):
|
||||
from funding.factory import db_session
|
||||
from funding.factory import db
|
||||
from funding.validation import val_username, val_email
|
||||
|
||||
try:
|
||||
|
@ -57,37 +67,39 @@ class User(base):
|
|||
val_email(email)
|
||||
|
||||
user = User(username, password, email)
|
||||
db_session.add(user)
|
||||
db_session.commit()
|
||||
db_session.flush()
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
db.session.flush()
|
||||
return user
|
||||
except Exception as ex:
|
||||
db_session.rollback()
|
||||
db.session.rollback()
|
||||
raise
|
||||
|
||||
|
||||
class Proposal(base):
|
||||
class Proposal(db.Model):
|
||||
__tablename__ = "proposals"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
headline = sa.Column(sa.VARCHAR, nullable=False)
|
||||
content = sa.Column(sa.VARCHAR, nullable=False)
|
||||
category = sa.Column(sa.VARCHAR, nullable=False)
|
||||
date_added = sa.Column(sa.TIMESTAMP, default=datetime.now)
|
||||
html = sa.Column(sa.VARCHAR)
|
||||
last_edited = sa.Column(sa.TIMESTAMP)
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
archived = db.Column(db.Boolean, default=False)
|
||||
headline = db.Column(db.VARCHAR, nullable=False)
|
||||
content = db.Column(db.VARCHAR, nullable=False)
|
||||
category = db.Column(db.VARCHAR, nullable=False)
|
||||
date_added = db.Column(db.TIMESTAMP, default=datetime.now)
|
||||
html = db.Column(db.VARCHAR)
|
||||
last_edited = db.Column(db.TIMESTAMP)
|
||||
|
||||
# the FFS target
|
||||
funds_target = sa.Column(sa.Float, nullable=False)
|
||||
funds_target = db.Column(db.Float, nullable=False)
|
||||
|
||||
# the FFS progress (cached)
|
||||
funds_progress = sa.Column(sa.Float, nullable=False, default=0)
|
||||
funds_progress = db.Column(db.Float, nullable=False, default=0)
|
||||
|
||||
# the FFS withdrawal amount (paid to the author)
|
||||
funds_withdrew = sa.Column(sa.Float, nullable=False, default=0)
|
||||
funds_withdrew = db.Column(db.Float, nullable=False, default=0)
|
||||
|
||||
# the FFS receiving and withdrawal addresses
|
||||
addr_donation = sa.Column(sa.VARCHAR)
|
||||
addr_receiving = sa.Column(sa.VARCHAR)
|
||||
addr_donation = db.Column(db.VARCHAR)
|
||||
addr_receiving = db.Column(db.VARCHAR)
|
||||
payment_id = db.Column(db.VARCHAR)
|
||||
|
||||
# proposal status:
|
||||
# 0: disabled
|
||||
|
@ -95,9 +107,9 @@ class Proposal(base):
|
|||
# 2: funding required
|
||||
# 3: wip
|
||||
# 4: completed
|
||||
status = sa.Column(sa.INTEGER, default=1)
|
||||
status = db.Column(db.INTEGER, default=1)
|
||||
|
||||
user_id = sa.Column(sa.Integer, sa.ForeignKey('users.user_id'))
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'))
|
||||
user = relationship("User", back_populates="proposals")
|
||||
|
||||
payouts = relationship("Payout", back_populates="proposal")
|
||||
|
@ -131,17 +143,12 @@ class Proposal(base):
|
|||
|
||||
@classmethod
|
||||
def find_by_id(cls, pid: int):
|
||||
from funding.factory import db_session
|
||||
from funding.factory import db
|
||||
q = cls.query
|
||||
q = q.filter(Proposal.id == pid)
|
||||
result = q.first()
|
||||
if not result:
|
||||
return
|
||||
|
||||
# check if we have a valid addr_donation generated. if not, make one.
|
||||
if not result.addr_donation and result.status == 2:
|
||||
from funding.bin.daemon import Daemon
|
||||
Proposal.generate_donation_addr(result)
|
||||
return result
|
||||
|
||||
@property
|
||||
|
@ -154,21 +161,21 @@ class Proposal(base):
|
|||
|
||||
@property
|
||||
def comment_count(self):
|
||||
from funding.factory import db_session
|
||||
q = db_session.query(sa.func.count(Comment.id))
|
||||
from funding.factory import db
|
||||
q = db.session.query(db.func.count(Comment.id))
|
||||
q = q.filter(Comment.proposal_id == self.id)
|
||||
return q.scalar()
|
||||
|
||||
def get_comments(self):
|
||||
from funding.factory import db_session
|
||||
q = db_session.query(Comment)
|
||||
from funding.factory import db
|
||||
q = db.session.query(Comment)
|
||||
q = q.filter(Comment.proposal_id == self.id)
|
||||
q = q.filter(Comment.replied_to == None)
|
||||
q = q.filter(Comment.replied_to.is_(None))
|
||||
q = q.order_by(Comment.date_added.desc())
|
||||
comments = q.all()
|
||||
|
||||
for c in comments:
|
||||
q = db_session.query(Comment)
|
||||
q = db.session.query(Comment)
|
||||
q = q.filter(Comment.proposal_id == self.id)
|
||||
q = q.filter(Comment.replied_to == c.id)
|
||||
_c = q.all()
|
||||
|
@ -177,6 +184,12 @@ class Proposal(base):
|
|||
setattr(self, '_comments', comments)
|
||||
return self
|
||||
|
||||
@property
|
||||
def spends(self):
|
||||
amount = sum([p.amount for p in self.payouts])
|
||||
pct = amount / 100 * self.balance['sum']
|
||||
return {"amount": amount, "pct": pct}
|
||||
|
||||
@property
|
||||
def balance(self):
|
||||
"""This property retrieves the current funding status
|
||||
|
@ -184,28 +197,49 @@ class Proposal(base):
|
|||
daemon too much. Returns a nice dictionary containing
|
||||
all relevant proposal funding info"""
|
||||
from funding.bin.utils import Summary, coin_to_usd
|
||||
from funding.factory import cache, db_session
|
||||
rtn = {'sum': 0.0, 'txs': [], 'pct': 0.0}
|
||||
from funding.factory import cache, db
|
||||
rtn = {'sum': 0.0, 'txs': [], 'pct': 0.0, 'available': 0}
|
||||
|
||||
cache_key = 'coin_balance_pid_%d' % self.id
|
||||
data = cache.get(cache_key)
|
||||
if not data:
|
||||
from funding.bin.daemon import Daemon
|
||||
try:
|
||||
data = Daemon().get_transfers_in(proposal=self)
|
||||
if not isinstance(data, dict):
|
||||
print('error; get_transfers_in; %d' % self.id)
|
||||
return rtn
|
||||
cache.set(cache_key, data=data, expiry=60)
|
||||
except Exception as ex:
|
||||
print('error; get_transfers_in; %d' % self.id)
|
||||
return rtn
|
||||
if self.archived:
|
||||
return rtn
|
||||
|
||||
try:
|
||||
r = requests.get(f'http://{settings.RPC_HOST}:{settings.RPC_PORT}/json_rpc', json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "0",
|
||||
"method": "get_payments",
|
||||
"params": {
|
||||
"payment_id": self.payment_id
|
||||
}
|
||||
})
|
||||
r.raise_for_status()
|
||||
blob = r.json()
|
||||
|
||||
assert 'result' in blob
|
||||
assert 'payments' in blob['result']
|
||||
assert isinstance(blob['result']['payments'], list)
|
||||
except Exception as ex:
|
||||
return rtn
|
||||
|
||||
txs = blob['result']['payments']
|
||||
for tx in txs:
|
||||
tx['amount_human'] = float(tx['amount'])/1e11
|
||||
tx['txid'] = tx['tx_hash']
|
||||
tx['type'] = 'in'
|
||||
|
||||
data = {
|
||||
'sum': sum([float(z['amount']) / 1e11 for z in txs]),
|
||||
'txs': txs
|
||||
}
|
||||
|
||||
if not isinstance(data, dict):
|
||||
print('error; get_transfers_in; %d' % self.id)
|
||||
return rtn
|
||||
|
||||
prices = Summary.fetch_prices()
|
||||
for tx in data['txs']:
|
||||
if prices:
|
||||
tx['amount_usd'] = coin_to_usd(amt=tx['amount_human'], btc_per_coin=prices['coin-btc'], usd_per_btc=prices['btc-usd'])
|
||||
tx['datetime'] = datetime.fromtimestamp(tx['timestamp'])
|
||||
|
||||
if data.get('sum', 0.0):
|
||||
data['pct'] = 100 / float(self.funds_target / data.get('sum', 0.0))
|
||||
|
@ -216,8 +250,8 @@ class Proposal(base):
|
|||
|
||||
if data['pct'] != self.funds_progress:
|
||||
self.funds_progress = data['pct']
|
||||
db_session.commit()
|
||||
db_session.flush()
|
||||
db.session.commit()
|
||||
db.session.flush()
|
||||
|
||||
if data['available']:
|
||||
data['remaining_pct'] = 100 / float(data['sum'] / data['available'])
|
||||
|
@ -226,74 +260,9 @@ class Proposal(base):
|
|||
|
||||
return data
|
||||
|
||||
@property
|
||||
def spends(self):
|
||||
from funding.bin.utils import Summary, coin_to_usd
|
||||
from funding.factory import cache, db_session
|
||||
rtn = {'sum': 0.0, 'txs': [], 'pct': 0.0}
|
||||
|
||||
cache_key = 'coin_spends_pid_%d' % self.id
|
||||
data = cache.get(cache_key)
|
||||
if not data:
|
||||
from funding.bin.daemon import Daemon
|
||||
try:
|
||||
data = Daemon().get_transfers_out(proposal=self)
|
||||
if not isinstance(data, dict):
|
||||
print('error; get_transfers_out; %d' % self.id)
|
||||
return rtn
|
||||
cache.set(cache_key, data=data, expiry=60)
|
||||
except:
|
||||
print('error; get_transfers_out; %d' % self.id)
|
||||
return rtn
|
||||
|
||||
data['remaining_pct'] = 0.0
|
||||
prices = Summary.fetch_prices()
|
||||
|
||||
for tx in data['txs']:
|
||||
if prices:
|
||||
tx['amount_usd'] = coin_to_usd(amt=tx['amount_human'], btc_per_coin=prices['coin-btc'], usd_per_btc=prices['btc-usd'])
|
||||
tx['datetime'] = datetime.fromtimestamp(tx['timestamp'])
|
||||
|
||||
if data.get('sum', 0.0):
|
||||
data['pct'] = 100 / float(self.funds_target / data.get('sum', 0.0))
|
||||
data['spent'] = data['sum']
|
||||
else:
|
||||
data['pct'] = 0.0
|
||||
data['spent'] = 0.0
|
||||
|
||||
cache_key_in = 'coin_balance_pid_%d' % self.id
|
||||
data_in = cache.get(cache_key_in)
|
||||
if data_in and data['spent']:
|
||||
data['remaining_pct'] = 100 / float(data_in['sum'] / data['spent'])
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def generate_donation_addr(cls):
|
||||
from funding.factory import db_session
|
||||
from funding.bin.daemon import Daemon
|
||||
if cls.addr_donation:
|
||||
return cls.addr_donation
|
||||
|
||||
# check if the current user has an account in the wallet
|
||||
account = Daemon().get_accounts(cls.id)
|
||||
if not account:
|
||||
account = Daemon().create_account(cls.id)
|
||||
index = account['account_index']
|
||||
|
||||
address = account.get('address') or account.get('base_address')
|
||||
if not address:
|
||||
raise Exception('Cannot generate account/address for pid %d' % cls.id)
|
||||
|
||||
# assign donation address, commit to db
|
||||
cls.addr_donation = address
|
||||
db_session.commit()
|
||||
db_session.flush()
|
||||
return address
|
||||
|
||||
@classmethod
|
||||
def find_by_args(cls, status: int = None, cat: str = None, limit: int = 20, offset=0):
|
||||
from funding.factory import db_session
|
||||
from funding.factory import db
|
||||
if isinstance(status, int) and status not in settings.FUNDING_STATUSES.keys():
|
||||
raise NotImplementedError('invalid status')
|
||||
if isinstance(cat, str) and cat not in settings.FUNDING_CATEGORIES:
|
||||
|
@ -313,71 +282,82 @@ class Proposal(base):
|
|||
|
||||
@classmethod
|
||||
def search(cls, key: str):
|
||||
key_ilike = '%' + key.replace('%', '') + '%'
|
||||
key_ilike = f"%{key.replace('%', '')}%"
|
||||
q = Proposal.query
|
||||
q = q.filter(sa.or_(
|
||||
q = q.filter(db.or_(
|
||||
Proposal.headline.ilike(key_ilike),
|
||||
Proposal.content.ilike(key_ilike)))
|
||||
return q.all()
|
||||
|
||||
|
||||
class Payout(base):
|
||||
class Payout(db.Model):
|
||||
__tablename__ = "payouts"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
proposal_id = sa.Column(sa.Integer, sa.ForeignKey('proposals.id'))
|
||||
proposal_id = db.Column(db.Integer, db.ForeignKey('proposals.id'))
|
||||
proposal = relationship("Proposal", back_populates="payouts")
|
||||
|
||||
amount = sa.Column(sa.Integer, nullable=False)
|
||||
to_address = sa.Column(sa.VARCHAR, nullable=False)
|
||||
amount = db.Column(db.Integer, nullable=False)
|
||||
to_address = db.Column(db.VARCHAR, nullable=False)
|
||||
|
||||
ix_proposal_id = sa.Index("ix_proposal_id", proposal_id)
|
||||
date_sent = db.Column(db.TIMESTAMP, default=datetime.now)
|
||||
|
||||
ix_proposal_id = db.Index("ix_proposal_id", proposal_id)
|
||||
|
||||
@classmethod
|
||||
def add(cls, proposal_id, amount, to_address):
|
||||
# @TODO: validate that we can make this payout; check previous payouts
|
||||
from flask.ext.login import current_user
|
||||
from flask_login import current_user
|
||||
if not current_user.admin:
|
||||
raise Exception("user must be admin to add a payout")
|
||||
from funding.factory import db_session
|
||||
from funding.factory import db
|
||||
|
||||
try:
|
||||
payout = Payout(propsal_id=proposal_id, amount=amount, to_address=to_address)
|
||||
db_session.add(payout)
|
||||
db_session.commit()
|
||||
db_session.flush()
|
||||
db.session.add(payout)
|
||||
db.session.commit()
|
||||
db.session.flush()
|
||||
return payout
|
||||
except Exception as ex:
|
||||
db_session.rollback()
|
||||
db.session.rollback()
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def get_payouts(proposal_id):
|
||||
from funding.factory import db_session
|
||||
return db_session.query(Payout).filter(Payout.proposal_id == proposal_id).all()
|
||||
from funding.factory import db
|
||||
return db.session.query(Payout).filter(Payout.proposal_id == proposal_id).all()
|
||||
|
||||
@property
|
||||
def as_tx(self):
|
||||
return {
|
||||
"block_height": "-",
|
||||
"type": "out",
|
||||
"amount_human": self.amount,
|
||||
"amount": self.amount
|
||||
}
|
||||
|
||||
|
||||
class Comment(base):
|
||||
class Comment(db.Model):
|
||||
__tablename__ = "comments"
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
proposal_id = sa.Column(sa.Integer, sa.ForeignKey('proposals.id'))
|
||||
proposal_id = db.Column(db.Integer, db.ForeignKey('proposals.id'))
|
||||
proposal = relationship("Proposal", back_populates="comments")
|
||||
|
||||
user_id = sa.Column(sa.Integer, sa.ForeignKey('users.user_id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'), nullable=False)
|
||||
user = relationship("User", back_populates="comments")
|
||||
|
||||
date_added = sa.Column(sa.TIMESTAMP, default=datetime.now)
|
||||
date_added = db.Column(db.TIMESTAMP, default=datetime.now)
|
||||
|
||||
message = sa.Column(sa.VARCHAR, nullable=False)
|
||||
replied_to = sa.Column(sa.ForeignKey("comments.id"))
|
||||
message = db.Column(db.VARCHAR, nullable=False)
|
||||
replied_to = db.Column(db.ForeignKey("comments.id"))
|
||||
|
||||
locked = sa.Column(sa.Boolean, default=False)
|
||||
locked = db.Column(db.Boolean, default=False)
|
||||
|
||||
automated = sa.Column(sa.Boolean, default=False)
|
||||
automated = db.Column(db.Boolean, default=False)
|
||||
|
||||
ix_comment_replied_to = sa.Index("ix_comment_replied_to", replied_to)
|
||||
ix_comment_proposal_id = sa.Index("ix_comment_proposal_id", proposal_id)
|
||||
ix_comment_replied_to = db.Index("ix_comment_replied_to", replied_to)
|
||||
ix_comment_proposal_id = db.Index("ix_comment_proposal_id", proposal_id)
|
||||
|
||||
@property
|
||||
def message_html(self):
|
||||
|
@ -390,28 +370,30 @@ class Comment(base):
|
|||
|
||||
@staticmethod
|
||||
def find_by_id(cid: int):
|
||||
from funding.factory import db_session
|
||||
return db_session.query(Comment).filter(Comment.id == cid).first()
|
||||
from funding.factory import db
|
||||
return db.session.query(Comment).filter(Comment.id == cid).first()
|
||||
|
||||
@staticmethod
|
||||
def remove(cid: int):
|
||||
from funding.factory import db_session
|
||||
from flask.ext.login import current_user
|
||||
if current_user.id != user_id and not current_user.admin:
|
||||
raise Exception("no rights to remove this comment")
|
||||
from funding.factory import db
|
||||
from flask_login import current_user
|
||||
comment = Comment.get(cid=cid)
|
||||
|
||||
if current_user.id != comment.user_id and not current_user.admin:
|
||||
raise Exception("no rights to remove this comment")
|
||||
|
||||
try:
|
||||
comment.delete()
|
||||
db_session.commit()
|
||||
db_session.flush()
|
||||
db.session.commit()
|
||||
db.session.flush()
|
||||
except:
|
||||
db_session.rollback()
|
||||
db.session.rollback()
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def lock(cid: int):
|
||||
from funding.factory import db_session
|
||||
from flask.ext.login import current_user
|
||||
from funding.factory import db
|
||||
from flask_login import current_user
|
||||
if not current_user.admin:
|
||||
raise Exception("admin required")
|
||||
comment = Comment.find_by_id(cid=cid)
|
||||
|
@ -419,17 +401,17 @@ class Comment(base):
|
|||
raise Exception("comment by that id not found")
|
||||
comment.locked = True
|
||||
try:
|
||||
db_session.commit()
|
||||
db_session.flush()
|
||||
db.session.commit()
|
||||
db.session.flush()
|
||||
return comment
|
||||
except:
|
||||
db_session.rollback()
|
||||
db.session.rollback()
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def add_comment(cls, pid: int, user_id: int, message: str, cid: int = None, message_id: int = None, automated=False):
|
||||
from flask.ext.login import current_user
|
||||
from funding.factory import db_session
|
||||
from flask_login import current_user
|
||||
from funding.factory import db
|
||||
if not message:
|
||||
raise Exception("empty message")
|
||||
|
||||
|
@ -448,7 +430,7 @@ class Comment(base):
|
|||
comment.replied_to = parent.id
|
||||
else:
|
||||
try:
|
||||
user = db_session.query(User).filter(User.id == user_id).first()
|
||||
user = db.session.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise Exception("no user by that id")
|
||||
comment = next(c for c in user.comments if c.id == message_id)
|
||||
|
@ -460,10 +442,10 @@ class Comment(base):
|
|||
raise Exception("unknown error")
|
||||
try:
|
||||
comment.message = message
|
||||
db_session.add(comment)
|
||||
db_session.commit()
|
||||
db_session.flush()
|
||||
db.session.add(comment)
|
||||
db.session.commit()
|
||||
db.session.flush()
|
||||
except Exception as ex:
|
||||
db_session.rollback()
|
||||
db.session.rollback()
|
||||
raise Exception(str(ex))
|
||||
return comment
|
||||
return comment
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
from flask import request, redirect, Response, abort, render_template, url_for, flash, make_response, send_from_directory, jsonify
|
||||
from flask.ext.login import login_user , logout_user , current_user, login_required, current_user
|
||||
from flask_login import login_user , logout_user , current_user, login_required, current_user
|
||||
from dateutil.parser import parse as dateutil_parse
|
||||
from flask_yoloapi import endpoint, parameter
|
||||
|
||||
import settings
|
||||
from funding.factory import app, db_session
|
||||
from funding.bin.utils import Summary
|
||||
from funding.factory import app, db, cache
|
||||
from funding.orm.orm import Proposal, User, Comment
|
||||
|
||||
|
||||
|
@ -27,7 +29,7 @@ def api():
|
|||
|
||||
@app.route('/proposal/add/disclaimer')
|
||||
def proposal_add_disclaimer():
|
||||
return make_response(render_template(('proposal/disclaimer.html')))
|
||||
return make_response(render_template('proposal/disclaimer.html'))
|
||||
|
||||
|
||||
@app.route('/proposal/add')
|
||||
|
@ -79,9 +81,9 @@ def propsal_comment_reply(cid, pid):
|
|||
@app.route('/proposal/<int:pid>')
|
||||
def proposal(pid):
|
||||
p = Proposal.find_by_id(pid=pid)
|
||||
p.get_comments()
|
||||
if not p:
|
||||
return make_response(redirect(url_for('proposals')))
|
||||
p.get_comments()
|
||||
return make_response(render_template(('proposal/proposal.html'), proposal=p))
|
||||
|
||||
|
||||
|
@ -155,9 +157,9 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
|
|||
except Exception as ex:
|
||||
return make_response(jsonify('letters detected'),500)
|
||||
if funds_target < 1:
|
||||
return make_response(jsonify('Proposal asking less than 1 error :)'), 500)
|
||||
if len(addr_receiving) != settings.COIN_ADDRESS_LENGTH:
|
||||
return make_response(jsonify('Faulty address, should be of length 72'), 500)
|
||||
return make_response(jsonify('Proposal asking less than 1 error :)'), 500)
|
||||
if len(addr_receiving) not in settings.COIN_ADDRESS_LENGTH:
|
||||
return make_response(jsonify(f'Faulty address, should be of length: {" or ".join(map(str, settings.COIN_ADDRESS_LENGTH))}'), 500)
|
||||
|
||||
p = Proposal(headline=title, content=content, category='misc', user=current_user)
|
||||
p.html = html
|
||||
|
@ -167,18 +169,32 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
|
|||
p.category = category
|
||||
p.status = status
|
||||
|
||||
db_session.add(p)
|
||||
db_session.commit()
|
||||
db_session.flush()
|
||||
# generate integrated address
|
||||
try:
|
||||
r = requests.get(f'http://{settings.RPC_HOST}:{settings.RPC_PORT}/json_rpc', json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "0",
|
||||
"method": "make_integrated_address"
|
||||
})
|
||||
r.raise_for_status()
|
||||
blob = r.json()
|
||||
|
||||
p.addr_donation = Proposal.generate_donation_addr(p)
|
||||
assert 'result' in blob
|
||||
assert 'integrated_address' in blob['result']
|
||||
assert 'payment_id' in blob['result']
|
||||
except Exception as ex:
|
||||
raise
|
||||
|
||||
db_session.commit()
|
||||
db_session.flush()
|
||||
p.addr_donation = blob['result']['integrated_address']
|
||||
p.payment_id = blob['result']['payment_id']
|
||||
|
||||
# reset cached statistics
|
||||
from funding.bin.utils import Summary
|
||||
Summary.fetch_stats(purge=True)
|
||||
db.session.add(p)
|
||||
|
||||
db.session.commit()
|
||||
db.session.flush()
|
||||
|
||||
# reset cached stuffz
|
||||
cache.delete('funding_stats')
|
||||
|
||||
return make_response(jsonify({'url': url_for('proposal', pid=p.id)}))
|
||||
|
||||
|
@ -189,7 +205,7 @@ def proposal_edit(pid):
|
|||
if not p:
|
||||
return make_response(redirect(url_for('proposals')))
|
||||
|
||||
return make_response(render_template(('proposal/edit.html'), proposal=p))
|
||||
return make_response(render_template('proposal/edit.html', proposal=p))
|
||||
|
||||
|
||||
@app.route('/search')
|
||||
|
@ -205,7 +221,7 @@ def search(key=None):
|
|||
|
||||
@app.route('/user/<path:name>')
|
||||
def user(name):
|
||||
q = db_session.query(User)
|
||||
q = db.session.query(User)
|
||||
q = q.filter(User.username == name)
|
||||
user = q.first()
|
||||
return render_template('user.html', user=user)
|
||||
|
@ -241,7 +257,9 @@ def proposals(status, page, cat):
|
|||
@app.route('/donate')
|
||||
def donate():
|
||||
from funding.bin.daemon import Daemon
|
||||
from funding.factory import cache, db_session
|
||||
from funding.factory import cache, db
|
||||
|
||||
return "devfund page currently not working :D"
|
||||
|
||||
data_default = {'sum': 0, 'txs': []}
|
||||
cache_key = 'devfund_txs_in'
|
||||
|
@ -280,6 +298,7 @@ def register():
|
|||
try:
|
||||
user = User.add(username, password, email)
|
||||
flash('Successfully registered. No confirmation email required. You can login!')
|
||||
cache.delete('funding_stats') # reset cached stuffz
|
||||
return redirect(url_for('login'))
|
||||
except Exception as ex:
|
||||
flash('Could not register user. Probably a duplicate username or email that already exists.', 'error')
|
||||
|
@ -318,4 +337,4 @@ def logout():
|
|||
|
||||
@app.route('/static/<path:path>')
|
||||
def static_route(path):
|
||||
return send_from_directory('static', path)
|
||||
return send_from_directory('static', path)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from flask.ext.login import login_required
|
||||
from wowfunding.factory import app, db_session
|
||||
from flask_login import login_required
|
||||
from funding.factory import app, db
|
||||
|
||||
|
||||
@app.route('/admin/index')
|
||||
|
|
|
@ -650,10 +650,6 @@ ul.b {
|
|||
color: #999;
|
||||
}
|
||||
|
||||
.tx_item .height {
|
||||
float:right
|
||||
}
|
||||
|
||||
.tx_item .height b {
|
||||
font-size:14px;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
When you encounter problems; please visit #wownero on chat.freenode.org
|
||||
</p>
|
||||
<p>
|
||||
AEON (<a target="_blank" href="https://github.com/camthegeek">camthegeek</a>) has contributed commits to <a href="http://github.com/skftn/wownero-wfs">upstream</a>; WOW is grateful.
|
||||
AEON (<a target="_blank" href="https://github.com/camthegeek">camthegeek</a>) has contributed commits <a href="http://github.com/skftn/wownero-wfs">upstream</a>; WOW is grateful.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
<!-- Footer -->
|
||||
<footer class="bg-dark footer">
|
||||
<div class="container">
|
||||
<p class="m-0 text-center text-white">WOW 2018</p>
|
||||
<p class="m-0 text-center text-white">WOW 2018-2020</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<div class="col-lg-4">
|
||||
<form class="form-horizontal" action="" method=post>
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="inlineFormInput">Name</label>
|
||||
|
@ -46,10 +46,13 @@
|
|||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Login</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<small>Forgot your password? Out of luck! Create a new account lol</small>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<a href="/register">Or register here</a>
|
||||
<a href="/register">Register here</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,20 +1,28 @@
|
|||
{% macro tx_item(tx) %}
|
||||
<li class="list-group-item tx_item">
|
||||
<span class="datetime">
|
||||
{{tx['datetime'].strftime('%Y-%m-%d %H:%M')}}
|
||||
</span>
|
||||
|
||||
<span class="height">
|
||||
<b>Blockheight</b>:
|
||||
{% if tx['type'] == 'pool' %}
|
||||
soon^tm
|
||||
{% else %}
|
||||
{{tx['height']}}
|
||||
{% endif %}
|
||||
{% if tx['type'] == 'pool' %}
|
||||
soon^tm
|
||||
{% elif tx['type'] == 'out' %}
|
||||
<small>hidden</small>
|
||||
{% else %}
|
||||
{{tx['block_height']}}
|
||||
{% endif %}
|
||||
</span>
|
||||
<br>
|
||||
|
||||
{% if tx['type'] in ['in', 'pool'] %}
|
||||
<a target="_blank" href="https://explore.wownero.com/tx/{{tx['txid']}}">{{tx['txid'][:32]}}...</a>
|
||||
{% else %}
|
||||
{% set lulz = [
|
||||
'vodka', 'hookers', 'booze', 'strippers', 'new lambo',
|
||||
'new ferrari', 'new villa', 'new vacation home', 'new tesla',
|
||||
'new watch', 'new home cinema set', 'throphy wife', 'drugs']
|
||||
%}
|
||||
<a style="font-size:11px;" href="#">Sent to author. Enjoy the {{ lulz|random }}!</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="amount{% if tx['type'] in ['pool', 'in'] %} in{% endif %}">
|
||||
{% if tx['type'] in ['in', 'pool'] %}
|
||||
+
|
||||
|
|
|
@ -97,11 +97,13 @@
|
|||
{{proposal.balance['available']|round(3) or 0 }} WOW Raised
|
||||
{% set remaining = proposal.funds_target - proposal.balance['available']|float|round(3) %}
|
||||
|
||||
<small>
|
||||
{% if remaining > 0 %}
|
||||
({{ (proposal.funds_target - proposal.balance['available']|float|round(3)|int) }} WOW until goal)
|
||||
{% elif remaining < 0 %}
|
||||
({{ (proposal.balance['available']-proposal.funds_target|float|round(3)|int) }} WOW past goal!)
|
||||
{% endif %}
|
||||
</small>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.balance['pct']}}%;">
|
||||
|
@ -113,15 +115,16 @@
|
|||
|
||||
<br/>
|
||||
<div class="col-lg-8">
|
||||
{{proposal.spends['spent']|round(3) or 0}} WOW Paid out
|
||||
{{ proposal.spends['amount'] }} WOW Paid out <small>({{ proposal.spends['pct']|round(1) }}%)</small>
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.spends['remaining_pct']}}%;">
|
||||
<!-- @todo: spends remaining pct -->
|
||||
<div class="progress-bar progress-warning progress-bar" style="width: {{ proposal.spends['pct'] }}%;">
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
{{(proposal.balance['available']-proposal.spends['spent']) |round(3) or 0}} WOW Available to Payout
|
||||
{{ (proposal.balance['available']-proposal.spends['amount']) |round(3) or 0}} WOW Available for payout :-)
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.balance['remaining_pct']}}%;">
|
||||
</div>
|
||||
|
@ -185,15 +188,15 @@
|
|||
<!-- /.row -->
|
||||
{% endif %}
|
||||
|
||||
{% if proposal.spends['txs'] %}
|
||||
{% if proposal.payouts %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card my-6" id="incoming_txs">
|
||||
<h5 class="card-header">Outgoing transactions <small>({{proposal.spends['txs']|length}})</small></h5>
|
||||
<h5 class="card-header">Outgoing transactions</h5>
|
||||
<div class="card-body">
|
||||
<ul class="list-group">
|
||||
{% for tx in proposal.spends['txs'] %}
|
||||
{{ tx_item(tx) }}
|
||||
{% for payout in proposal.payouts %}
|
||||
{{ tx_item(payout.as_tx) }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
sqlalchemy==1.3.4
|
||||
flask==0.12.3
|
||||
flask
|
||||
flask-yoloapi==0.1.5
|
||||
flask_session
|
||||
flask-login
|
||||
|
@ -12,3 +12,6 @@ requests
|
|||
pyqrcode
|
||||
pypng
|
||||
pillow-simd
|
||||
Flask-Caching
|
||||
flask-sqlalchemy
|
||||
sqlalchemy_json
|
|
@ -7,11 +7,12 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|||
SECRET = ''
|
||||
DEBUG = True
|
||||
|
||||
COIN_ADDRESS_LENGTH = 97
|
||||
COIN_ADDRESS_LENGTH = [97, 108]
|
||||
COINCODE = ''
|
||||
PSQL_USER = ''
|
||||
PSQL_PASS = ''
|
||||
PSQL_HOST = "127.0.0.1:5432"
|
||||
PSQL_DB = ''
|
||||
PSQL_USER = 'postgres'
|
||||
PSQL_PASS = ''
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://{user}:{pw}@localhost/{db}').format(user=PSQL_USER, pw=PSQL_PASS, db=PSQL_DB)
|
||||
|
||||
|
|
Loading…
Reference in New Issue