app.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.app
  4. ~~~~~~~~~~~~~~~~~~~~
  5. manages the app creation and configuration process
  6. :copyright: (c) 2014 by the FlaskBB Team.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. import os
  10. import logging
  11. import datetime
  12. import time
  13. from sqlalchemy import event
  14. from sqlalchemy.engine import Engine
  15. from flask import Flask, request
  16. from flask_login import current_user
  17. from flask_whooshalchemy import whoosh_index
  18. # Import the user blueprint
  19. from flaskbb.user.views import user
  20. from flaskbb.user.models import User, Guest
  21. # Import the (private) message blueprint
  22. from flaskbb.message.views import message
  23. from flaskbb.message.models import Conversation
  24. # Import the auth blueprint
  25. from flaskbb.auth.views import auth
  26. # Import the admin blueprint
  27. from flaskbb.management.views import management
  28. # Import the forum blueprint
  29. from flaskbb.forum.views import forum
  30. from flaskbb.forum.models import Post, Topic, Category, Forum
  31. # extensions
  32. from flaskbb.extensions import db, login_manager, mail, cache, redis_store, \
  33. debugtoolbar, migrate, themes, plugin_manager, babel, csrf
  34. # various helpers
  35. from flaskbb.utils.helpers import format_date, time_since, crop_title, \
  36. is_online, render_markup, mark_online, forum_is_unread, topic_is_unread, \
  37. render_template
  38. from flaskbb.utils.translations import FlaskBBDomain
  39. # permission checks (here they are used for the jinja filters)
  40. from flaskbb.utils.permissions import can_post_reply, can_post_topic, \
  41. can_delete_topic, can_delete_post, can_edit_post, can_edit_user, \
  42. can_ban_user, can_moderate, is_admin, is_moderator, is_admin_or_moderator
  43. # app specific configurations
  44. from flaskbb.utils.settings import flaskbb_config
  45. def create_app(config=None):
  46. """Creates the app."""
  47. # Initialize the app
  48. app = Flask("flaskbb")
  49. # Use the default config and override it afterwards
  50. app.config.from_object('flaskbb.configs.default.DefaultConfig')
  51. # Update the config
  52. app.config.from_object(config)
  53. # try to update the config via the environment variable
  54. app.config.from_envvar("FLASKBB_SETTINGS", silent=True)
  55. configure_blueprints(app)
  56. configure_extensions(app)
  57. configure_template_filters(app)
  58. configure_context_processors(app)
  59. configure_before_handlers(app)
  60. configure_errorhandlers(app)
  61. configure_logging(app)
  62. return app
  63. def configure_blueprints(app):
  64. app.register_blueprint(forum, url_prefix=app.config["FORUM_URL_PREFIX"])
  65. app.register_blueprint(user, url_prefix=app.config["USER_URL_PREFIX"])
  66. app.register_blueprint(auth, url_prefix=app.config["AUTH_URL_PREFIX"])
  67. app.register_blueprint(
  68. management, url_prefix=app.config["ADMIN_URL_PREFIX"]
  69. )
  70. app.register_blueprint(
  71. message, url_prefix=app.config["MESSAGE_URL_PREFIX"]
  72. )
  73. def configure_extensions(app):
  74. """Configures the extensions."""
  75. # Flask-WTF CSRF
  76. csrf.init_app(app)
  77. # Flask-Plugins
  78. plugin_manager.init_app(app)
  79. # Flask-SQLAlchemy
  80. db.init_app(app)
  81. # Flask-Migrate
  82. migrate.init_app(app, db)
  83. # Flask-Mail
  84. mail.init_app(app)
  85. # Flask-Cache
  86. cache.init_app(app)
  87. # Flask-Debugtoolbar
  88. debugtoolbar.init_app(app)
  89. # Flask-Themes
  90. themes.init_themes(app, app_identifier="flaskbb")
  91. # Flask-And-Redis
  92. redis_store.init_app(app)
  93. # Flask-WhooshAlchemy
  94. with app.app_context():
  95. whoosh_index(app, Post)
  96. whoosh_index(app, Topic)
  97. whoosh_index(app, Forum)
  98. whoosh_index(app, Category)
  99. whoosh_index(app, User)
  100. # Flask-Login
  101. login_manager.login_view = app.config["LOGIN_VIEW"]
  102. login_manager.refresh_view = app.config["REAUTH_VIEW"]
  103. login_manager.anonymous_user = Guest
  104. @login_manager.user_loader
  105. def load_user(user_id):
  106. """Loads the user. Required by the `login` extension."""
  107. user_instance = User.query.filter_by(id=user_id).first()
  108. if user_instance:
  109. return user_instance
  110. else:
  111. return None
  112. login_manager.init_app(app)
  113. # Flask-BabelEx
  114. babel.init_app(app=app, default_domain=FlaskBBDomain(app))
  115. @babel.localeselector
  116. def get_locale():
  117. # if a user is logged in, use the locale from the user settings
  118. if current_user.is_authenticated() and current_user.language:
  119. return current_user.language
  120. # otherwise we will just fallback to the default language
  121. return flaskbb_config["DEFAULT_LANGUAGE"]
  122. def configure_template_filters(app):
  123. """Configures the template filters."""
  124. app.jinja_env.filters['markup'] = render_markup
  125. app.jinja_env.filters['format_date'] = format_date
  126. app.jinja_env.filters['time_since'] = time_since
  127. app.jinja_env.filters['is_online'] = is_online
  128. app.jinja_env.filters['crop_title'] = crop_title
  129. app.jinja_env.filters['forum_is_unread'] = forum_is_unread
  130. app.jinja_env.filters['topic_is_unread'] = topic_is_unread
  131. # Permission filters
  132. app.jinja_env.filters['edit_post'] = can_edit_post
  133. app.jinja_env.filters['delete_post'] = can_delete_post
  134. app.jinja_env.filters['delete_topic'] = can_delete_topic
  135. app.jinja_env.filters['post_reply'] = can_post_reply
  136. app.jinja_env.filters['post_topic'] = can_post_topic
  137. # Moderator permission filters
  138. app.jinja_env.filters['is_admin'] = is_admin
  139. app.jinja_env.filters['is_moderator'] = is_moderator
  140. app.jinja_env.filters['is_admin_or_moderator'] = is_admin_or_moderator
  141. app.jinja_env.filters['can_moderate'] = can_moderate
  142. app.jinja_env.filters['can_edit_user'] = can_edit_user
  143. app.jinja_env.filters['can_ban_user'] = can_ban_user
  144. def configure_context_processors(app):
  145. """Configures the context processors."""
  146. @app.context_processor
  147. def inject_flaskbb_config():
  148. """Injects the ``flaskbb_config`` config variable into the
  149. templates.
  150. """
  151. return dict(flaskbb_config=flaskbb_config)
  152. def configure_before_handlers(app):
  153. """Configures the before request handlers."""
  154. @app.before_request
  155. def update_lastseen():
  156. """Updates `lastseen` before every reguest if the user is
  157. authenticated."""
  158. if current_user.is_authenticated():
  159. current_user.lastseen = datetime.datetime.utcnow()
  160. db.session.add(current_user)
  161. db.session.commit()
  162. if app.config["REDIS_ENABLED"]:
  163. @app.before_request
  164. def mark_current_user_online():
  165. if current_user.is_authenticated():
  166. mark_online(current_user.username)
  167. else:
  168. mark_online(request.remote_addr, guest=True)
  169. def configure_errorhandlers(app):
  170. """Configures the error handlers."""
  171. @app.errorhandler(403)
  172. def forbidden_page(error):
  173. return render_template("errors/forbidden_page.html"), 403
  174. @app.errorhandler(404)
  175. def page_not_found(error):
  176. return render_template("errors/page_not_found.html"), 404
  177. @app.errorhandler(500)
  178. def server_error_page(error):
  179. return render_template("errors/server_error.html"), 500
  180. def configure_logging(app):
  181. """Configures logging."""
  182. logs_folder = os.path.join(app.root_path, os.pardir, "logs")
  183. from logging.handlers import SMTPHandler
  184. formatter = logging.Formatter(
  185. '%(asctime)s %(levelname)s: %(message)s '
  186. '[in %(pathname)s:%(lineno)d]')
  187. info_log = os.path.join(logs_folder, app.config['INFO_LOG'])
  188. info_file_handler = logging.handlers.RotatingFileHandler(
  189. info_log,
  190. maxBytes=100000,
  191. backupCount=10
  192. )
  193. info_file_handler.setLevel(logging.INFO)
  194. info_file_handler.setFormatter(formatter)
  195. app.logger.addHandler(info_file_handler)
  196. error_log = os.path.join(logs_folder, app.config['ERROR_LOG'])
  197. error_file_handler = logging.handlers.RotatingFileHandler(
  198. error_log,
  199. maxBytes=100000,
  200. backupCount=10
  201. )
  202. error_file_handler.setLevel(logging.ERROR)
  203. error_file_handler.setFormatter(formatter)
  204. app.logger.addHandler(error_file_handler)
  205. if app.config["SEND_LOGS"]:
  206. mail_handler = \
  207. SMTPHandler(
  208. app.config['MAIL_SERVER'],
  209. app.config['MAIL_DEFAULT_SENDER'],
  210. app.config['ADMINS'],
  211. 'application error, no admins specified',
  212. (app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD'])
  213. )
  214. mail_handler.setLevel(logging.ERROR)
  215. mail_handler.setFormatter(formatter)
  216. app.logger.addHandler(mail_handler)
  217. if app.config["SQLALCHEMY_ECHO"]:
  218. # Ref: http://stackoverflow.com/a/8428546
  219. @event.listens_for(Engine, "before_cursor_execute")
  220. def before_cursor_execute(conn, cursor, statement,
  221. parameters, context, executemany):
  222. conn.info.setdefault('query_start_time', []).append(time.time())
  223. @event.listens_for(Engine, "after_cursor_execute")
  224. def after_cursor_execute(conn, cursor, statement,
  225. parameters, context, executemany):
  226. total = time.time() - conn.info['query_start_time'].pop(-1)
  227. app.logger.debug("Total Time: %f", total)