changeset 772:3885421c29a3

admin: Make the whole FoodTruck site into a blueprint. This makes it possible to use an app factory, which makes it easier to write unit tests.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 03 Jul 2016 07:54:54 -0700
parents 673979a5d548
children 87f1e79d3fbe
files foodtruck/bcryptfallback.py foodtruck/blueprint.py foodtruck/main.py foodtruck/pubutil.py foodtruck/views/create.py foodtruck/views/dashboard.py foodtruck/views/edit.py foodtruck/views/menu.py foodtruck/views/preview.py foodtruck/views/publish.py foodtruck/views/sources.py foodtruck/web.py
diffstat 12 files changed, 271 insertions(+), 227 deletions(-) [+]
line wrap: on
line diff
--- a/foodtruck/bcryptfallback.py	Sun Jul 03 07:53:17 2016 -0700
+++ b/foodtruck/bcryptfallback.py	Sun Jul 03 07:54:54 2016 -0700
@@ -37,6 +37,9 @@
             self.generate_password_hash = generate_password_hash
             self.check_password_hash = check_password_hash
 
+        def init_app(self, app):
+            pass
+
     Bcrypt = SHA512Fallback
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foodtruck/blueprint.py	Sun Jul 03 07:54:54 2016 -0700
@@ -0,0 +1,152 @@
+import os
+import os.path
+import time
+import logging
+from flask import Blueprint, current_app, g, request, render_template
+from .configuration import (
+        FoodTruckConfigNotFoundError, get_foodtruck_config)
+from .sites import FoodTruckSites, InvalidSiteError
+
+
+logger = logging.getLogger(__name__)
+
+
+# Prepare the Login extension.
+from flask.ext.login import LoginManager, UserMixin
+
+
+class User(UserMixin):
+    def __init__(self, uid, pwd):
+        self.id = uid
+        self.password = pwd
+
+
+def load_user(user_id):
+    admin_id = g.config.get('security/username')
+    if admin_id == user_id:
+        admin_pwd = g.config.get('security/password')
+        return User(admin_id, admin_pwd)
+    return None
+
+
+login_manager = LoginManager()
+login_manager.login_view = 'login'
+login_manager.user_loader(load_user)
+
+
+def record_login_manager(state):
+    if state.app.secret_key == 'temp-key':
+        def _handler():
+            raise FoodTruckConfigNotFoundError()
+
+        logger.debug("No secret key found, disabling website login.")
+        login_manager.unauthorized_handler(_handler)
+        login_manager.login_view = None
+
+
+# Setup Bcrypt.
+from foodtruck.bcryptfallback import Bcrypt
+bcrypt_ext = Bcrypt()
+
+
+def record_bcrypt(state):
+    if (getattr(Bcrypt, 'is_fallback_bcrypt', None) is True and
+            not state.app.config.get('FOODTRUCK_CMDLINE_MODE', False)):
+        raise Exception(
+                "You're running FoodTruck outside of `chef`, and will need to "
+                "install Flask-Bcrypt to get more proper security.")
+
+
+# Create the FoodTruck blueprint.
+foodtruck_bp = Blueprint(
+        'FoodTruck', __name__,
+        template_folder='templates',
+        static_folder='static')
+
+foodtruck_bp.record(record_login_manager)
+foodtruck_bp.record(record_bcrypt)
+
+
+def after_this_request(f):
+    if not hasattr(g, 'after_request_callbacks'):
+        g.after_request_callbacks = []
+    g.after_request_callbacks.append(f)
+    return f
+
+
+class LazySomething(object):
+    def __init__(self, factory):
+        self._factory = factory
+        self._something = None
+
+    def __getattr__(self, name):
+        if self._something is not None:
+            return getattr(self._something, name)
+
+        self._something = self._factory()
+        return getattr(self._something, name)
+
+
+@foodtruck_bp.before_request
+def _setup_foodtruck_globals():
+    def _get_config():
+        admin_root = current_app.config['FOODTRUCK_ROOT']
+        procedural_config = current_app.config['FOODTRUCK_PROCEDURAL_CONFIG']
+        return get_foodtruck_config(admin_root, procedural_config)
+
+    def _get_sites():
+        names = g.config.get('sites')
+        if not names or not isinstance(names, dict):
+            raise InvalidSiteError(
+                    "No sites are defined in the configuration file.")
+
+        current = request.cookies.get('foodtruck_site_name')
+        if current is not None and current not in names:
+            current = None
+        if current is None:
+            current = next(iter(names.keys()))
+        s = FoodTruckSites(g.config, current)
+        return s
+
+    def _get_current_site():
+        return g.sites.get()
+
+    g.config = LazySomething(_get_config)
+    g.sites = LazySomething(_get_sites)
+    g.site = LazySomething(_get_current_site)
+
+
+@foodtruck_bp.after_request
+def _call_after_request_callbacks(response):
+    for callback in getattr(g, 'after_request_callbacks', ()):
+        callback(response)
+    return response
+
+
+@foodtruck_bp.errorhandler
+def _on_error(ex):
+    logging.exception(ex)
+
+
+@foodtruck_bp.app_template_filter('iso8601')
+def timestamp_to_iso8601(t):
+    t = time.localtime(t)
+    return time.strftime('%Y-%m-%dT%H:%M:%SZ', t)
+
+
+@foodtruck_bp.app_template_filter('datetime')
+def timestamp_to_datetime(t, fmt=None):
+    fmt = fmt or '%x'
+    t = time.localtime(t)
+    return time.strftime(fmt, t)
+
+
+import foodtruck.views.create  # NOQA
+import foodtruck.views.dashboard  # NOQA
+import foodtruck.views.edit  # NOQA
+import foodtruck.views.menu  # NOQA
+import foodtruck.views.preview  # NOQA
+import foodtruck.views.publish  # NOQA
+import foodtruck.views.sources  # NOQA
+
+
--- a/foodtruck/main.py	Sun Jul 03 07:53:17 2016 -0700
+++ b/foodtruck/main.py	Sun Jul 03 07:54:54 2016 -0700
@@ -9,8 +9,9 @@
         import foodtruck.settings
         foodtruck.settings.DEBUG = debug
 
