comparison foodtruck/web.py @ 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 e67da1f7293b
children ba0a6bd5e913
comparison
equal deleted inserted replaced
771:673979a5d548 772:3885421c29a3
1 import os
2 import os.path 1 import os.path
3 import time
4 import logging 2 import logging
5 from flask import Flask, g, request, render_template 3 from flask import Flask
6 from werkzeug import SharedDataMiddleware 4 from werkzeug import SharedDataMiddleware
7 from .configuration import ( 5 from .blueprint import foodtruck_bp, login_manager, bcrypt_ext
8 FoodTruckConfigNotFoundError, get_foodtruck_config)
9 from .sites import FoodTruckSites, InvalidSiteError
10 6
11 7
12 logger = logging.getLogger(__name__) 8 logger = logging.getLogger(__name__)
13 9
14 app = Flask(__name__)
15 app.config.from_object('foodtruck.settings')
16 app.config.from_envvar('FOODTRUCK_SETTINGS', silent=True)
17 10
18 admin_root = app.config.get('FOODTRUCK_ROOT', os.getcwd()) 11 def create_foodtruck_app(extra_settings=None):
19 config_path = os.path.join(admin_root, 'app.cfg') 12 app = Flask(__name__)
13 app.config.from_object('foodtruck.settings')
14 app.config.from_envvar('FOODTRUCK_SETTINGS', silent=True)
15 if extra_settings:
16 app.config.from_object(extra_settings)
20 17
21 # If we're being run as the `chef admin run` command, from inside a PieCrust 18 admin_root = app.config.setdefault('FOODTRUCK_ROOT', os.getcwd())
22 # website, do a few things differently. 19 config_path = os.path.join(admin_root, 'app.cfg')
23 _procedural_config = None
24 20
25 if (app.config.get('FOODTRUCK_CMDLINE_MODE', False) and 21 # If we're being run as the `chef admin run` command, from inside a PieCrust
26 os.path.isfile(os.path.join(admin_root, 'config.yml'))): 22 # website, do a few things differently.
27 app.secret_key = os.urandom(22) 23 app.config['FOODTRUCK_PROCEDURAL_CONFIG'] = None
28 app.config['LOGIN_DISABLED'] = True 24 if (app.config.get('FOODTRUCK_CMDLINE_MODE', False) and
29 _procedural_config = { 25 os.path.isfile(os.path.join(admin_root, 'config.yml'))):
30 'sites': { 26 app.secret_key = os.urandom(22)
31 'local': admin_root} 27 app.config['LOGIN_DISABLED'] = True
32 } 28 app.config['FOODTRUCK_PROCEDURAL_CONFIG'] = {
29 'sites': {
30 'local': admin_root}
31 }
33 32
34 # Add a special route for the `.well-known` directory. 33 # Add a special route for the `.well-known` directory.
35 app.wsgi_app = SharedDataMiddleware( 34 app.wsgi_app = SharedDataMiddleware(
36 app.wsgi_app, 35 app.wsgi_app,
37 {'/.well-known': os.path.join(admin_root, '.well-known')}) 36 {'/.well-known': os.path.join(admin_root, '.well-known')})
38 37
39 if os.path.isfile(config_path): 38 if os.path.isfile(config_path):
40 app.config.from_pyfile(config_path) 39 app.config.from_pyfile(config_path)
41 40
42 if app.config['DEBUG']: 41 if app.config['DEBUG']:
43 l = logging.getLogger() 42 l = logging.getLogger()
44 l.setLevel(logging.DEBUG) 43 l.setLevel(logging.DEBUG)
44 else:
45 @app.errorhandler(FoodTruckConfigNotFoundError)
46 def _on_config_missing(ex):
47 return render_template('install.html')
45 48
46 logger.debug("Using FoodTruck admin root: %s" % admin_root) 49 @app.errorhandler(InvalidSiteError)
50 def _on_invalid_site(ex):
51 data = {'error': "The was an error with your configuration file: %s" %
52 str(ex)}
53 return render_template('error.html', **data)
47 54
55 _missing_secret_key = False
56 if not app.secret_key:
57 # If there's no secret key, create a temp one but mark the app as not
58 # correctly installed so it shows the installation information page.
59 app.secret_key = 'temp-key'
60 _missing_secret_key = True
48 61
49 def after_this_request(f): 62 # Register extensions and blueprints.
50 if not hasattr(g, 'after_request_callbacks'): 63 login_manager.init_app(app)
51 g.after_request_callbacks = [] 64 bcrypt_ext.init_app(app)
52 g.after_request_callbacks.append(f) 65 app.register_blueprint(foodtruck_bp)
53 return f
54 66
67 logger.debug("Created FoodTruck app with admin root: %s" % admin_root)
55 68
56 class LazySomething(object): 69 return app
57 def __init__(self, factory):
58 self._factory = factory
59 self._something = None
60 70
61 def __getattr__(self, name):
62 if self._something is not None:
63 return getattr(self._something, name)
64
65 self._something = self._factory()
66 return getattr(self._something, name)
67
68
69 @app.before_request
70 def _setup_foodtruck_globals():
71 def _get_config():
72 return get_foodtruck_config(admin_root, _procedural_config)
73
74 def _get_sites():
75 names = g.config.get('sites')
76 if not names or not isinstance(names, dict):
77 raise InvalidSiteError(
78 "No sites are defined in the configuration file.")
79
80 current = request.cookies.get('foodtruck_site_name')
81 if current is not None and current not in names:
82 current = None
83 if current is None:
84 current = next(iter(names.keys()))
85 s = FoodTruckSites(g.config, current)
86 return s
87
88 def _get_current_site():
89 return g.sites.get()
90
91 g.config = LazySomething(_get_config)
92 g.sites = LazySomething(_get_sites)
93 g.site = LazySomething(_get_current_site)
94
95
96 @app.after_request
97 def _call_after_request_callbacks(response):
98 for callback in getattr(g, 'after_request_callbacks', ()):
99 callback(response)
100 return response
101
102
103 if not app.config['DEBUG']:
104 logger.debug("Registering exception handlers.")
105
106 @app.errorhandler(FoodTruckConfigNotFoundError)
107 def _on_config_missing(ex):
108 return render_template('install.html')
109
110 @app.errorhandler(InvalidSiteError)
111 def _on_invalid_site(ex):
112 data = {'error': "The was an error with your configuration file: %s" %
113 str(ex)}
114 return render_template('error.html', **data)
115
116
117 @app.errorhandler
118 def _on_error(ex):
119 logging.exception(ex)
120
121
122 _missing_secret_key = False
123
124 if not app.secret_key:
125 # If there's no secret key, create a temp one but mark the app as not
126 # correctly installed so it shows the installation information page.
127 app.secret_key = 'temp-key'
128 _missing_secret_key = True
129
130
131 from flask.ext.login import LoginManager, UserMixin
132
133
134 class User(UserMixin):
135 def __init__(self, uid, pwd):
136 self.id = uid
137 self.password = pwd
138
139
140 def load_user(user_id):
141 admin_id = g.config.get('security/username')
142 if admin_id == user_id:
143 admin_pwd = g.config.get('security/password')
144 return User(admin_id, admin_pwd)
145 return None
146
147
148 login_manager = LoginManager()
149 login_manager.init_app(app)
150 login_manager.login_view = 'login'
151 login_manager.user_loader(load_user)
152
153 if _missing_secret_key:
154 def _handler():
155 raise FoodTruckConfigNotFoundError()
156
157 logger.debug("No secret key found, disabling website.")
158 login_manager.unauthorized_handler(_handler)
159 login_manager.login_view = None
160
161
162 from foodtruck.bcryptfallback import Bcrypt
163 if (getattr(Bcrypt, 'is_fallback_bcrypt', None) is True and
164 not app.config.get('FOODTRUCK_CMDLINE_MODE', False)):
165 raise Exception(
166 "You're running FoodTruck outside of `chef`, and will need to "
167 "install Flask-Bcrypt to get more proper security.")
168 app.bcrypt = Bcrypt(app)
169
170
171 @app.template_filter('iso8601')
172 def timestamp_to_iso8601(t):
173 t = time.localtime(t)
174 return time.strftime('%Y-%m-%dT%H:%M:%SZ', t)
175
176 @app.template_filter('datetime')
177 def timestamp_to_datetime(t, fmt=None):
178 fmt = fmt or '%x'
179 t = time.localtime(t)
180 return time.strftime(fmt, t)
181
182
183 import foodtruck.views.create # NOQA
184 import foodtruck.views.dashboard # NOQA
185 import foodtruck.views.edit # NOQA
186 import foodtruck.views.menu # NOQA
187 import foodtruck.views.preview # NOQA
188 import foodtruck.views.publish # NOQA
189 import foodtruck.views.sources # NOQA
190