app.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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
  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(management, url_prefix=app.config["ADMIN_URL_PREFIX"])
  66. def configure_extensions(app):
  67. """
  68. Configures the extensions
  69. """
  70. # Flask-Plugins
  71. plugin_manager.init_app(app)
  72. # Flask-SQLAlchemy
  73. db.init_app(app)
  74. # Flask-Migrate
  75. migrate.init_app(app, db)
  76. # Flask-Mail
  77. mail.init_app(app)
  78. # Flask-Cache
  79. cache.init_app(app)
  80. # Flask-Debugtoolbar
  81. debugtoolbar.init_app(app)
  82. # Flask-Themes
  83. themes.init_themes(app, app_identifier="flaskbb")
  84. # Flask-And-Redis
  85. redis_store.init_app(app)
  86. # Flask-WhooshAlchemy
  87. with app.app_context():
  88. whoosh_index(app, Post)
  89. whoosh_index(app, Topic)
  90. whoosh_index(app, Forum)
  91. whoosh_index(app, Category)
  92. whoosh_index(app, User)
  93. # Flask-Login
  94. login_manager.login_view = app.config["LOGIN_VIEW"]
  95. login_manager.refresh_view = app.config["REAUTH_VIEW"]
  96. login_manager.anonymous_user = Guest
  97. @login_manager.user_loader
  98. def load_user(id):
  99. """
  100. Loads the user. Required by the `login` extension
  101. """
  102. unread_count = db.session.query(db.func.count(PrivateMessage.id)).\
  103. filter(PrivateMessage.unread == True,
  104. PrivateMessage.user_id == id).subquery()
  105. u = db.session.query(User, unread_count).filter(User.id == id).first()
  106. if u:
  107. user, user.pm_unread = u
  108. return user
  109. else:
  110. return None
  111. login_manager.init_app(app)
  112. def configure_template_filters(app):
  113. """
  114. Configures the template filters
  115. """
  116. app.jinja_env.filters['markup'] = render_markup
  117. app.jinja_env.filters['format_date'] = format_date
  118. app.jinja_env.filters['time_since'] = time_since
  119. app.jinja_env.filters['is_online'] = is_online
  120. app.jinja_env.filters['crop_title'] = crop_title
  121. app.jinja_env.filters['forum_is_unread'] = forum_is_unread
  122. app.jinja_env.filters['topic_is_unread'] = topic_is_unread
  123. # Permission filters
  124. app.jinja_env.filters['edit_post'] = can_edit_post
  125. app.jinja_env.filters['delete_post'] = can_delete_post
  126. app.jinja_env.filters['delete_topic'] = can_delete_topic
  127. app.jinja_env.filters['post_reply'] = can_post_reply
  128. app.jinja_env.filters['post_topic'] = can_post_topic
  129. # Moderator permission filters
  130. app.jinja_env.filters['is_admin'] = is_admin
  131. app.jinja_env.filters['is_moderator'] = is_moderator
  132. app.jinja_env.filters['is_admin_or_moderator'] = is_admin_or_moderator
  133. app.jinja_env.filters['can_moderate'] = can_moderate
  134. app.jinja_env.filters['can_edit_user'] = can_edit_user
  135. app.jinja_env.filters['can_ban_user'] = can_ban_user
  136. def configure_context_processors(app):
  137. """
  138. Configures the context processors
  139. """
  140. @app.context_processor
  141. def inject_flaskbb_config():
  142. """
  143. Injects the ``flaskbb_config`` config variable into the templates.
  144. """
  145. return dict(flaskbb_config=flaskbb_config)
  146. def configure_before_handlers(app):
  147. """
  148. Configures the before request handlers
  149. """
  150. @app.before_request
  151. def update_lastseen():
  152. """
  153. Updates `lastseen` before every reguest if the user is authenticated
  154. """
  155. if current_user.is_authenticated():
  156. current_user.lastseen = datetime.datetime.utcnow()
  157. db.session.add(current_user)
  158. db.session.commit()
  159. if app.config["REDIS_ENABLED"]:
  160. @app.before_request
  161. def mark_current_user_online():
  162. if current_user.is_authenticated():
  163. mark_online(current_user.username)
  164. else:
  165. mark_online(request.remote_addr, guest=True)
  166. def configure_errorhandlers(app):
  167. """
  168. Configures the error handlers
  169. """
  170. @app.errorhandler(403)
  171. def forbidden_page(error):
  172. return render_template("errors/forbidden_page.html"), 403
  173. @app.errorhandler(404)
  174. def page_not_found(error):
  175. return render_template("errors/page_not_found.html"), 404
  176. @app.errorhandler(500)
  177. def server_error_page(error):
  178. return render_template("errors/server_error.html"), 500
  179. def configure_logging(app):
  180. """
  181. Configures logging.
  182. """
  183. logs_folder = os.path.join(app.root_path, os.pardir, "logs")
  184. from logging.handlers import SMTPHandler
  185. formatter = logging.Formatter(
  186. '%(asctime)s %(levelname)s: %(message)s '
  187. '[in %(pathname)s:%(lineno)d]')
  188. info_log = os.path.join(logs_folder, app.config['INFO_LOG'])
  189. info_file_handler = logging.handlers.RotatingFileHandler(
  190. info_log,
  191. maxBytes=100000,
  192. backupCount=10
  193. )
  194. info_file_handler.setLevel(logging.INFO)
  195. info_file_handler.setFormatter(formatter)
  196. app.logger.addHandler(info_file_handler)
  197. error_log = os.path.join(logs_folder, app.config['ERROR_LOG'])
  198. error_file_handler = logging.handlers.RotatingFileHandler(
  199. error_log,
  200. maxBytes=100000,
  201. backupCount=10
  202. )
  203. error_file_handler.setLevel(logging.ERROR)
  204. error_file_handler.setFormatter(formatter)
  205. app.logger.addHandler(error_file_handler)
  206. if app.config["SEND_LOGS"]:
  207. mail_handler = \
  208. SMTPHandler(app.config['MAIL_SERVER'],
  209. app.config['MAIL_DEFAULT_SENDER'],
  210. app.config['ADMINS'],
  211. 'application error, no admins specified',
  212. (
  213. app.config['MAIL_USERNAME'],
  214. app.config['MAIL_PASSWORD'],
  215. ))
  216. mail_handler.setLevel(logging.ERROR)
  217. mail_handler.setFormatter(formatter)
  218. app.logger.addHandler(mail_handler)
  219. if app.config["SQLALCHEMY_ECHO"]:
  220. # Ref: http://stackoverflow.com/a/8428546
  221. @event.listens_for(Engine, "before_cursor_execute")
  222. def before_cursor_execute(conn, cursor, statement,
  223. parameters, context, executemany):
  224. context._query_start_time = time.time()
  225. @event.listens_for(Engine, "after_cursor_execute")
  226. def after_cursor_execute(conn, cursor, statement,
  227. parameters, context, executemany):
  228. total = time.time() - context._query_start_time
  229. # Modification for StackOverflow: times in milliseconds
  230. app.logger.debug("Total Time: %.02fms" % (total*1000))