-    from .web import app
+    from .web import create_foodtruck_app
     try:
+        app = create_foodtruck_app()
         app.run(host=host, port=port, debug=debug, threaded=True)
     except SystemExit:
         # This is needed for Werkzeug's code reloader to be able to correctly
--- a/foodtruck/pubutil.py	Sun Jul 03 07:53:17 2016 -0700
+++ b/foodtruck/pubutil.py	Sun Jul 03 07:54:54 2016 -0700
@@ -4,7 +4,7 @@
 import errno
 import signal
 import logging
-from .web import app
+from .blueprint import foodtruck_bp
 
 
 logger = logging.getLogger(__name__)
@@ -12,20 +12,29 @@
 server_shutdown = False
 
 
-def _shutdown_server_and_raise_sigint():
-    if not app.debug or os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
+def _shutdown_server_and_raise_sigint(is_app_debug):
+    if (not is_app_debug or
+            os.environ.get('WERKZEUG_RUN_MAIN') == 'true'):
         # This is needed when hitting CTRL+C to shutdown the Werkzeug server,
         # otherwise SSE generators will keep it alive.
         logger.debug("Shutting down SSE generators...")
+        logger.flush()
         global server_shutdown
         server_shutdown = True
     raise KeyboardInterrupt()
 
 
-if app.config.get('FOODTRUCK_CMDLINE_MODE', False):
-    # Make sure CTRL+C works correctly.
-    signal.signal(signal.SIGINT,
-                  lambda *args: _shutdown_server_and_raise_sigint())
+def record_pipeline(state):
+    if state.app.config.get('FOODTRUCK_CMDLINE_MODE', False):
+        # Make sure CTRL+C works correctly.
+        logger.debug("Adding SIGINT callback for pipeline thread.")
+        signal.signal(
+                signal.SIGINT,
+                lambda *args: _shutdown_server_and_raise_sigint(
+                    state.app.debug))
+
+
+foodtruck_bp.record(record_pipeline)
 
 
 def _read_pid_file(pid_file):
