Mercurial > piecrust2
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