app.py 9.1 KB

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