--- a/foodtruck/views/create.py	Sun Jul 03 07:53:17 2016 -0700
+++ b/foodtruck/views/create.py	Sun Jul 03 07:54:54 2016 -0700
@@ -7,14 +7,14 @@
 from piecrust.sources.interfaces import IInteractiveSource
 from piecrust.sources.base import MODE_CREATING
 from piecrust.routing import create_route_metadata
+from ..blueprint import foodtruck_bp
 from ..views import with_menu_context
-from ..web import app
 
 
 logger = logging.getLogger(__name__)
 
 
-@app.route('/write/<source_name>', methods=['GET', 'POST'])
+@foodtruck_bp.route('/write/<source_name>', methods=['GET', 'POST'])
 @login_required
 def write_page(source_name):
     site = g.site.piecrust_app
@@ -57,7 +57,7 @@
             uri = uri[len(uri_root):]
             logger.debug("Redirecting to: %s" % uri)
 
-            return redirect(url_for('edit_page', slug=uri))
+            return redirect(url_for('.edit_page', slug=uri))
 
         abort(400)
 
@@ -76,7 +76,7 @@
     data = {}
     data['is_new_page'] = True
     data['source_name'] = source.name
-    data['url_postback'] = url_for('write_page', source_name=source.name)
+    data['url_postback'] = url_for('.write_page', source_name=source.name)
     data['fields'] = []
     for f in source.getInteractiveFields():
         data['fields'].append({
--- a/foodtruck/views/dashboard.py	Sun Jul 03 07:53:17 2016 -0700
+++ b/foodtruck/views/dashboard.py	Sun Jul 03 07:54:54 2016 -0700
@@ -2,21 +2,21 @@
 import os.path
 import logging
 from flask import (
-        g, request,
+        current_app, g, request,
         render_template, url_for, redirect)
 from flask.ext.login import login_user, logout_user, login_required
 from piecrust.configuration import parse_config_header
 from piecrust.rendering import QualifiedPage
 from piecrust.uriutil import split_uri
 from ..textutil import text_preview
+from ..blueprint import foodtruck_bp, load_user, after_this_request
 from ..views import with_menu_context
-from ..web import app, load_user, after_this_request
 
 
 logger = logging.getLogger(__name__)
 
 
-@app.route('/')
+@foodtruck_bp.route('/')
 @login_required
 def index():
     data = {}
@@ -29,7 +29,7 @@
         facs = source.getPageFactories()
         src_data = {
                 'name': source.name,
-                'list_url': url_for('list_source', source_name=source.name),
+                'list_url': url_for('.list_source', source_name=source.name),
                 'page_count': len(facs)}
         data['sources'].append(src_data)
 
@@ -57,18 +57,18 @@
 
     data['site_name'] = site.name
     data['site_title'] = site.piecrust_app.config.get('site/title', site.name)
-    data['url_publish'] = url_for('publish')
-    data['url_preview'] = url_for('preview_site_root', sitename=site.name)
+    data['url_publish'] = url_for('.publish')
+    data['url_preview'] = url_for('.preview_site_root', sitename=site.name)
 
     data['sites'] = []
     for s in g.sites.getall():
         data['sites'].append({
             'name': s.name,
             'display_name': s.piecrust_app.config.get('site/title'),
-            'url': url_for('index', site_name=s.name)
+            'url': url_for('.index', site_name=s.name)
             })
     data['needs_switch'] = len(g.config.get('sites')) > 1
-    data['url_switch'] = url_for('switch_site')
+    data['url_switch'] = url_for('.switch_site')
 
     with_menu_context(data)
     return render_template('dashboard.html', **data)
@@ -105,26 +105,26 @@
     return {
             'title': qp.config.get('title'),
             'slug': slug,
-            'url': url_for('edit_page', slug=slug),
+            'url': url_for('.edit_page', slug=slug),
             'text': extract
             }
 
 
 @login_required
-@app.route('/switch_site', methods=['POST'])
+@foodtruck_bp.route('/switch_site', methods=['POST'])
 def switch_site():
     site_name = request.form.get('site_name')
     if not site_name:
-        return redirect(url_for('index'))
+        return redirect(url_for('.index'))
 
     @after_this_request
     def _save_site(resp):
         resp.set_cookie('foodtruck_site_name', site_name)
 
-    return redirect(url_for('index'))
+    return redirect(url_for('.index'))
 
 
-@app.route('/login', methods=['GET', 'POST'])
+@foodtruck_bp.route('/login', methods=['GET', 'POST'])
 def login():
     data = {}
 
@@ -134,10 +134,10 @@
         remember = request.form.get('remember')
 
         user = load_user(username)
-        if user is not None and app.bcrypt:
-            if app.bcrypt.check_password_hash(user.password, password):
+        if user is not None and current_app.bcrypt:
+            if current_app.bcrypt.check_password_hash(user.password, password):
                 login_user(user, remember=bool(remember))
-                return redirect(url_for('index'))
+                return redirect(url_for('.index'))
         data['message'] = (
                 "User '%s' doesn't exist or password is incorrect." %
                 username)
@@ -145,9 +145,9 @@
     return render_template('login.html', **data)
 
 
-@app.route('/logout')
+@foodtruck_bp.route('/logout')
 @login_required
 def logout():
     logout_user()
-    return redirect(url_for('index'))
+    return redirect(url_for('.index'))
 
--- a/foodtruck/views/edit.py	Sun Jul 03 07:53:17 2016 -0700
+++ b/foodtruck/views/edit.py	Sun Jul 03 07:54:54 2016 -0700
@@ -6,15 +6,15 @@
 from piecrust.rendering import (
         PageRenderingContext, render_page)
 from piecrust.serving.util import get_requested_page
+from ..blueprint import foodtruck_bp
 from ..views import with_menu_context
-from ..web import app
 
 
 logger = logging.getLogger(__name__)
 
 
-@app.route('/edit/', defaults={'slug': ''}, methods=['GET', 'POST'])
-@app.route('/edit/<path:slug>', methods=['GET', 'POST'])
+@foodtruck_bp.route('/edit/', defaults={'slug': ''}, methods=['GET', 'POST'])
+@foodtruck_bp.route('/edit/<path:slug>', methods=['GET', 'POST'])
 @login_required
 def edit_page(slug):
     site = g.site
@@ -66,7 +66,7 @@
 def _edit_page_form(page):
     data = {}
     data['is_new_page'] = False
-    data['url_cancel'] = url_for('list_source', source_name=page.source.name)
+    data['url_cancel'] = url_for('.list_source', source_name=page.source.name)
     with open(page.path, 'r', encoding='utf8', newline='') as fp:
         data['page_text'] = fp.read()
     data['is_dos_nl'] = "1" if '\r\n' in data['page_text'] else "0"
--- a/foodtruck/views/menu.py	Sun Jul 03 07:53:17 2016 -0700
+++ b/foodtruck/views/menu.py	Sun Jul 03 07:54:54 2016 -0700
@@ -20,8 +20,8 @@
         elif 'blog' in s.name:
             source_icon = 'filing'
 
-        url_write = url_for('write_page', source_name=s.name)
-        url_listall = url_for('list_source', source_name=s.name)
+        url_write = url_for('.write_page', source_name=s.name)
+        url_listall = url_for('.list_source', source_name=s.name)
 
         ctx = {
                 'url': url_listall,
@@ -35,12 +35,12 @@
         entries.append(ctx)
 
     entries.append({
-        'url': url_for('publish'),
+        'url': url_for('.publish'),
         'title': "Publish",
         'icon': 'upload'})
 
     # entries.append({
-    #     'url': url_for('settings'),
+    #     'url': url_for('.settings'),
     #     'title': "Settings",
     #     'icon': 'gear-b'})
 
@@ -62,7 +62,7 @@
 
     data = {'entries': entries,
             'user': current_user,
-            'url_logout': url_for('logout')}
+            'url_logout': url_for('.logout')}
     return data
 
 
--- a/foodtruck/views/preview.py	Sun Jul 03 07:53:17 2016 -0700
+++ b/foodtruck/views/preview.py	Sun Jul 03 07:54:54 2016 -0700
@@ -1,28 +1,27 @@
 import os.path
-from flask import g, make_response
+from flask import current_app, g, make_response
 from flask.ext.login import login_required
 from piecrust import CACHE_DIR
 from piecrust.app import PieCrustFactory
 from piecrust.serving.server import Server
-from ..web import app
+from ..blueprint import foodtruck_bp
 
 
-@app.route('/site/<sitename>/')
+@foodtruck_bp.route('/site/<sitename>/')
 @login_required
 def preview_site_root(sitename):
     return preview_site(sitename, '/')
 
 
-@app.route('/site/<sitename>/<path:url>')
+@foodtruck_bp.route('/site/<sitename>/<path:url>')
 @login_required
 def preview_site(sitename, url):
     root_dir = g.sites.get_root_dir(sitename)
     appfactory = PieCrustFactory(
             root_dir,
             cache_key='foodtruck',
-            debug=app.debug)
+            debug=current_app.debug)
     server = Server(appfactory,
                     root_url='/site/%s/' % sitename)
     return make_response(server._run_request)
 
-
--- a/foodtruck/views/publish.py	Sun Jul 03 07:53:17 2016 -0700
+++ b/foodtruck/views/publish.py	Sun Jul 03 07:54:54 2016 -0700
@@ -2,15 +2,15 @@
 import logging
 from flask import request, g, url_for, render_template, Response
 from flask.ext.login import login_required
+from ..blueprint import foodtruck_bp
 from ..pubutil import PublishLogReader
 from ..views import with_menu_context
-from ..web import app
 
 
 logger = logging.getLogger(__name__)
 
 
-@app.route('/publish', methods=['GET', 'POST'])
+@foodtruck_bp.route('/publish', methods=['GET', 'POST'])
 @login_required
 def publish():
     if request.method == 'POST':
@@ -28,7 +28,7 @@
         return render_template('error.html', **data)
 
     data = {}
-    data['url_run'] = url_for('publish')
+    data['url_run'] = url_for('.publish')
     data['site_title'] = site.piecrust_app.config.get('site/title', site.name)
     data['targets'] = []
     for tn in sorted(pub_cfg.keys()):
@@ -46,7 +46,7 @@
     return render_template('publish.html', **data)
 
 
-@app.route('/publish-log')
+@foodtruck_bp.route('/publish-log')
 @login_required
 def stream_publish_log():
     pid_path = g.site.publish_pid_file
--- a/foodtruck/views/sources.py	Sun Jul 03 07:53:17 2016 -0700
+++ b/foodtruck/views/sources.py	Sun Jul 03 07:54:54 2016 -0700
@@ -1,13 +1,13 @@
 from flask import g, abort, render_template, url_for
 from flask.ext.login import login_required
 from piecrust.data.paginator import Paginator
+from ..blueprint import foodtruck_bp
 from ..textutil import text_preview, html_to_text
 from ..views import with_menu_context
-from ..web import app
 
 
-@app.route('/list/<source_name>/', defaults={'page_num': 1})
-@app.route('/list/<source_name>/<int:page_num>')
+@foodtruck_bp.route('/list/<source_name>/', defaults={'page_num': 1})
+@foodtruck_bp.route('/list/<source_name>/<int:page_num>')
 @login_required
 def list_source(source_name, page_num):
     site = g.site.piecrust_app
@@ -29,26 +29,26 @@
                 'tags': p.get('tags', []),
                 'category': p.get('category'),
                 'source': source_name,
-                'url': url_for('edit_page', slug=p['slug'])
+                'url': url_for('.edit_page', slug=p['slug'])
                 }
         data['pages'].append(page_data)
 
     prev_page_url = None
     if pgn.prev_page_number:
         prev_page_url = url_for(
-                'list_source', source_name=source_name,
+                '.list_source', source_name=source_name,
                 page_num=pgn.prev_page_number)
     next_page_url = None
     if pgn.next_page_number:
         next_page_url = url_for(
-                'list_source', source_name=source_name,
+                '.list_source', source_name=source_name,
                 page_num=pgn.next_page_number)
 
     page_urls = []
     for i in pgn.all_page_numbers(7):
         url = None
         if i != page_num:
