Passed
Push — master ( 956510...210001 )
by Jochen
04:10
created

byceps/application.py (1 issue)

1
"""
2
byceps.application
3
~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2006-2020 Jochen Kupperschmidt
6
:License: Modified BSD, see LICENSE for details.
7
"""
8
9
from importlib import import_module
10
from pathlib import Path
11
from typing import Any, Dict, Iterator, Optional, Tuple, Union
12
13
from flask import Flask, redirect
14
import jinja2
15
16
from . import config, config_defaults
17
from .database import db
18
from . import email
19
from .redis import redis
20
from .util.framework.blueprint import register_blueprint
21
from .util.l10n import set_locale
22
from .util import templatefilters
23
from .util.templating import SiteTemplateOverridesLoader
24
25
26
BlueprintReg = Tuple[str, Optional[str]]
27
28
29
def create_app(
30
    config_filename: Union[Path, str],
31
    config_overrides: Optional[Dict[str, Any]] = None,
32
) -> Flask:
33
    """Create the actual Flask application."""
34
    app = Flask(__name__)
35
36
    app.config.from_object(config_defaults)
37
    app.config.from_pyfile(str(config_filename))
38
    if config_overrides is not None:
39
        app.config.from_mapping(config_overrides)
40
41
    # Throw an exception when an undefined name is referenced in a template.
42
    app.jinja_env.undefined = jinja2.StrictUndefined
43
44
    # Set the locale.
45
    set_locale(app.config['LOCALE'])  # Fail if not configured.
46
47
    # Initialize database.
48
    db.init_app(app)
49
50
    # Initialize Redis connection.
51
    redis.init_app(app)
52
53
    email.init_app(app)
54
55
    config.init_app(app)
56
57
    _register_blueprints(app)
58
59
    templatefilters.register(app)
60
61
    _add_static_file_url_rules(app)
62
63
    return app
64
65
66
def _register_blueprints(app: Flask) -> None:
67
    """Register blueprints depending on the configuration."""
68
    for name, url_prefix in _get_blueprints(app):
69
        register_blueprint(app, name, url_prefix)
70
71
72
def _get_blueprints(app: Flask) -> Iterator[BlueprintReg]:
73
    """Yield blueprints to register on the application."""
74
    yield from _get_blueprints_common()
75
76
    current_mode = config.get_site_mode(app)
77
    if current_mode.is_public():
78
        yield from _get_blueprints_site()
79
    elif current_mode.is_admin():
80
        yield from _get_blueprints_admin()
81
82
    yield from _get_blueprints_api()
83
84
    yield from _get_blueprints_health()
85
86
    if app.config['METRICS_ENABLED']:
87
        yield from _get_blueprints_metrics()
88
89
    if app.debug:
90
        yield from _get_blueprints_debug()
91
92
93
def _get_blueprints_common() -> Iterator[BlueprintReg]:
94
    yield from [
95
        ('authentication',              '/authentication'           ),
96
        ('authorization',               None                        ),
97
        ('core',                        '/core'                     ),
98
        ('user',                        None                        ),
99
        ('user.avatar',                 '/users'                    ),
100
        ('user.creation',               '/users'                    ),
101
        ('user.current',                '/users'                    ),
102
        ('user.email_address',          '/users/email_address'      ),
103
    ]
104
105
106
def _get_blueprints_site() -> Iterator[BlueprintReg]:
107
    yield from [
108
        ('attendance',                  '/attendance'               ),
109
        ('board',                       '/board'                    ),
110
        ('consent',                     '/consent'                  ),
111
        ('news',                        '/news'                     ),
112
        ('newsletter',                  '/newsletter'               ),
113
        ('orga_team',                   '/orgas'                    ),
114
        ('party',                       None                        ),
115
        ('seating',                     '/seating'                  ),
116
        ('shop.order',                  '/shop'                     ),
117
        ('shop.orders',                 '/shop/orders'              ),
118
        ('snippet',                     None                        ),
119
        ('terms',                       '/terms'                    ),
120
        ('ticketing',                   '/tickets'                  ),
121
        ('user.profile',                '/users'                    ),
122
        ('user_badge',                  '/user_badges'              ),
123
        ('user_group',                  '/user_groups'              ),
124
        ('user_message',                '/user_messages'            ),
125
    ]
