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.

167 lines
5.0KB

  1. #!/usr/bin/env python
  2. """Flask app for Rophako."""
  3. from flask import Flask, g, request, session, render_template, send_file, abort
  4. from flask_sslify import SSLify
  5. import jinja2
  6. import os.path
  7. import time
  8. import sys
  9. # Get the Flask app object ready right away so other modules can import it
  10. # without getting a circular import error.
  11. app = Flask(__name__,
  12. static_url_path="/.static",
  13. )
  14. # We use a custom Jinja loader to support multiple template paths for custom
  15. # and default templates. The base list of template paths to check includes
  16. # your custom path (from config.SITE_ROOT), the "rophako/www" path for normal
  17. # pages, and then the blueprint paths for all imported plugins. This list will
  18. # be extended while blueprints are being loaded and passed in below to the
  19. # jinja2.ChoiceLoader.
  20. BLUEPRINT_PATHS = []
  21. from rophako.settings import Config
  22. Config.load_settings()
  23. Config.load_plugins()
  24. from rophako import __version__
  25. from rophako.plugin import load_plugin
  26. import rophako.model.tracking as Tracking
  27. import rophako.utils
  28. app.DEBUG = Config.site.debug == "true"
  29. app.secret_key = Config.security.secret_key.decode("string_escape")
  30. # Security?
  31. if Config.security.force_ssl == "true":
  32. app.config['SESSION_COOKIE_SECURE'] = True
  33. sslify = SSLify(app)
  34. # Load all the built-in essential plugins.
  35. load_plugin("rophako.modules.admin")
  36. load_plugin("rophako.modules.account")
  37. # Custom Jinja handler to support custom- and default-template folders for
  38. # rendering templates.
  39. template_paths = [
  40. Config.site.site_root, # Site specific.
  41. "rophako/www", # Default/fall-back
  42. ]
  43. template_paths.extend(BLUEPRINT_PATHS)
  44. app.jinja_loader = jinja2.ChoiceLoader([ jinja2.FileSystemLoader(x) for x in template_paths])
  45. app.jinja_env.globals["csrf_token"] = rophako.utils.generate_csrf_token
  46. app.jinja_env.globals["include_page"] = rophako.utils.include
  47. # Preload the emoticon data.
  48. import rophako.model.emoticons as Emoticons
  49. Emoticons.load_theme()
  50. @app.before_request
  51. def before_request():
  52. """Called before all requests. Initialize global template variables."""
  53. # Default template vars.
  54. g.info = {
  55. "time": time.time(),
  56. "app": {
  57. "name": "Rophako",
  58. "version": __version__,
  59. "python_version": "{}.{}".format(sys.version_info.major, sys.version_info.minor),
  60. "author": "Noah Petherbridge",
  61. "photo_url": Config.photo.root_public,
  62. },
  63. "uri": request.path,
  64. "session": {
  65. "login": False, # Not logged in, until proven otherwise.
  66. "username": "guest",
  67. "uid": 0,
  68. "name": "Guest",
  69. "role": "user",
  70. },
  71. "tracking": Tracking.track_visit(request, session),
  72. }
  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. # Search for this file.
  106. for root in [Config.site.site_root, "rophako/www"]:
  107. abspath = os.path.abspath("{}/{}".format(root, path))
  108. if os.path.isfile(abspath):
  109. return send_file(abspath)
  110. # The exact file wasn't found, look for some extensions and index pages.
  111. suffixes = [
  112. ".html",
  113. "/index.html",
  114. ".md", # Markdown formatted pages.
  115. "/index.md",
  116. ]
  117. for suffix in suffixes:
  118. if not "." in path and os.path.isfile(abspath + suffix):
  119. # HTML, or Markdown?
  120. if suffix.endswith(".html"):
  121. return rophako.utils.template(path + suffix)
  122. else:
  123. return rophako.utils.markdown_template(abspath + suffix)
  124. return not_found("404")
  125. @app.route("/")
  126. def index():
  127. return catchall("index")
  128. @app.errorhandler(404)
  129. def not_found(error):
  130. return render_template('errors/404.html', **g.info), 404
  131. @app.errorhandler(403)
  132. def forbidden(error):
  133. return render_template('errors/403.html', **g.info), 403