-            url = url_for('list_source', source_name=source_name, page_num=i)
+            url = url_for('.list_source', source_name=source_name, page_num=i)
         page_urls.append({'num': i, 'url': url})
 
     data['pagination'] = {
--- a/foodtruck/web.py	Sun Jul 03 07:53:17 2016 -0700
+++ b/foodtruck/web.py	Sun Jul 03 07:54:54 2016 -0700
@@ -1,190 +1,70 @@
-import os
 import os.path
-import time
 import logging
-from flask import Flask, g, request, render_template
+from flask import Flask
 from werkzeug import SharedDataMiddleware
-from .configuration import (
-        FoodTruckConfigNotFoundError, get_foodtruck_config)
-from .sites import FoodTruckSites, InvalidSiteError
+from .blueprint import foodtruck_bp, login_manager, bcrypt_ext
 
 
 logger = logging.getLogger(__name__)
 
-app = Flask(__name__)
-app.config.from_object('foodtruck.settings')
-app.config.from_envvar('FOODTRUCK_SETTINGS', silent=True)
-
-admin_root = app.config.get('FOODTRUCK_ROOT', os.getcwd())
-config_path = os.path.join(admin_root, 'app.cfg')
-
-# If we're being run as the `chef admin run` command, from inside a PieCrust
-# website, do a few things differently.
-_procedural_config = None
-
-if (app.config.get('FOODTRUCK_CMDLINE_MODE', False) and
-        os.path.isfile(os.path.join(admin_root, 'config.yml'))):
-    app.secret_key = os.urandom(22)
-    app.config['LOGIN_DISABLED'] = True
-    _procedural_config = {
-            'sites': {
-                'local': admin_root}
-            }
 
-# Add a special route for the `.well-known` directory.
-app.wsgi_app = SharedDataMiddleware(
-        app.wsgi_app,
-        {'/.well-known': os.path.join(admin_root, '.well-known')})
-
-if os.path.isfile(config_path):
-    app.config.from_pyfile(config_path)
+def create_foodtruck_app(extra_settings=None):
+    app = Flask(__name__)
+    app.config.from_object('foodtruck.settings')
+    app.config.from_envvar('FOODTRUCK_SETTINGS', silent=True)
+    if extra_settings:
+        app.config.from_object(extra_settings)
 
-if app.config['DEBUG']:
-    l = logging.getLogger()
-    l.setLevel(logging.DEBUG)
-
-logger.debug("Using FoodTruck admin root: %s" % admin_root)
-
-
-def after_this_request(f):
-    if not hasattr(g, 'after_request_callbacks'):
-        g.after_request_callbacks = []
-    g.after_request_callbacks.append(f)
-    return f
-
+    admin_root = app.config.setdefault('FOODTRUCK_ROOT', os.getcwd())
+    config_path = os.path.join(admin_root, 'app.cfg')
 
-class LazySomething(object):
-    def __init__(self, factory):
-        self._factory = factory
-        self._something = None
-
-    def __getattr__(self, name):
-        if self._something is not None:
-            return getattr(self._something, name)
-
-        self._something = self._factory()
-        return getattr(self._something, name)
-
-
-@app.before_request
-def _setup_foodtruck_globals():
-    def _get_config():
-        return get_foodtruck_config(admin_root, _procedural_config)
+    # If we're being run as the `chef admin run` command, from inside a PieCrust
+    # website, do a few things differently.
+    app.config['FOODTRUCK_PROCEDURAL_CONFIG'] = None
+    if (app.config.get('FOODTRUCK_CMDLINE_MODE', False) and
+            os.path.isfile(os.path.join(admin_root, 'config.yml'))):
+        app.secret_key = os.urandom(22)
+        app.config['LOGIN_DISABLED'] = True
+        app.config['FOODTRUCK_PROCEDURAL_CONFIG'] = {
+                'sites': {
+                    'local': admin_root}
+                }
 
-    def _get_sites():
-        names = g.config.get('sites')
-        if not names or not isinstance(names, dict):
-            raise InvalidSiteError(
-                    "No sites are defined in the configuration file.")
-
-        current = request.cookies.get('foodtruck_site_name')
-        if current is not None and current not in names:
-            current = None
-        if current is None:
-            current = next(iter(names.keys()))
-        s = FoodTruckSites(g.config, current)
-        return s
+    # Add a special route for the `.well-known` directory.
+    app.wsgi_app = SharedDataMiddleware(
+            app.wsgi_app,
+            {'/.well-known': os.path.join(admin_root, '.well-known')})
 
-    def _get_current_site():
-        return g.sites.get()
-
-    g.config = LazySomething(_get_config)
-    g.sites = LazySomething(_get_sites)
-    g.site = LazySomething(_get_current_site)
-
-
-@app.after_request
-def _call_after_request_callbacks(response):
-    for callback in getattr(g, 'after_request_callbacks', ()):
-        callback(response)
-    return response
-
+    if os.path.isfile(config_path):
+        app.config.from_pyfile(config_path)
 
-if not app.config['DEBUG']:
-    logger.debug("Registering exception handlers.")
-
-    @app.errorhandler(FoodTruckConfigNotFoundError)
-    def _on_config_missing(ex):
-        return render_template('install.html')
-
-    @app.errorhandler(InvalidSiteError)
-    def _on_invalid_site(ex):
-        data = {'error': "The was an error with your configuration file: %s" %
-                str(ex)}
-        return render_template('error.html', **data)
-
-
-@app.errorhandler
-def _on_error(ex):
-    logging.exception(ex)
-
-
-_missing_secret_key = False
+    if app.config['DEBUG']:
+        l = logging.getLogger()
+        l.setLevel(logging.DEBUG)
+    else:
+        @app.errorhandler(FoodTruckConfigNotFoundError)
+        def _on_config_missing(ex):
+            return render_template('install.html')
 
-if not app.secret_key:
-    # If there's no secret key, create a temp one but mark the app as not
-    # correctly installed so it shows the installation information page.
-    app.secret_key = 'temp-key'
-    _missing_secret_key = True
-
-
-from flask.ext.login import LoginManager, UserMixin
-
-
-class User(UserMixin):
-    def __init__(self, uid, pwd):
-        self.id = uid
-        self.password = pwd
-
-
-def load_user(user_id):
-    admin_id = g.config.get('security/username')
-    if admin_id == user_id:
-        admin_pwd = g.config.get('security/password')
-        return User(admin_id, admin_pwd)
-    return None
+        @app.errorhandler(InvalidSiteError)
+        def _on_invalid_site(ex):
+            data = {'error': "The was an error with your configuration file: %s" %
+                    str(ex)}
+            return render_template('error.html', **data)
 
-
-login_manager = LoginManager()
-login_manager.init_app(app)
-login_manager.login_view = 'login'
-login_manager.user_loader(load_user)
-
-if _missing_secret_key:
-    def _handler():
-        raise FoodTruckConfigNotFoundError()
-
-    logger.debug("No secret key found, disabling website.")
-    login_manager.unauthorized_handler(_handler)
-    login_manager.login_view = None
-
+    _missing_secret_key = False
+    if not app.secret_key:
+        # If there's no secret key, create a temp one but mark the app as not
+        # correctly installed so it shows the installation information page.
+        app.secret_key = 'temp-key'
+        _missing_secret_key = True
 
-from foodtruck.bcryptfallback import Bcrypt
-if (getattr(Bcrypt, 'is_fallback_bcrypt', None) is True and
-        not app.config.get('FOODTRUCK_CMDLINE_MODE', False)):
-    raise Exception(
-            "You're running FoodTruck outside of `chef`, and will need to "
-            "install Flask-Bcrypt to get more proper security.")
-app.bcrypt = Bcrypt(app)
-
-
-@app.template_filter('iso8601')
-def timestamp_to_iso8601(t):
-    t = time.localtime(t)
-    return time.strftime('%Y-%m-%dT%H:%M:%SZ', t)
+    # Register extensions and blueprints.
+    login_manager.init_app(app)
+    bcrypt_ext.init_app(app)
+    app.register_blueprint(foodtruck_bp)
 
-@app.template_filter('datetime')
-def timestamp_to_datetime(t, fmt=None):
-    fmt = fmt or '%x'
-    t = time.localtime(t)
-    return time.strftime(fmt, t)
-
+    logger.debug("Created FoodTruck app with admin root: %s" % admin_root)
 
-import foodtruck.views.create  # NOQA
-import foodtruck.views.dashboard  # NOQA
-import foodtruck.views.edit  # NOQA
-import foodtruck.views.menu  # NOQA
-import foodtruck.views.preview  # NOQA
-import foodtruck.views.publish  # NOQA
-import foodtruck.views.sources  # NOQA
+    return app