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.

190 lines
5.8KB

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