app.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  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. unread_count = db.session.query(db.func.count(Conversation.id)).\
  108. filter(Conversation.unread,
  109. Conversation.user_id == user_id).subquery()
  110. u = db.session.query(User, unread_count).filter(User.id == user_id).\
  111. first()
  112. if u:
  113. user_instance, user_instance.pm_unread = u
  114. return user_instance
  115. else:
  116. return None
  117. login_manager.init_app(app)
  118. # Flask-BabelEx
  119. babel.init_app(app=app, default_domain=FlaskBBDomain(app))
  120. @babel.localeselector
  121. def get_locale():
  122. # if a user is logged in, use the locale from the user settings
  123. if current_user.is_authenticated() and current_user.language:
  124. return current_user.language
  125. # otherwise we will just fallback to the default language
  126. return flaskbb_config["DEFAULT_LANGUAGE"]
  127. def configure_template_filters(app):
  128. """Configures the template filters."""
  129. app.jinja_env.filters['markup'] = render_markup
  130. app.jinja_env.filters['format_date'] = format_date
  131. app.jinja_env.filters['time_since'] = time_since
  132. app.jinja_env.filters['is_online'] = is_online
  133. app.jinja_env.filters['crop_title'] = crop_title
  134. app.jinja_env.filters['forum_is_unread'] = forum_is_unread
  135. app.jinja_env.filters['topic_is_unread'] = topic_is_unread
  136. # Permission filters
  137. app.jinja_env.filters['edit_post'] = can_edit_post
  138. app.jinja_env.filters['delete_post'] = can_delete_post
  139. app.jinja_env.filters['delete_topic'] = can_delete_topic
  140. app.jinja_env.filters['post_reply'] = can_post_reply
  141. app.jinja_env.filters['post_topic'] = can_post_topic
  142. # Moderator permission filters
  143. app.jinja_env.filters['is_admin'] = is_admin
  144. app.jinja_env.filters['is_moderator'] = is_moderator
  145. app.jinja_env.filters['is_admin_or_moderator'] = is_admin_or_moderator
  146. app.jinja_env.filters['can_moderate'] = can_moderate
  147. app.jinja_env.filters['can_edit_user'] = can_edit_user
  148. app.jinja_env.filters['can_ban_user'] = can_ban_user
  149. def configure_context_processors(app):
  150. """Configures the context processors."""
  151. @app.context_processor
  152. def inject_flaskbb_config():
  153. """Injects the ``flaskbb_config`` config variable into the
  154. templates.
  155. """
  156. return dict(flaskbb_config=flaskbb_config)
  157. def configure_before_handlers(app):
  158. """Configures the before request handlers."""
  159. @app.before_request
  160. def update_lastseen():
  161. """Updates `lastseen` before every reguest if the user is
  162. authenticated."""
  163. if current_user.is_authenticated():
  164. current_user.lastseen = datetime.datetime.utcnow()
  165. db.session.add(current_user)
  166. db.session.commit()
  167. if app.config["REDIS_ENABLED"]:
  168. @app.before_request
  169. def mark_current_user_online():
  170. if current_user.is_authenticated():
  171. mark_online(current_user.username)
  172. else:
  173. mark_online(request.remote_addr, guest=True)
  174. def configure_errorhandlers(app):
  175. """Configures the error handlers."""
  176. @app.errorhandler(403)
  177. def forbidden_page(error):
  178. return render_template("errors/forbidden_page.html"), 403
  179. @app.errorhandler(404)
  180. def page_not_found(error):
  181. return render_template("errors/page_not_found.html"), 404
  182. @app.errorhandler(500)
  183. def server_error_page(error):
  184. return render_template("errors/server_error.html"), 500
  185. def configure_logging(app):
  186. """Configures logging."""
  187. logs_folder = os.path.join(app.root_path, os.pardir, "logs")
  188. from logging.handlers import SMTPHandler
  189. formatter = logging.Formatter(
  190. '%(asctime)s %(levelname)s: %(message)s '
  191. '[in %(pathname)s:%(lineno)d]')
  192. info_log = os.path.join(logs_folder, app.config['INFO_LOG'])
  193. info_file_handler = logging.handlers.RotatingFileHandler(
  194. info_log,
  195. maxBytes=100000,
  196. backupCount=10
  197. )
  198. info_file_handler.setLevel(logging.INFO)
  199. info_file_handler.setFormatter(formatter)
  200. app.logger.addHandler(info_file_handler)
  201. error_log = os.path.join(logs_folder, app.config['ERROR_LOG'])
  202. error_file_handler = logging.handlers.RotatingFileHandler(
  203. error_log,
  204. maxBytes=100000,
  205. backupCount=10
  206. )
  207. error_file_handler.setLevel(logging.ERROR)
  208. error_file_handler.setFormatter(formatter)
  209. app.logger.addHandler(error_file_handler)
  210. if app.config["SEND_LOGS"]:
  211. mail_handler = \
  212. SMTPHandler(
  213. app.config['MAIL_SERVER'],
  214. app.config['MAIL_DEFAULT_SENDER'],
  215. app.config['ADMINS'],
  216. 'application error, no admins specified',
  217. (app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD'])
  218. )
  219. mail_handler.setLevel(logging.ERROR)
  220. mail_handler.setFormatter(formatter)
  221. app.logger.addHandler(mail_handler)
  222. if app.config["SQLALCHEMY_ECHO"]:
  223. # Ref: http://stackoverflow.com/a/8428546
  224. @event.listens_for(Engine, "before_cursor_execute")
  225. def before_cursor_execute(conn, cursor, statement,
  226. parameters, context, executemany):
  227. conn.info.setdefault('query_start_time', []).append(time.time())
  228. @event.listens_for(Engine, "after_cursor_execute")
  229. def after_cursor_execute(conn, cursor, statement,
  230. parameters, context, executemany):
  231. total = time.time() - conn.info['query_start_time'].pop(-1)
  232. app.logger.debug("Total Time: %f", total)