summaryrefslogtreecommitdiff
path: root/talermerchantdemos/blog/blog.py
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-04-21 21:07:18 +0200
committerFlorian Dold <florian@dold.me>2021-04-21 21:07:18 +0200
commit2b84f3bd5de3a500b63193924bea2f3dfd0c9afd (patch)
tree08d5a2bd354ab7849d731eabe7b132429b6d2225 /talermerchantdemos/blog/blog.py
parent4356540b2eb12b2c307270566b0b4ce969e12c60 (diff)
downloadtaler-merchant-demos-2b84f3bd5de3a500b63193924bea2f3dfd0c9afd.tar.gz
taler-merchant-demos-2b84f3bd5de3a500b63193924bea2f3dfd0c9afd.tar.bz2
taler-merchant-demos-2b84f3bd5de3a500b63193924bea2f3dfd0c9afd.zip
common base template, new logo and lots of cleanup
Diffstat (limited to 'talermerchantdemos/blog/blog.py')
-rw-r--r--talermerchantdemos/blog/blog.py190
1 files changed, 120 insertions, 70 deletions
diff --git a/talermerchantdemos/blog/blog.py b/talermerchantdemos/blog/blog.py
index be9b0a2..e8cce32 100644
--- a/talermerchantdemos/blog/blog.py
+++ b/talermerchantdemos/blog/blog.py
@@ -34,20 +34,38 @@ import sys
from urllib.parse import urljoin, urlencode, urlparse
from taler.util.talerconfig import TalerConfig, ConfigurationError
from ..blog.content import ARTICLES, get_article_file, get_image_file
-from talermerchantdemos.httpcommon import backend_get, backend_post, self_localized, err_abort, Deadline
+from talermerchantdemos.httpcommon import (
+ backend_get,
+ backend_post,
+ self_localized,
+ Deadline,
+ BackendException,
+)
+
+
+def err_abort(abort_status_code, **params):
+ """
+ Return a error response to the client.
+
+ @param abort_status_code status code to return along the response.
+ @param params _kw_ arguments to passed verbatim to the templating engine.
+ """
+ t = flask.render_template("blog-error.html.j2", lang=get_locale(), **params)
+ flask.abort(flask.make_response(t, abort_status_code))
def refundable(pay_status):
refunded = pay_status.get("refunded")
refund_deadline = pay_status.get("contract_terms", {}).get("refund_deadline")
- assert(refunded != None and refund_deadline)
+ assert refunded != None and refund_deadline
t_ms = refund_deadline.get("t_ms")
- assert(t_ms)
+ assert t_ms
rd = Deadline(t_ms)
if not refunded and not rd.isExpired():
return True
return False
+
if not sys.version_info.major == 3 and sys.version_info.minor >= 6:
print("Python 3.6 or higher is required.")
print(
@@ -57,10 +75,7 @@ if not sys.version_info.major == 3 and sys.version_info.minor >= 6:
)
sys.exit(1)
-BASE_DIR = os.path.dirname(os.path.abspath(__file__))
-app = flask.Flask(
- __name__, template_folder=BASE_DIR, static_folder=BASE_DIR + "/../static/"
-)
+app = flask.Flask(__name__, template_folder="../templates", static_folder="../static")
app.debug = True
app.secret_key = base64.b64encode(os.urandom(64)).decode("utf-8")
@@ -73,19 +88,24 @@ try:
except ConfigurationError as ce:
print(ce)
exit(1)
+
ARTICLE_AMOUNT = CURRENCY + ":0.5"
BACKEND_URL = urljoin(BACKEND_BASE_URL, "instances/blog/")
+BABEL_TRANSLATION_DIRECTORIES = "../translations"
+
app.config.from_object(__name__)
babel = Babel(app)
-LOGGER.info("Using translations from:" + ':'.join(list(babel.translation_directories)))
+LOGGER.info("Using translations from:" + ":".join(list(babel.translation_directories)))
translations = [str(translation) for translation in babel.list_translations()]
-if not 'en' in translations:
- translations.append('en')
-LOGGER.info("Operating with the following translations available: " + ' '.join(translations))
+if not "en" in translations:
+ translations.append("en")
+LOGGER.info(
+ "Operating with the following translations available: " + " ".join(translations)
+)
-app.jinja_env.globals.update(self_localized=self_localized)
+app.add_template_global(self_localized)
##
@@ -98,6 +118,7 @@ def utility_processor():
# These helpers will be available in templates
def env(name, default=None):
return os.environ.get(name, default)
+
return dict(env=env)
@@ -110,9 +131,10 @@ def utility_processor():
@app.errorhandler(Exception)
def internal_error(e):
return flask.render_template(
- "templates/error.html.j2",
+ "blog-error.html.j2",
message=gettext("Internal error"),
- stack=traceback.format_exc()
+ stack=traceback.format_exc(),
+ lang=get_locale(),
)
@@ -122,27 +144,29 @@ def internal_error(e):
# @return response object of the index page.
@app.route("/")
def index():
- default = 'en'
+ default = "en"
target = flask.request.accept_languages.best_match(translations, default)
return flask.redirect("/" + target + "/", code=302)
+
##
# Serve the /favicon.ico requests.
#
# @return the favicon.ico file.
@app.route("/favicon.ico")
def favicon():
- LOGGER.info("will look into: " + os.path.join(app.root_path, 'static'))
+ LOGGER.info("will look into: " + os.path.join(app.root_path, "static"))
return flask.send_from_directory(
- os.path.join(app.root_path, 'static'),
+ os.path.join(app.root_path, "static"),
"favicon.ico",
- mimetype="image/vnd.microsoft.ico"
+ mimetype="image/vnd.microsoft.ico",
)
+
@babel.localeselector
def get_locale():
- parts = request.path.split('/', 2)
- if (2 >= len(parts)):
+ parts = request.path.split("/", 2)
+ if 2 >= len(parts):
# Totally unexpected path format, do not localize
return "en"
lang = parts[1]
@@ -150,6 +174,7 @@ def get_locale():
return lang
return "en"
+
##
# Serve the main index page for a particular language.
#
@@ -157,14 +182,14 @@ def get_locale():
@app.route("/<lang>/")
def start(lang):
if lang in ARTICLES:
- translated=ARTICLES[lang]
+ translated = ARTICLES[lang]
else:
- translated={}
+ translated = {}
return flask.render_template(
- "templates/index.html.j2",
+ "blog-index.html.j2",
lang=lang,
merchant_currency=CURRENCY,
- articles=translated.values()
+ articles=translated.values(),
)
@@ -172,21 +197,30 @@ def start(lang):
def confirm_refund(lang, order_id):
session_id = flask.session.get("session_id", "")
pay_status = backend_get(
- BACKEND_URL, f"private/orders/{order_id}", params=dict(session_id=session_id)
+ BACKEND_URL,
+ f"private/orders/{order_id}",
+ params=dict(session_id=session_id),
+ auth_token=APIKEY,
)
order_status = pay_status.get("order_status")
if order_status != "paid":
err_abort(
- 400, message=gettext("Cannot refund unpaid article"),
+ 400,
+ message=gettext("Cannot refund unpaid article"),
)
article_name = pay_status["contract_terms"]["extra"]["article_name"]
if not refundable(pay_status):
return flask.render_template(
- "templates/error.html.j2", message=gettext("Article is not anymore refundable")
+ "blog-error.html.j2",
+ message=gettext("Article is not anymore refundable"),
+ lang=get_locale(),
)
return flask.render_template(
- "templates/confirm_refund.html.j2", article_name=article_name, order_id=order_id
+ "blog-confirm-refund.html.j2",
+ article_name=article_name,
+ order_id=order_id,
+ lang=get_locale(),
)
@@ -207,7 +241,10 @@ def refund(order_id):
return flask.jsonify(dict(error="Aborting refund: order unknown")), 400
session_id = flask.session.get("session_id", "")
pay_status = backend_get(
- BACKEND_URL, f"private/orders/{order_id}", params=dict(session_id=session_id)
+ BACKEND_URL,
+ f"private/orders/{order_id}",
+ params=dict(session_id=session_id),
+ auth_token=APIKEY,
)
order_status = pay_status.get("order_status")
@@ -215,16 +252,16 @@ def refund(order_id):
err_abort(
402,
message=gettext("You did not pay for this article (nice try!)"),
- json=pay_status
+ json=pay_status,
)
if not refundable(pay_status):
err_abort(
- 403,
- message=gettext("Item not refundable (anymore)"),
- json=pay_status
+ 403, message=gettext("Item not refundable (anymore)"), json=pay_status
)
refund_spec = dict(reason="Demo reimbursement", refund=ARTICLE_AMOUNT)
- resp = backend_post(BACKEND_URL, f"private/orders/{order_id}/refund", refund_spec)
+ backend_post(
+ BACKEND_URL, f"private/orders/{order_id}/refund", refund_spec, auth_token=APIKEY
+ )
return flask.redirect(pay_status["order_status_url"])
@@ -245,7 +282,9 @@ def refund(order_id):
def render_article(article_name, lang, data, order_id, refundable):
article_info = ARTICLES[lang].get(article_name)
if article_info is None:
- m = gettext("Internal error: Files for article ({}) not found.").format(article_name)
+ m = gettext("Internal error: Files for article ({}) not found.").format(
+ article_name
+ )
err_abort(500, message=m)
if data is not None:
if data in article_info.extra_files:
@@ -255,25 +294,27 @@ def render_article(article_name, lang, data, order_id, refundable):
)
err_abort(404, message=m)
# the order_id is needed for refunds
+ article_contents = open(get_article_file(article_info)).read()
return flask.render_template(
- "templates/article_frame.html.j2",
- article_file=get_article_file(article_info),
+ "blog-article-frame.html.j2",
+ article_contents=article_contents,
article_name=article_name,
order_id=order_id,
lang=lang,
- refundable=refundable
+ refundable=refundable,
)
+
##
# Setup a fresh order with the backend.
#
# @param article_name which article the order is for
# @param lang which language to use
#
-def post_order(article_name,lang):
+def post_order(article_name, lang):
order = dict(
amount=ARTICLE_AMOUNT,
- extra=dict(article_name=article_name,lang=lang),
+ extra=dict(article_name=article_name, lang=lang),
fulfillment_url=flask.request.base_url,
summary="Essay: " + article_name.replace("_", " "),
# FIXME: add support for i18n of summary!
@@ -283,7 +324,9 @@ def post_order(article_name,lang):
order_resp = backend_post(
BACKEND_URL,
"private/orders",
- dict(order=order, refund_delay=dict(d_ms=1000 * 120)))
+ dict(order=order, refund_delay=dict(d_ms=1000 * 120)),
+ auth_token=APIKEY,
+ )
return order_resp
@@ -324,26 +367,28 @@ def article(article_name, lang=None, data=None):
if not order_id:
if not lang:
err_abort(403, message=gettext("Direct access forbidden"))
- order_resp = post_order(article_name,lang)
+ order_resp = post_order(article_name, lang)
order_id = order_resp["order_id"]
# Ask the backend for the status of the payment
pay_status = backend_get(
BACKEND_URL,
f"private/orders/{order_id}",
- params=dict(session_id=session_id)
+ params=dict(session_id=session_id),
+ auth_token=APIKEY,
)
order_status = pay_status.get("order_status")
if order_status == "claimed":
if not lang:
err_abort(403, message=gettext("Direct access forbidden"))
# Order already claimed, must setup fresh order
- order_resp = post_order(article_name,lang)
+ order_resp = post_order(article_name, lang)
order_id = order_resp["order_id"]
pay_status = backend_get(
BACKEND_URL,
f"private/orders/{order_id}",
- params=dict(session_id=session_id)
+ params=dict(session_id=session_id),
+ auth_token=APIKEY,
)
order_status = pay_status.get("order_status")
# This really must be 'unpaid' now...
@@ -351,17 +396,14 @@ def article(article_name, lang=None, data=None):
if order_status == "paid":
refunded = pay_status["refunded"]
if refunded:
- return flask.render_template(
- "templates/article_refunded.html.j2",
+ return flask.render_template(
+ "blog-article-refunded.html.j2",
article_name=article_name,
order_id=order_id,
+ lang=lang,
)
response = render_article(
- article_name,
- lang,
- data,
- order_id,
- refundable(pay_status)
+ article_name, lang, data, order_id, refundable(pay_status)
)
return response
@@ -372,14 +414,10 @@ def article(article_name, lang=None, data=None):
if ai is not None and au is not None:
response = flask.redirect(au)
response.set_cookie(
- "order_id",
- ai,
- path=urllib.parse.quote(f"/essay/{article_name}")
+ "order_id", ai, path=urllib.parse.quote(f"/essay/{article_name}")
)
response.set_cookie(
- "order_id",
- ai,
- path=urllib.parse.quote(f"/{lang}/essay/{article_name}")
+ "order_id", ai, path=urllib.parse.quote(f"/{lang}/essay/{article_name}")
)
return response
@@ -387,27 +425,39 @@ def article(article_name, lang=None, data=None):
# run the payment protocol.
response = flask.redirect(pay_status["order_status_url"])
response.set_cookie(
- "order_id",
- order_id,
- path=urllib.parse.quote(f"/essay/{article_name}")
+ "order_id", order_id, path=urllib.parse.quote(f"/essay/{article_name}")
)
response.set_cookie(
- "order_id",
- order_id,
- path=urllib.parse.quote(f"/{lang}/essay/{article_name}")
+ "order_id", order_id, path=urllib.parse.quote(f"/{lang}/essay/{article_name}")
)
return response
+
@app.errorhandler(500)
-def handler(e):
+def handler_500(e):
return flask.render_template(
- "templates/error.html.j2",
- message=gettext("Internal server error")
+ "blog-error.html.j2",
+ message=gettext("Internal server error"),
+ lang=get_locale(),
)
+
@app.errorhandler(404)
-def handler(e):
+def handler_404(e):
return flask.render_template(
- "templates/error.html.j2",
- message=gettext("Page not found")
+ "blog-error.html.j2",
+ message=gettext("Page not found"),
+ lang=get_locale(),
+ )
+
+
+@app.errorhandler(BackendException)
+def handler_backend_exception(e):
+ t = flask.render_template(
+ "survey-error.html.j2",
+ lang=get_locale(),
+ message=e.args[0],
+ json=e.backend_json,
+ status_code=e.backend_status,
)
+ return flask.make_response(t, 500)