126
127
128
def _get_blueprints_admin() -> Iterator[BlueprintReg]:
129
    yield from [
130
        ('admin.attendance',            '/admin/attendance'         ),
131
        ('admin.authorization',         '/admin/authorization'      ),
132
        ('admin.board',                 '/admin/board'              ),
133
        ('admin.brand',                 '/admin/brands'             ),
134
        ('admin.consent',               '/admin/consent'            ),
135
        ('admin.core',                  None                        ),
136
        ('admin.dashboard',             '/admin/dashboard'          ),
137
        ('admin.email',                 '/admin/email'              ),
138
        ('admin.news',                  '/admin/news'               ),
139
        ('admin.newsletter',            '/admin/newsletter'         ),
140
        ('admin.jobs',                  '/admin/jobs'               ),
141
        ('admin.orga',                  '/admin/orgas'              ),
142
        ('admin.orga_presence',         '/admin/presence'           ),
143
        ('admin.orga_team',             '/admin/orga_teams'         ),
144
        ('admin.party',                 '/admin/parties'            ),
145
        ('admin.seating',               '/admin/seating'            ),
146
        ('admin.shop',                  None                        ),
147
        ('admin.shop.article',          '/admin/shop/articles'      ),
148
        ('admin.shop.email',            '/admin/shop/email'         ),
149
        ('admin.shop.order',            '/admin/shop/orders'        ),
150
        ('admin.shop.shipping',         '/admin/shop/shipping'      ),
151
        ('admin.shop.shop',             '/admin/shop/shop'          ),
152
        ('admin.site',                  '/admin/sites'              ),
153
        ('admin.snippet',               '/admin/snippets'           ),
154
        ('admin.terms',                 '/admin/terms'              ),
155
        ('admin.ticketing',             '/admin/ticketing'          ),
156
        ('admin.ticketing.checkin',     '/admin/ticketing/checkin'  ),
157
        ('admin.tourney',               '/admin/tourney'            ),
158
        ('admin.user',                  '/admin/users'              ),
159
        ('admin.user_badge',            '/admin/user_badges'        ),
160
    ]
161
162
163
def _get_blueprints_api() -> Iterator[BlueprintReg]:
164
    yield from [
165
        ('api.attendance',              '/api/attendances'          ),
166
        ('api.snippet',                 '/api/snippets'             ),
167
        ('api.tourney.avatar',          '/api/tourney/avatars'      ),
168
        ('api.tourney.match.comments',  '/api/tourney'              ),
169
        ('api.user',                    '/api/users'                ),
170
        ('api.user_badge',              '/api/user_badges'          ),
171
    ]
172
173
174
def _get_blueprints_health() -> Iterator[BlueprintReg]:
175
    yield from [
176
        ('healthcheck',                 '/health'                   ),
177
    ]
178
179
180
def _get_blueprints_metrics() -> Iterator[BlueprintReg]:
181
    yield from [
182
        ('metrics',                     '/metrics'                  ),
183
    ]
184
185
186
def _get_blueprints_debug() -> Iterator[BlueprintReg]:
187
    yield from [
188
        ('style_guide',                 '/style_guide'              ),
189
    ]
190
191
192
def _add_static_file_url_rules(app: Flask) -> None:
193
    """Add URL rules to for static files."""
194
    for rule_prefix, endpoint in [
195
        ('/global', 'global_file'),
196
        ('/brand', 'brand_file'),
197
        ('/party', 'party_file'),
198
        ('/site', 'site_file'),
199
    ]:
200
        rule = rule_prefix + '/<path:filename>'
201
        app.add_url_rule(
202
            rule, endpoint=endpoint, methods=['GET'], build_only=True
203
        )
204
205
206
def init_app(app: Flask) -> None:
207
    """Initialize the application after is has been created."""
208
    with app.app_context():
209
        _set_url_root_path(app)
210
211
        site_mode = config.get_site_mode()
212
        if site_mode.is_public():
213
            # Incorporate site-specific template overrides.
214
            app.jinja_loader = SiteTemplateOverridesLoader()
215
216
            site_id = config.get_current_site_id()
217
218
            # Import site-specific code.
219
            _load_site_extension(app, site_id)
220
        elif site_mode.is_admin() and app.config['RQ_DASHBOARD_ENABLED']:
221
            import rq_dashboard
222
223
            app.register_blueprint(
224
                rq_dashboard.blueprint, url_prefix='/admin/rq'
225
            )
226
227
228
def _set_url_root_path(app: Flask) -> None:
229
    """Set an optional URL path to redirect to from the root URL path (`/`).
230
231
    Important: Don't specify the target with a leading slash unless you
232
    really mean the root of the host.
233
    """
234
    target_url = app.config['ROOT_REDIRECT_TARGET']
235
    if target_url is None:
236
        return
237
238
    status_code = app.config['ROOT_REDIRECT_STATUS_CODE']
239
240
    def _redirect():
241
        return redirect(target_url, status_code)
242
243
    app.add_url_rule('/', endpoint='root', view_func=_redirect)
244
245
246
def _load_site_extension(app: Flask, site_id: str) -> None:
1 ignored issue
show
This code seems to be duplicated in your project.
Loading history...
247
    """Import and call custom extension code from the site's package, if
248
    available.
249
250
    If a site package contains a module named `extension` and it
251
    contains a top-level callable named `template_context_processor`,
252
    then that callable is registered with the application as a template
253
    context processor.
254
    """
255
    module_name = f'sites.{site_id}.extension'
256
    try:
257
        module = import_module(module_name)
258
    except ModuleNotFoundError:
259
        # No extension module found in site package.
260
        return
261
262
    context_processor = getattr(module, 'template_context_processor', None)
263
    if context_processor is None:
264
        # Context processor not found in module.
265
        return
266
267
    if not callable(context_processor):
268
        # Context processor object is not callable.
269
        return
270
271
    app.context_processor(context_processor)
272