something something managing too many camera stuff
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.

app.py 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. # encoding: utf-8
  2. import logging
  3. import os
  4. import subprocess
  5. from logging.handlers import RotatingFileHandler
  6. from flask_babelex import gettext, Babel
  7. from flask import Flask, render_template, g, send_from_directory, jsonify, safe_join, request, Response
  8. from flask_bootstrap import Bootstrap
  9. from flask_mail import Mail
  10. from flask_migrate import Migrate
  11. from flask_security import Security
  12. from flask_security.utils import hash_password
  13. from flask_security import signals as FlaskSecuritySignals
  14. from flask_security import confirmable as FSConfirmable
  15. from flask_uploads import configure_uploads, UploadSet, patch_request_class
  16. from flask_thumbnails import Thumbnail
  17. from forms import ExtendedRegisterForm
  18. from models import db, user_datastore, Role, state_str
  19. from utils import InvalidUsage, is_admin, add_user_log, IMAGES
  20. import texttable
  21. from flask_debugtoolbar import DebugToolbarExtension
  22. from dbseed import make_db_seed
  23. from pprint import pprint as pp
  24. import click
  25. from version import VERSION
  26. __VERSION__ = VERSION
  27. try:
  28. from raven.contrib.flask import Sentry
  29. import raven
  30. print(" * Sentry support loaded")
  31. HAS_SENTRY = True
  32. except ImportError:
  33. print(" * No Sentry support")
  34. HAS_SENTRY = False
  35. mail = Mail()
  36. def create_app(config_filename="config.py", app_name=None, register_blueprints=True):
  37. # App configuration
  38. app = Flask(app_name or __name__)
  39. env_cfg = os.getenv("CUSTOM_CONFIG", config_filename)
  40. app.config.from_pyfile(env_cfg)
  41. Bootstrap(app)
  42. app.jinja_env.add_extension("jinja2.ext.with_")
  43. app.jinja_env.add_extension("jinja2.ext.do")
  44. app.jinja_env.globals.update(is_admin=is_admin)
  45. app.jinja_env.filters["state_to_str"] = state_str
  46. if HAS_SENTRY:
  47. app.config["SENTRY_RELEASE"] = raven.fetch_git_sha(os.path.dirname(__file__))
  48. sentry = Sentry(app, dsn=app.config["SENTRY_DSN"]) # noqa: F841
  49. print(" * Sentry support activated")
  50. print(" * Sentry DSN: %s" % app.config["SENTRY_DSN"])
  51. if app.config["DEBUG"] is True:
  52. app.jinja_env.auto_reload = True
  53. app.logger.setLevel(logging.DEBUG)
  54. # Logging
  55. if not app.debug:
  56. formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s " "[in %(pathname)s:%(lineno)d]")
  57. file_handler = RotatingFileHandler("%s/errors_app.log" % os.getcwd(), "a", 1000000, 1)
  58. file_handler.setLevel(logging.DEBUG)
  59. file_handler.setFormatter(formatter)
  60. app.logger.addHandler(file_handler)
  61. mail.init_app(app)
  62. migrate = Migrate(app, db) # noqa: F841
  63. babel = Babel(app) # noqa: F841
  64. toolbar = DebugToolbarExtension(app) # noqa: F841
  65. db.init_app(app)
  66. # Setup Flask-Security
  67. security = Security( # noqa: F841
  68. app, user_datastore, register_form=ExtendedRegisterForm, confirm_register_form=ExtendedRegisterForm
  69. )
  70. @FlaskSecuritySignals.password_reset.connect_via(app)
  71. @FlaskSecuritySignals.password_changed.connect_via(app)
  72. def log_password_reset(sender, user):
  73. if not user:
  74. return
  75. add_user_log(user.id, user.id, "user", "info", "Your password has been changed !")
  76. @FlaskSecuritySignals.reset_password_instructions_sent.connect_via(app)
  77. def log_reset_password_instr(sender, user, token):
  78. if not user:
  79. return
  80. add_user_log(user.id, user.id, "user", "info", "Password reset instructions sent.")
  81. git_version = ""
  82. gitpath = os.path.join(os.getcwd(), ".git")
  83. if os.path.isdir(gitpath):
  84. git_version = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
  85. if git_version:
  86. git_version = git_version.strip().decode("UTF-8")
  87. @babel.localeselector
  88. def get_locale():
  89. # if a user is logged in, use the locale from the user settings
  90. identity = getattr(g, "identity", None)
  91. if identity is not None and identity.id:
  92. return identity.user.locale
  93. # otherwise try to guess the language from the user accept
  94. # header the browser transmits. We support fr/en in this
  95. # example. The best match wins.
  96. return request.accept_languages.best_match(["fr", "en"])
  97. @babel.timezoneselector
  98. def get_timezone():
  99. identity = getattr(g, "identity", None)
  100. if identity is not None and identity.id:
  101. return identity.user.timezone
  102. @app.before_request
  103. def before_request():
  104. cfg = {
  105. "CAMGEAR_VERSION_VER": VERSION,
  106. "CAMGEAR_VERSION_GIT": git_version,
  107. "CAMGEAR_VERSION": "{0} ({1})".format(VERSION, git_version),
  108. }
  109. g.cfg = cfg
  110. @app.errorhandler(InvalidUsage)
  111. def handle_invalid_usage(error):
  112. response = jsonify(error.to_dict())
  113. response.status_code = error.status_code
  114. return response
  115. pictures = UploadSet("pictures", IMAGES)
  116. configure_uploads(app, pictures)
  117. patch_request_class(app, 10 * 1024 * 1024) # 10m limit
  118. thumb = Thumbnail(app) # noqa: F841
  119. if register_blueprints:
  120. from controllers.main import bp_main
  121. app.register_blueprint(bp_main)
  122. from controllers.users import bp_users
  123. app.register_blueprint(bp_users)
  124. from controllers.admin import bp_admin
  125. app.register_blueprint(bp_admin)
  126. from controllers.accessories import bp_accessories
  127. app.register_blueprint(bp_accessories)
  128. from controllers.cameras import bp_cameras
  129. app.register_blueprint(bp_cameras)
  130. from controllers.lenses import bp_lenses
  131. app.register_blueprint(bp_lenses)
  132. from controllers.autocompletion import bp_autocomplete
  133. app.register_blueprint(bp_autocomplete)
  134. @app.route("/uploads/<string:thing>/<path:stuff>", methods=["GET"])
  135. def get_uploads_stuff(thing, stuff):
  136. if app.debug:
  137. directory = safe_join(app.config["UPLOADS_DEFAULT_DEST"], thing)
  138. app.logger.debug(f"serving {stuff} from {directory}")
  139. return send_from_directory(directory, stuff, as_attachment=True)
  140. else:
  141. app.logger.debug(f"X-Accel-Redirect serving {stuff}")
  142. resp = Response("")
  143. resp.headers["Content-Disposition"] = f"attachment; filename={stuff}"
  144. resp.headers["X-Accel-Redirect"] = f"/_protected/media/tracks/{thing}/{stuff}"
  145. return resp
  146. @app.errorhandler(404)
  147. def page_not_found(msg):
  148. pcfg = {
  149. "title": gettext("Whoops, something failed."),
  150. "error": 404,
  151. "message": gettext("Page not found"),
  152. "e": msg,
  153. }
  154. return render_template("error_page.jinja2", pcfg=pcfg), 404
  155. @app.errorhandler(403)
  156. def err_forbidden(msg):
  157. pcfg = {
  158. "title": gettext("Whoops, something failed."),
  159. "error": 403,
  160. "message": gettext("Access forbidden"),
  161. "e": msg,
  162. }
  163. return render_template("error_page.jinja2", pcfg=pcfg), 403
  164. @app.errorhandler(410)
  165. def err_gone(msg):
  166. pcfg = {"title": gettext("Whoops, something failed."), "error": 410, "message": gettext("Gone"), "e": msg}
  167. return render_template("error_page.jinja2", pcfg=pcfg), 410
  168. if not app.debug:
  169. @app.errorhandler(500)
  170. def err_failed(msg):
  171. pcfg = {
  172. "title": gettext("Whoops, something failed."),
  173. "error": 500,
  174. "message": gettext("Something is broken"),
  175. "e": msg,
  176. }
  177. return render_template("error_page.jinja2", pcfg=pcfg), 500
  178. @app.after_request
  179. def set_x_powered_by(response):
  180. response.headers["X-Powered-By"] = "camgear"
  181. return response
  182. # Other commands
  183. @app.cli.command()
  184. def routes():
  185. """Dump all routes of defined app"""
  186. table = texttable.Texttable()
  187. table.set_deco(texttable.Texttable().HEADER)
  188. table.set_cols_dtype(["t", "t", "t"])
  189. table.set_cols_align(["l", "l", "l"])
  190. table.set_cols_width([50, 30, 80])
  191. table.add_rows([["Prefix", "Verb", "URI Pattern"]])
  192. for rule in sorted(app.url_map.iter_rules(), key=lambda x: str(x)):
  193. methods = ",".join(rule.methods)
  194. table.add_row([rule.endpoint, methods, rule])
  195. print(table.draw())
  196. @app.cli.command()
  197. def config():
  198. """Dump config"""
  199. pp(app.config)
  200. @app.cli.command()
  201. def seed():
  202. """Seed database with default content"""
  203. make_db_seed(db)
  204. @app.cli.command()
  205. def createuser():
  206. """Create an user"""
  207. username = click.prompt("Username", type=str)
  208. email = click.prompt("Email", type=str)
  209. password = click.prompt("Password", type=str, hide_input=True, confirmation_prompt=True)
  210. while True:
  211. role = click.prompt("Role [admin/user]", type=str)
  212. if role == "admin" or role == "user":
  213. break
  214. if click.confirm("Do you want to continue ?"):
  215. role = Role.query.filter(Role.name == role).first()
  216. if not role:
  217. raise click.UsageError("Roles not present in database")
  218. u = user_datastore.create_user(name=username, email=email, password=hash_password(password), roles=[role])
  219. db.session.commit()
  220. if FSConfirmable.requires_confirmation(u):
  221. FSConfirmable.send_confirmation_instructions(u)
  222. print("Look at your emails for validation instructions.")
  223. return app