From 2b84f3bd5de3a500b63193924bea2f3dfd0c9afd Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 21 Apr 2021 21:07:18 +0200 Subject: common base template, new logo and lots of cleanup --- talermerchantdemos/blog/blog.py | 190 +++++++++++++++++++++++++--------------- 1 file changed, 120 insertions(+), 70 deletions(-) (limited to 'talermerchantdemos/blog/blog.py') 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("//") 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) -- cgit v1.2.3