A Python content management system designed for kirsle.net featuring a blog, comments and photo albums. https://rophako.kirsle.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

app.py 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #!/usr/bin/env python
  2. from __future__ import unicode_literals, absolute_import
  3. """Flask app for Rophako."""
  4. from flask import (Flask, g, request, session, render_template, send_file,
  5. abort, redirect)
  6. from flask_sslify import SSLify
  7. from flask_compress import Compress
  8. import jinja2
  9. import os.path
  10. import datetime
  11. import sys
  12. # Get the Flask app object ready right away so other modules can import it
  13. # without getting a circular import error.
  14. app = Flask(__name__,
  15. static_url_path="/.static",
  16. )
  17. Compress(app)
  18. # We use a custom Jinja loader to support multiple template paths for custom
  19. # and default templates. The base list of template paths to check includes
  20. # your custom path (from config.SITE_ROOT), the "rophako/www" path for normal
  21. # pages, and then the blueprint paths for all imported plugins. This list will
  22. # be extended while blueprints are being loaded and passed in below to the
  23. # jinja2.ChoiceLoader.
  24. BLUEPRINT_PATHS = []
  25. from rophako.settings import Config
  26. Config.load_settings()
  27. Config.load_plugins()
  28. from rophako.plugin import load_plugin
  29. from rophako.log import logger
  30. #import rophako.model.tracking as Tracking
  31. import rophako.utils
  32. # String escaping for the secret key (processes \ escapes properly), the
  33. # escape encoding name varies between Python 2 and 3.
  34. string_escape = "string_escape" if sys.version_info[0] == 2 \
  35. else "unicode_escape"
  36. app.DEBUG = Config.site.debug == "true"
  37. app.secret_key = bytes(Config.security.secret_key.encode("utf-8")) \
  38. .decode(string_escape)
  39. # Make templates easier to edit live.
  40. app.config["TEMPLATES_AUTO_RELOAD"] = True
  41. # Security?
  42. if Config.security.force_ssl == True:
  43. app.config['SESSION_COOKIE_SECURE'] = True
  44. sslify = SSLify(app)
  45. # Load all the built-in essential plugins.
  46. load_plugin("rophako.modules.admin")
  47. load_plugin("rophako.modules.account")
  48. # Custom Jinja handler to support custom- and default-template folders for
  49. # rendering templates.
  50. template_paths = [
  51. Config.site.site_root, # Site specific.
  52. "rophako/www", # Default/fall-back
  53. ]
  54. template_paths.extend(BLUEPRINT_PATHS)
  55. app.jinja_loader = jinja2.ChoiceLoader([ jinja2.FileSystemLoader(x) for x in template_paths])
  56. app.jinja_env.trim_blocks = True
  57. app.jinja_env.lstrip_blocks = True
  58. app.jinja_env.globals["csrf_token"] = rophako.utils.generate_csrf_token
  59. app.jinja_env.globals["include_page"] = rophako.utils.include
  60. app.jinja_env.globals["settings"] = lambda: Config
  61. app.jinja_env.globals["strftime"] = lambda x: datetime.datetime.utcnow().strftime(x)
  62. # Preload the emoticon data.
  63. import rophako.model.emoticons as Emoticons
  64. Emoticons.load_theme()
  65. @app.before_request
  66. def before_request():
  67. """Called before all requests. Initialize global template variables."""
  68. # Session lifetime.
  69. app.permanent_session_lifetime = datetime.timedelta(days=Config.security.session_lifetime)
  70. session.permanent = True
  71. # Default template vars.
  72. g.info = rophako.utils.default_vars()
  73. # Default session vars.
  74. if not "login" in session:
  75. session.update(g.info["session"])
  76. # CSRF protection.
  77. if request.method == "POST":
  78. token = session.pop("_csrf", None)
  79. if not token or str(token) != str(request.form.get("token")):
  80. abort(403)
  81. # Refresh their login status from the DB.
  82. if session["login"]:
  83. import rophako.model.user as User
  84. if not User.exists(uid=session["uid"]):
  85. # Weird! Log them out.
  86. from rophako.modules.account import logout
  87. logout()
  88. return
  89. db = User.get_user(uid=session["uid"])
  90. session["username"] = db["username"]
  91. session["name"] = db["name"]
  92. session["role"] = db["role"]
  93. # Copy session params into g.info. The only people who should touch the
  94. # session are the login/out pages.
  95. for key in session:
  96. g.info["session"][key] = session[key]
  97. @app.context_processor
  98. def after_request():
  99. """Called just before render_template. Inject g.info into the template vars."""
  100. return g.info
  101. @app.route("/<path:path>")
  102. def catchall(path):
  103. """The catch-all path handler. If it exists in the www folders, it's sent,
  104. otherwise we give the 404 error page."""
  105. if path.endswith("/"):
  106. path = path.strip("/") # Remove trailing slashes.
  107. return redirect(path)
  108. # Search for this file.
  109. for root in [Config.site.site_root, "rophako/www"]:
  110. abspath = os.path.abspath("{}/{}".format(root, path))
  111. if os.path.isfile(abspath):
  112. return send_file(abspath)
  113. # The exact file wasn't found, look for some extensions and index pages.
  114. suffixes = [
  115. ".html",
  116. "/index.html",
  117. ".md", # Markdown formatted pages.
  118. "/index.md",
  119. ]
  120. for suffix in suffixes:
  121. if not "." in path and os.path.isfile(abspath + suffix):
  122. # HTML, or Markdown?
  123. if suffix.endswith(".html"):
  124. return rophako.utils.template(path + suffix)
  125. else:
  126. return rophako.utils.markdown_template(abspath + suffix)
  127. return not_found("404")
  128. @app.route("/")
  129. def index():
  130. return catchall("index")
  131. @app.errorhandler(Exception)
  132. def catch_exception(error):
  133. """Catch unexpected Python exceptions and e-mail them out."""
  134. logger.error("INTERNAL SERVER ERROR: {}".format(str(error)))
  135. # E-mail it out.
  136. rophako.utils.handle_exception(error)
  137. return rophako.utils.template("errors/500.html")
  138. @app.errorhandler(404)
  139. def not_found(error):
  140. return render_template('errors/404.html', **g.info), 404
  141. @app.errorhandler(403)
  142. def forbidden(error):
  143. return render_template('errors/403.html', **g.info), 403