FlaskBB 2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- celery_worker.py +18 -0
- flaskbb/__init__.py +19 -0
- flaskbb/app.py +551 -0
- flaskbb/auth/__init__.py +7 -0
- flaskbb/auth/forms.py +170 -0
- flaskbb/auth/plugins.py +119 -0
- flaskbb/auth/services/__init__.py +56 -0
- flaskbb/auth/services/activation.py +60 -0
- flaskbb/auth/services/authentication.py +188 -0
- flaskbb/auth/services/factories.py +52 -0
- flaskbb/auth/services/password.py +67 -0
- flaskbb/auth/services/reauthentication.py +87 -0
- flaskbb/auth/services/registration.py +261 -0
- flaskbb/auth/views.py +501 -0
- flaskbb/cli/__init__.py +24 -0
- flaskbb/cli/db.py +40 -0
- flaskbb/cli/main.py +705 -0
- flaskbb/cli/plugins.py +214 -0
- flaskbb/cli/themes.py +109 -0
- flaskbb/cli/translations.py +105 -0
- flaskbb/cli/users.py +113 -0
- flaskbb/cli/utils.py +203 -0
- flaskbb/configs/__init__.py +0 -0
- flaskbb/configs/config.cfg.template +281 -0
- flaskbb/configs/default.py +291 -0
- flaskbb/configs/testing.py +60 -0
- flaskbb/core/__init__.py +0 -0
- flaskbb/core/auth/__init__.py +7 -0
- flaskbb/core/auth/activation.py +60 -0
- flaskbb/core/auth/authentication.py +301 -0
- flaskbb/core/auth/password.py +54 -0
- flaskbb/core/auth/registration.py +112 -0
- flaskbb/core/changesets.py +96 -0
- flaskbb/core/exceptions.py +85 -0
- flaskbb/core/tokens.py +142 -0
- flaskbb/core/user/__init__.py +0 -0
- flaskbb/core/user/update.py +93 -0
- flaskbb/deprecation.py +106 -0
- flaskbb/display/__init__.py +0 -0
- flaskbb/display/navigation.py +109 -0
- flaskbb/email.py +89 -0
- flaskbb/exceptions.py +28 -0
- flaskbb/extensions.py +83 -0
- flaskbb/fixtures/__init__.py +0 -0
- flaskbb/fixtures/groups.py +137 -0
- flaskbb/fixtures/settings.py +325 -0
- flaskbb/forum/__init__.py +3 -0
- flaskbb/forum/forms.py +212 -0
- flaskbb/forum/locals.py +59 -0
- flaskbb/forum/models.py +1664 -0
- flaskbb/forum/utils.py +39 -0
- flaskbb/forum/views.py +1327 -0
- flaskbb/management/__init__.py +20 -0
- flaskbb/management/forms.py +516 -0
- flaskbb/management/models.py +150 -0
- flaskbb/management/plugins.py +46 -0
- flaskbb/management/views.py +1480 -0
- flaskbb/markup.py +148 -0
- flaskbb/migrations/201501082314_8ad96e49dc6_init.py +321 -0
- flaskbb/migrations/201503222157_514ca0a3282c_private_messages.py +99 -0
- flaskbb/migrations/201504082225_127be3fb000_added_m2m_forumgroups_table.py +37 -0
- flaskbb/migrations/201606061345_221d918aa9f0_add_user_authentication_infos.py +35 -0
- flaskbb/migrations/201606210939_d9530a529b3f_add_timezone_awareness_for_datetime.py +244 -0
- flaskbb/migrations/201611190919_d87cea4e995d_remove_timezone_info_from_birthday_field.py +38 -0
- flaskbb/migrations/201705041144_933bd7d807c4_add_more_non_nullables.py +192 -0
- flaskbb/migrations/201706300917_881dd22cab94_add_date_modified_to_conversations.py +50 -0
- flaskbb/migrations/201709041519_d0ffadc3ea48_add_hidden_columns.py +127 -0
- flaskbb/migrations/201711180953_7c3fcf8a3335_add_plugin_tables.py +66 -0
- flaskbb/migrations/201802021027_af3f5579c84d_add_cascades.py +483 -0
- flaskbb/migrations/201802282131_232e68a03aa2_change_emoji_shortcodes_to_characters.py +949 -0
- flaskbb/migrations/201803012138_5945d8081a95_remove_conversations.py +55 -0
- flaskbb/migrations/__init__.py +0 -0
- flaskbb/migrations/script.py.mako +24 -0
- flaskbb/plugins/__init__.py +10 -0
- flaskbb/plugins/manager.py +243 -0
- flaskbb/plugins/models.py +139 -0
- flaskbb/plugins/spec.py +1141 -0
- flaskbb/plugins/utils.py +86 -0
- flaskbb/static/app.css +19 -0
- flaskbb/static/app.css.map +1 -0
- flaskbb/static/app.js +2 -0
- flaskbb/static/app.js.map +1 -0
- flaskbb/static/avatar.svg +4 -0
- flaskbb/static/avatar100x100.png +0 -0
- flaskbb/static/avatar150x150.png +0 -0
- flaskbb/static/avatar400x400.png +0 -0
- flaskbb/static/avatar80x80.png +0 -0
- flaskbb/static/fa-regular-400.eot +0 -0
- flaskbb/static/fa-regular-400.svg +801 -0
- flaskbb/static/fa-regular-400.ttf +0 -0
- flaskbb/static/fa-regular-400.woff +0 -0
- flaskbb/static/fa-regular-400.woff2 +0 -0
- flaskbb/static/fa-solid-900.eot +0 -0
- flaskbb/static/fa-solid-900.svg +5034 -0
- flaskbb/static/fa-solid-900.ttf +0 -0
- flaskbb/static/fa-solid-900.woff +0 -0
- flaskbb/static/fa-solid-900.woff2 +0 -0
- flaskbb/static/favicon.ico +0 -0
- flaskbb/static/kuyN6Bh.jpg +0 -0
- flaskbb/static/vendors.js +3 -0
- flaskbb/static/vendors.js.LICENSE.txt +7 -0
- flaskbb/static/vendors.js.map +1 -0
- flaskbb/templates/_macros/form.html +218 -0
- flaskbb/templates/_macros/navigation.html +83 -0
- flaskbb/templates/_macros/pagination.html +109 -0
- flaskbb/templates/_macros/utils.html +11 -0
- flaskbb/templates/_partials/confirm_dialog.html +17 -0
- flaskbb/templates/_partials/editor_help.html +60 -0
- flaskbb/templates/_partials/flashed_messages.html +19 -0
- flaskbb/templates/auth/account_activation.html +22 -0
- flaskbb/templates/auth/forgot_password.html +27 -0
- flaskbb/templates/auth/login.html +35 -0
- flaskbb/templates/auth/reauth.html +22 -0
- flaskbb/templates/auth/register.html +34 -0
- flaskbb/templates/auth/request_account_activation.html +23 -0
- flaskbb/templates/auth/reset_password.html +25 -0
- flaskbb/templates/email/activate_account.html +10 -0
- flaskbb/templates/email/activate_account.txt +11 -0
- flaskbb/templates/email/reset_password.html +9 -0
- flaskbb/templates/email/reset_password.txt +11 -0
- flaskbb/templates/errors/forbidden_page.html +14 -0
- flaskbb/templates/errors/page_not_found.html +14 -0
- flaskbb/templates/errors/server_error.html +13 -0
- flaskbb/templates/errors/too_many_logins.html +14 -0
- flaskbb/templates/forum/_forum_meta.html +11 -0
- flaskbb/templates/forum/_forum_row.html +73 -0
- flaskbb/templates/forum/category.html +16 -0
- flaskbb/templates/forum/category_layout.html +124 -0
- flaskbb/templates/forum/edit_forum.html +109 -0
- flaskbb/templates/forum/forum.html +72 -0
- flaskbb/templates/forum/index.html +32 -0
- flaskbb/templates/forum/memberlist.html +62 -0
- flaskbb/templates/forum/new_post.html +49 -0
- flaskbb/templates/forum/new_topic.html +50 -0
- flaskbb/templates/forum/online_users.html +25 -0
- flaskbb/templates/forum/report_post.html +23 -0
- flaskbb/templates/forum/search_form.html +27 -0
- flaskbb/templates/forum/search_result.html +340 -0
- flaskbb/templates/forum/topic.html +244 -0
- flaskbb/templates/forum/topic_controls.html +119 -0
- flaskbb/templates/forum/topic_horizontal.html +201 -0
- flaskbb/templates/forum/topictracker.html +121 -0
- flaskbb/templates/layout.html +298 -0
- flaskbb/templates/management/banned_users.html +129 -0
- flaskbb/templates/management/category_form.html +46 -0
- flaskbb/templates/management/forum_form.html +51 -0
- flaskbb/templates/management/forums.html +191 -0
- flaskbb/templates/management/group_form.html +62 -0
- flaskbb/templates/management/groups.html +97 -0
- flaskbb/templates/management/management_layout.html +23 -0
- flaskbb/templates/management/overview.html +178 -0
- flaskbb/templates/management/plugins.html +96 -0
- flaskbb/templates/management/reports.html +116 -0
- flaskbb/templates/management/settings.html +74 -0
- flaskbb/templates/management/user_form.html +65 -0
- flaskbb/templates/management/users.html +165 -0
- flaskbb/templates/user/all_posts.html +48 -0
- flaskbb/templates/user/all_topics.html +56 -0
- flaskbb/templates/user/change_email.html +19 -0
- flaskbb/templates/user/change_password.html +19 -0
- flaskbb/templates/user/change_user_details.html +28 -0
- flaskbb/templates/user/general_settings.html +18 -0
- flaskbb/templates/user/profile.html +47 -0
- flaskbb/templates/user/profile_layout.html +124 -0
- flaskbb/templates/user/settings_layout.html +32 -0
- flaskbb/themes/aurora/Makefile +21 -0
- flaskbb/themes/aurora/README.md +41 -0
- flaskbb/themes/aurora/build_emoji_set.py +32 -0
- flaskbb/themes/aurora/info.json +11 -0
- flaskbb/themes/aurora/package-lock.json +4904 -0
- flaskbb/themes/aurora/package.json +60 -0
- flaskbb/themes/aurora/preview.png +0 -0
- flaskbb/themes/aurora/src/app/confirm_modal.js +45 -0
- flaskbb/themes/aurora/src/app/editor.js +142 -0
- flaskbb/themes/aurora/src/app/emoji.js +3515 -0
- flaskbb/themes/aurora/src/app/flaskbb.js +271 -0
- flaskbb/themes/aurora/src/app/utils.js +11 -0
- flaskbb/themes/aurora/src/app.js +26 -0
- flaskbb/themes/aurora/src/assets/avatar.svg +4 -0
- flaskbb/themes/aurora/src/assets/avatar100x100.png +0 -0
- flaskbb/themes/aurora/src/assets/avatar150x150.png +0 -0
- flaskbb/themes/aurora/src/assets/avatar400x400.png +0 -0
- flaskbb/themes/aurora/src/assets/avatar80x80.png +0 -0
- flaskbb/themes/aurora/src/assets/favicon.ico +0 -0
- flaskbb/themes/aurora/src/scss/_button.scss +39 -0
- flaskbb/themes/aurora/src/scss/_category.scss +67 -0
- flaskbb/themes/aurora/src/scss/_forum.scss +56 -0
- flaskbb/themes/aurora/src/scss/_management.scss +205 -0
- flaskbb/themes/aurora/src/scss/_misc.scss +181 -0
- flaskbb/themes/aurora/src/scss/_navigation.scss +130 -0
- flaskbb/themes/aurora/src/scss/_page.scss +51 -0
- flaskbb/themes/aurora/src/scss/_profile.scss +121 -0
- flaskbb/themes/aurora/src/scss/_pygments.scss +65 -0
- flaskbb/themes/aurora/src/scss/_text.scss +43 -0
- flaskbb/themes/aurora/src/scss/_topic.scss +172 -0
- flaskbb/themes/aurora/src/scss/_variables.scss +122 -0
- flaskbb/themes/aurora/src/scss/styles.scss +26 -0
- flaskbb/themes/aurora/tsconfig.json +24 -0
- flaskbb/themes/aurora/webpack.common.js +132 -0
- flaskbb/themes/aurora/webpack.dev.js +7 -0
- flaskbb/themes/aurora/webpack.prod.js +7 -0
- flaskbb/tokens/__init__.py +12 -0
- flaskbb/tokens/serializer.py +93 -0
- flaskbb/tokens/verifiers.py +34 -0
- flaskbb/translations/da/LC_MESSAGES/messages.po +3324 -0
- flaskbb/translations/de/LC_MESSAGES/messages.po +3359 -0
- flaskbb/translations/en/LC_MESSAGES/messages.po +4126 -0
- flaskbb/translations/es/LC_MESSAGES/messages.po +3363 -0
- flaskbb/translations/fr/LC_MESSAGES/messages.po +3398 -0
- flaskbb/translations/nb_NO/LC_MESSAGES/messages.po +3493 -0
- flaskbb/translations/pl/LC_MESSAGES/messages.po +3350 -0
- flaskbb/translations/pt/LC_MESSAGES/messages.po +3488 -0
- flaskbb/translations/pt_BR/LC_MESSAGES/messages.po +3350 -0
- flaskbb/translations/ru/LC_MESSAGES/messages.po +3335 -0
- flaskbb/translations/sv_SE/LC_MESSAGES/messages.po +3347 -0
- flaskbb/translations/ta/LC_MESSAGES/messages.po +3528 -0
- flaskbb/translations/uk/LC_MESSAGES/messages.po +3505 -0
- flaskbb/translations/zh_CN/LC_MESSAGES/messages.po +3325 -0
- flaskbb/translations/zh_TW/LC_MESSAGES/messages.po +3324 -0
- flaskbb/user/__init__.py +19 -0
- flaskbb/user/forms.py +131 -0
- flaskbb/user/models.py +527 -0
- flaskbb/user/plugins.py +87 -0
- flaskbb/user/services/__init__.py +0 -0
- flaskbb/user/services/factories.py +89 -0
- flaskbb/user/services/update.py +88 -0
- flaskbb/user/services/validators.py +107 -0
- flaskbb/user/views.py +235 -0
- flaskbb/utils/__init__.py +3 -0
- flaskbb/utils/alembic.py +79 -0
- flaskbb/utils/database.py +189 -0
- flaskbb/utils/datastructures.py +31 -0
- flaskbb/utils/forms.py +209 -0
- flaskbb/utils/helpers.py +940 -0
- flaskbb/utils/http.py +81 -0
- flaskbb/utils/populate.py +445 -0
- flaskbb/utils/queries.py +154 -0
- flaskbb/utils/requirements.py +352 -0
- flaskbb/utils/search.py +143 -0
- flaskbb/utils/settings.py +55 -0
- flaskbb/utils/translations.py +229 -0
- flaskbb-2.2.0.dist-info/METADATA +120 -0
- flaskbb-2.2.0.dist-info/RECORD +247 -0
- flaskbb-2.2.0.dist-info/WHEEL +4 -0
- flaskbb-2.2.0.dist-info/entry_points.txt +2 -0
- flaskbb-2.2.0.dist-info/licenses/LICENSE +33 -0
- wsgi.py +11 -0
celery_worker.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
flaskbb.celery_worker
|
|
4
|
+
~~~~~~~~~~~~~~~~~~~~~
|
|
5
|
+
|
|
6
|
+
Prepares the celery app for the celery worker.
|
|
7
|
+
To start celery, enter this in the console::
|
|
8
|
+
|
|
9
|
+
celery -A celery_worker.celery --loglevel=info worker
|
|
10
|
+
|
|
11
|
+
:copyright: (c) 2016 by the FlaskBB Team.
|
|
12
|
+
:license: BSD, see LICENSE for more details.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from flaskbb.app import create_app
|
|
16
|
+
from flaskbb.extensions import celery # noqa
|
|
17
|
+
|
|
18
|
+
app = create_app()
|
flaskbb/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
flaskbb
|
|
4
|
+
~~~~~~~
|
|
5
|
+
|
|
6
|
+
FlaskBB is a forum software written in python using the
|
|
7
|
+
microframework Flask.
|
|
8
|
+
|
|
9
|
+
:copyright: (c) 2014 by the FlaskBB Team.
|
|
10
|
+
:license: BSD, see LICENSE for more details.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
__version__ = "2.2.0"
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
from flaskbb.app import create_app # noqa
|
flaskbb/app.py
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
flaskbb.app
|
|
4
|
+
~~~~~~~~~~~
|
|
5
|
+
|
|
6
|
+
manages the app creation and configuration process
|
|
7
|
+
|
|
8
|
+
:copyright: (c) 2014 by the FlaskBB Team.
|
|
9
|
+
:license: BSD, see LICENSE for more details.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import logging.config
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
import warnings
|
|
18
|
+
from datetime import UTC, datetime
|
|
19
|
+
from typing import Any, Callable
|
|
20
|
+
|
|
21
|
+
from celery import Celery
|
|
22
|
+
from flask import Flask, request
|
|
23
|
+
from flask_login import current_user
|
|
24
|
+
from sqlalchemy import event
|
|
25
|
+
from sqlalchemy.engine import Engine
|
|
26
|
+
from sqlalchemy.exc import OperationalError, ProgrammingError
|
|
27
|
+
|
|
28
|
+
# extensions
|
|
29
|
+
from flaskbb.extensions import (
|
|
30
|
+
alembic,
|
|
31
|
+
allows,
|
|
32
|
+
babel,
|
|
33
|
+
cache,
|
|
34
|
+
celery,
|
|
35
|
+
csrf,
|
|
36
|
+
db,
|
|
37
|
+
debugtoolbar,
|
|
38
|
+
limiter,
|
|
39
|
+
login_manager,
|
|
40
|
+
mail,
|
|
41
|
+
pluggy,
|
|
42
|
+
redis_store,
|
|
43
|
+
themes,
|
|
44
|
+
whooshee,
|
|
45
|
+
)
|
|
46
|
+
from flaskbb.plugins import spec
|
|
47
|
+
from flaskbb.plugins.models import PluginRegistry
|
|
48
|
+
from flaskbb.plugins.utils import remove_zombie_plugins_from_db, template_hook
|
|
49
|
+
|
|
50
|
+
# models
|
|
51
|
+
from flaskbb.user.models import Guest, User
|
|
52
|
+
|
|
53
|
+
# various helpers
|
|
54
|
+
from flaskbb.utils.helpers import (
|
|
55
|
+
app_config_from_env,
|
|
56
|
+
crop_title,
|
|
57
|
+
format_date,
|
|
58
|
+
format_datetime,
|
|
59
|
+
format_time,
|
|
60
|
+
forum_is_unread,
|
|
61
|
+
get_alembic_locations,
|
|
62
|
+
get_flaskbb_config,
|
|
63
|
+
is_online,
|
|
64
|
+
mark_online,
|
|
65
|
+
render_template,
|
|
66
|
+
time_since,
|
|
67
|
+
time_utcnow,
|
|
68
|
+
topic_is_unread,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# permission checks (here they are used for the jinja filters)
|
|
72
|
+
from flaskbb.utils.requirements import (
|
|
73
|
+
CanBanUser,
|
|
74
|
+
CanEditUser,
|
|
75
|
+
IsAdmin,
|
|
76
|
+
IsAtleastModerator,
|
|
77
|
+
can_delete_topic,
|
|
78
|
+
can_edit_post,
|
|
79
|
+
can_moderate,
|
|
80
|
+
can_post_reply,
|
|
81
|
+
can_post_topic,
|
|
82
|
+
has_permission,
|
|
83
|
+
permission_with_identity,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# whooshees
|
|
87
|
+
from flaskbb.utils.search import (
|
|
88
|
+
ForumWhoosheer,
|
|
89
|
+
PostWhoosheer,
|
|
90
|
+
TopicWhoosheer,
|
|
91
|
+
UserWhoosheer,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# app specific configurations
|
|
95
|
+
from flaskbb.utils.settings import flaskbb_config
|
|
96
|
+
from flaskbb.utils.translations import FlaskBBDomain
|
|
97
|
+
|
|
98
|
+
from . import markup # noqa
|
|
99
|
+
from .auth import views as auth_views # noqa
|
|
100
|
+
from .deprecation import FlaskBBDeprecation
|
|
101
|
+
from .display.navigation import NavigationContentType
|
|
102
|
+
from .forum import views as forum_views # noqa
|
|
103
|
+
from .management import views as management_views # noqa
|
|
104
|
+
from .user import views as user_views # noqa
|
|
105
|
+
|
|
106
|
+
logger = logging.getLogger(__name__)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def create_app(config: object | None = None, instance_path: str | None = None):
|
|
110
|
+
"""Creates the app.
|
|
111
|
+
|
|
112
|
+
:param instance_path: An alternative instance path for the application.
|
|
113
|
+
By default the folder ``'instance'`` next to the
|
|
114
|
+
package or module is assumed to be the instance
|
|
115
|
+
path.
|
|
116
|
+
See :ref:`Instance Folders <flask:instance-folders>`.
|
|
117
|
+
:param config: The configuration file or object.
|
|
118
|
+
The environment variable is weightet as the heaviest.
|
|
119
|
+
For example, if the config is specified via an file
|
|
120
|
+
and a ENVVAR, it will load the config via the file and
|
|
121
|
+
later overwrite it from the ENVVAR.
|
|
122
|
+
If no config is provided, FlaskBB will try to load the
|
|
123
|
+
config named ``flaskbb.cfg`` from the instance path.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
app = Flask("flaskbb", instance_path=instance_path, instance_relative_config=True)
|
|
127
|
+
|
|
128
|
+
# instance folders are not automatically created by flask
|
|
129
|
+
if not os.path.exists(app.instance_path):
|
|
130
|
+
os.makedirs(app.instance_path)
|
|
131
|
+
|
|
132
|
+
configure_app(app, config)
|
|
133
|
+
configure_celery_app(app, celery)
|
|
134
|
+
configure_extensions(app)
|
|
135
|
+
|
|
136
|
+
load_plugins(app)
|
|
137
|
+
configure_blueprints(app)
|
|
138
|
+
configure_template_filters(app)
|
|
139
|
+
configure_context_processors(app)
|
|
140
|
+
configure_before_handlers(app)
|
|
141
|
+
configure_errorhandlers(app)
|
|
142
|
+
configure_migrations(app)
|
|
143
|
+
configure_translations(app)
|
|
144
|
+
pluggy.hook.flaskbb_additional_setup(app=app, pluggy=pluggy)
|
|
145
|
+
|
|
146
|
+
return app
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def configure_app(app: Flask, config: Any):
|
|
150
|
+
"""Configures FlaskBB."""
|
|
151
|
+
# Use the default config and override it afterwards
|
|
152
|
+
app.config.from_object("flaskbb.configs.default.DefaultConfig")
|
|
153
|
+
config = get_flaskbb_config(app, config)
|
|
154
|
+
# Path
|
|
155
|
+
if isinstance(config, str):
|
|
156
|
+
app.config.from_pyfile(config)
|
|
157
|
+
# Module
|
|
158
|
+
else:
|
|
159
|
+
# try to update the config from the object
|
|
160
|
+
app.config.from_object(config)
|
|
161
|
+
|
|
162
|
+
# Add the location of the config to the config
|
|
163
|
+
app.config["CONFIG_PATH"] = config
|
|
164
|
+
|
|
165
|
+
# Environment
|
|
166
|
+
# Parse the env for FLASKBB_ prefixed env variables and set
|
|
167
|
+
# them on the config object
|
|
168
|
+
app_config_from_env(app, prefix="FLASKBB_")
|
|
169
|
+
|
|
170
|
+
# Migrate Celery 4.x config to Celery 6.x
|
|
171
|
+
old_celery_config = app.config.get_namespace("CELERY_")
|
|
172
|
+
celery_config = {}
|
|
173
|
+
for key, value in old_celery_config.items():
|
|
174
|
+
# config is the new format
|
|
175
|
+
if key != "config":
|
|
176
|
+
config_key = f"CELERY_{key.upper()}"
|
|
177
|
+
celery_config[key] = value
|
|
178
|
+
try:
|
|
179
|
+
del app.config[config_key]
|
|
180
|
+
except KeyError:
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
# merge the new config with the old one
|
|
184
|
+
new_celery_config = app.config.get("CELERY_CONFIG")
|
|
185
|
+
new_celery_config.update(celery_config)
|
|
186
|
+
app.config.update({"CELERY_CONFIG": new_celery_config})
|
|
187
|
+
|
|
188
|
+
# Setting up logging as early as possible
|
|
189
|
+
configure_logging(app)
|
|
190
|
+
|
|
191
|
+
if not isinstance(config, str) and config is not None:
|
|
192
|
+
config_name = "{}.{}".format(config.__module__, config.__name__)
|
|
193
|
+
else:
|
|
194
|
+
config_name = config
|
|
195
|
+
|
|
196
|
+
logger.info("Using config from: {}".format(config_name))
|
|
197
|
+
|
|
198
|
+
deprecation_level = app.config.get("DEPRECATION_LEVEL", "default")
|
|
199
|
+
|
|
200
|
+
# never set the deprecation level during testing, pytest will handle it
|
|
201
|
+
if not app.testing: # pragma: no branch
|
|
202
|
+
warnings.simplefilter(deprecation_level, FlaskBBDeprecation)
|
|
203
|
+
|
|
204
|
+
# Filter Flask-Limiter in-memory warnings when running in debug mode oder test mode
|
|
205
|
+
if app.debug or app.testing:
|
|
206
|
+
warnings.filterwarnings(
|
|
207
|
+
action="ignore",
|
|
208
|
+
message=".*Using the in-memory storage for tracking rate limits.*",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
debug_panels = app.config.setdefault(
|
|
212
|
+
"DEBUG_TB_PANELS",
|
|
213
|
+
[
|
|
214
|
+
"flask_debugtoolbar.panels.versions.VersionDebugPanel",
|
|
215
|
+
"flask_debugtoolbar.panels.timer.TimerDebugPanel",
|
|
216
|
+
"flask_debugtoolbar.panels.headers.HeaderDebugPanel",
|
|
217
|
+
"flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel",
|
|
218
|
+
"flask_debugtoolbar.panels.config_vars.ConfigVarsDebugPanel",
|
|
219
|
+
"flask_debugtoolbar.panels.template.TemplateDebugPanel",
|
|
220
|
+
"flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel",
|
|
221
|
+
"flask_debugtoolbar.panels.logger.LoggingPanel",
|
|
222
|
+
"flask_debugtoolbar.panels.route_list.RouteListDebugPanel",
|
|
223
|
+
"flask_debugtoolbar.panels.profiler.ProfilerDebugPanel",
|
|
224
|
+
],
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if all("WarningsPanel" not in p for p in debug_panels):
|
|
228
|
+
debug_panels.append("flask_debugtoolbar_warnings.WarningsPanel")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def configure_celery_app(app: Flask, celery: Celery):
|
|
232
|
+
"""Configures the celery app."""
|
|
233
|
+
celery.conf.update(app.config.get("CELERY_CONFIG"))
|
|
234
|
+
|
|
235
|
+
TaskBase = celery.Task
|
|
236
|
+
|
|
237
|
+
class ContextTask(TaskBase):
|
|
238
|
+
def __call__(self, *args, **kwargs):
|
|
239
|
+
with app.app_context():
|
|
240
|
+
return TaskBase.__call__(self, *args, **kwargs)
|
|
241
|
+
|
|
242
|
+
celery.Task = ContextTask
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def configure_blueprints(app: Flask):
|
|
246
|
+
pluggy.hook.flaskbb_load_blueprints(app=app)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def configure_extensions(app: Flask):
|
|
250
|
+
"""Configures the extensions."""
|
|
251
|
+
# Flask-Allows
|
|
252
|
+
allows.init_app(app)
|
|
253
|
+
allows.identity_loader(lambda: current_user)
|
|
254
|
+
|
|
255
|
+
# Flask-WTF CSRF
|
|
256
|
+
csrf.init_app(app)
|
|
257
|
+
|
|
258
|
+
# Flask-SQLAlchemy
|
|
259
|
+
db.init_app(app)
|
|
260
|
+
|
|
261
|
+
# Flask-Alembic
|
|
262
|
+
alembic.init_app(app)
|
|
263
|
+
|
|
264
|
+
# Flask-Mail
|
|
265
|
+
mail.init_app(app)
|
|
266
|
+
|
|
267
|
+
# Flask-Cache
|
|
268
|
+
cache.init_app(app)
|
|
269
|
+
|
|
270
|
+
# Flask-Debugtoolbar
|
|
271
|
+
debugtoolbar.init_app(app)
|
|
272
|
+
|
|
273
|
+
# Flask-Themes
|
|
274
|
+
themes.init_themes(app, app_identifier="flaskbb")
|
|
275
|
+
|
|
276
|
+
# Flask-And-Redis
|
|
277
|
+
redis_store.init_app(app)
|
|
278
|
+
|
|
279
|
+
# Flask-Limiter
|
|
280
|
+
limiter.init_app(app)
|
|
281
|
+
|
|
282
|
+
# Flask-Whooshee
|
|
283
|
+
whooshee.init_app(app)
|
|
284
|
+
# not needed for unittests - and it will speed up testing A LOT
|
|
285
|
+
if not app.testing:
|
|
286
|
+
whooshee.register_whoosheer(PostWhoosheer)
|
|
287
|
+
whooshee.register_whoosheer(TopicWhoosheer)
|
|
288
|
+
whooshee.register_whoosheer(ForumWhoosheer)
|
|
289
|
+
whooshee.register_whoosheer(UserWhoosheer)
|
|
290
|
+
|
|
291
|
+
# Flask-Login
|
|
292
|
+
login_manager.login_view = app.config["LOGIN_VIEW"]
|
|
293
|
+
login_manager.refresh_view = app.config["REAUTH_VIEW"]
|
|
294
|
+
login_manager.login_message_category = app.config["LOGIN_MESSAGE_CATEGORY"]
|
|
295
|
+
login_manager.needs_refresh_message_category = app.config[
|
|
296
|
+
"REFRESH_MESSAGE_CATEGORY"
|
|
297
|
+
]
|
|
298
|
+
login_manager.anonymous_user = Guest
|
|
299
|
+
|
|
300
|
+
@login_manager.user_loader
|
|
301
|
+
def load_user(user_id: int):
|
|
302
|
+
"""Loads the user. Required by the `login` extension."""
|
|
303
|
+
user = db.session.execute(
|
|
304
|
+
db.select(User).filter_by(id=user_id)
|
|
305
|
+
).scalar_one_or_none()
|
|
306
|
+
pluggy.hook.flaskbb_current_user(app=app, user=user)
|
|
307
|
+
return user
|
|
308
|
+
|
|
309
|
+
login_manager.init_app(app)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def configure_template_filters(app: Flask):
|
|
313
|
+
"""Configures the template filters."""
|
|
314
|
+
filters: dict[str, Callable[..., Any]] = {}
|
|
315
|
+
|
|
316
|
+
filters["crop_title"] = crop_title
|
|
317
|
+
filters["format_date"] = format_date
|
|
318
|
+
filters["format_time"] = format_time
|
|
319
|
+
filters["format_datetime"] = format_datetime
|
|
320
|
+
filters["forum_is_unread"] = forum_is_unread
|
|
321
|
+
filters["is_online"] = is_online
|
|
322
|
+
filters["time_since"] = time_since
|
|
323
|
+
filters["topic_is_unread"] = topic_is_unread
|
|
324
|
+
|
|
325
|
+
permissions = [
|
|
326
|
+
("is_admin", IsAdmin),
|
|
327
|
+
("is_moderator", IsAtleastModerator),
|
|
328
|
+
("is_admin_or_moderator", IsAtleastModerator),
|
|
329
|
+
("can_edit_user", CanEditUser),
|
|
330
|
+
("can_ban_user", CanBanUser),
|
|
331
|
+
]
|
|
332
|
+
|
|
333
|
+
filters.update(
|
|
334
|
+
(name, permission_with_identity(perm, name=name)) for name, perm in permissions
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
filters["can_moderate"] = can_moderate
|
|
338
|
+
filters["post_reply"] = can_post_reply
|
|
339
|
+
filters["edit_post"] = can_edit_post
|
|
340
|
+
filters["delete_post"] = can_edit_post
|
|
341
|
+
filters["post_topic"] = can_post_topic
|
|
342
|
+
filters["delete_topic"] = can_delete_topic
|
|
343
|
+
filters["has_permission"] = has_permission
|
|
344
|
+
|
|
345
|
+
app.jinja_env.filters.update(filters)
|
|
346
|
+
|
|
347
|
+
jinja_globals: dict[str, Callable[..., Any]] = {}
|
|
348
|
+
jinja_globals["run_hook"] = template_hook
|
|
349
|
+
jinja_globals["NavigationContentType"] = NavigationContentType
|
|
350
|
+
app.jinja_env.globals.update(jinja_globals)
|
|
351
|
+
|
|
352
|
+
pluggy.hook.flaskbb_jinja_directives(app=app)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def configure_context_processors(app: Flask):
|
|
356
|
+
"""Configures the context processors."""
|
|
357
|
+
|
|
358
|
+
@app.context_processor
|
|
359
|
+
def inject_flaskbb_config():
|
|
360
|
+
"""Injects the ``flaskbb_config`` config variable into the
|
|
361
|
+
templates.
|
|
362
|
+
"""
|
|
363
|
+
return dict(flaskbb_config=flaskbb_config, format_date=format_date)
|
|
364
|
+
|
|
365
|
+
@app.context_processor
|
|
366
|
+
def inject_now():
|
|
367
|
+
"""Injects the current time."""
|
|
368
|
+
return dict(now=datetime.now(UTC))
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def configure_before_handlers(app: Flask):
|
|
372
|
+
"""Configures the before request handlers."""
|
|
373
|
+
|
|
374
|
+
@app.before_request
|
|
375
|
+
def update_lastseen():
|
|
376
|
+
"""Updates `lastseen` before every reguest if the user is
|
|
377
|
+
authenticated."""
|
|
378
|
+
if current_user.is_authenticated:
|
|
379
|
+
current_user.lastseen = time_utcnow()
|
|
380
|
+
db.session.add(current_user)
|
|
381
|
+
db.session.commit()
|
|
382
|
+
|
|
383
|
+
if app.config["REDIS_ENABLED"]:
|
|
384
|
+
|
|
385
|
+
@app.before_request
|
|
386
|
+
def mark_current_user_online():
|
|
387
|
+
if current_user.is_authenticated:
|
|
388
|
+
mark_online(current_user.username)
|
|
389
|
+
else:
|
|
390
|
+
mark_online(request.remote_addr, guest=True)
|
|
391
|
+
|
|
392
|
+
pluggy.hook.flaskbb_request_processors(app=app)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def configure_errorhandlers(app: Flask):
|
|
396
|
+
"""Configures the error handlers."""
|
|
397
|
+
|
|
398
|
+
@app.errorhandler(403)
|
|
399
|
+
def forbidden_page(error):
|
|
400
|
+
return render_template("errors/forbidden_page.html"), 403
|
|
401
|
+
|
|
402
|
+
@app.errorhandler(404)
|
|
403
|
+
def page_not_found(error):
|
|
404
|
+
return render_template("errors/page_not_found.html"), 404
|
|
405
|
+
|
|
406
|
+
@app.errorhandler(500)
|
|
407
|
+
def server_error_page(error):
|
|
408
|
+
return render_template("errors/server_error.html"), 500
|
|
409
|
+
|
|
410
|
+
pluggy.hook.flaskbb_errorhandlers(app=app)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def configure_migrations(app: Flask):
|
|
414
|
+
"""Configure migrations."""
|
|
415
|
+
plugin_dirs = pluggy.hook.flaskbb_load_migrations()
|
|
416
|
+
version_locations = get_alembic_locations(plugin_dirs)
|
|
417
|
+
|
|
418
|
+
app.config["ALEMBIC"]["version_locations"] = version_locations
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def configure_translations(app: Flask):
|
|
422
|
+
"""Configure translations."""
|
|
423
|
+
|
|
424
|
+
# we have to initialize the extension after we have loaded the plugins
|
|
425
|
+
# because we of the 'flaskbb_load_translations' hook
|
|
426
|
+
babel.init_app(app=app, default_domain=FlaskBBDomain(app))
|
|
427
|
+
|
|
428
|
+
@babel.localeselector
|
|
429
|
+
def get_locale():
|
|
430
|
+
# if a user is logged in, use the locale from the user settings
|
|
431
|
+
if current_user and current_user.is_authenticated and current_user.language:
|
|
432
|
+
return current_user.language
|
|
433
|
+
# otherwise we will just fallback to the default language
|
|
434
|
+
return flaskbb_config["DEFAULT_LANGUAGE"]
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def configure_logging(app: Flask):
|
|
438
|
+
"""Configures logging."""
|
|
439
|
+
if app.config.get("USE_DEFAULT_LOGGING"):
|
|
440
|
+
configure_default_logging(app)
|
|
441
|
+
|
|
442
|
+
if app.config.get("LOG_CONF_FILE"):
|
|
443
|
+
logging.config.fileConfig(
|
|
444
|
+
app.config["LOG_CONF_FILE"], disable_existing_loggers=False
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
if app.config["SQLALCHEMY_ECHO"]:
|
|
448
|
+
# Ref: http://stackoverflow.com/a/8428546
|
|
449
|
+
@event.listens_for(Engine, "before_cursor_execute")
|
|
450
|
+
def before_cursor_execute(
|
|
451
|
+
conn, cursor, statement, parameters, context, executemany
|
|
452
|
+
):
|
|
453
|
+
conn.info.setdefault("query_start_time", []).append(time.time())
|
|
454
|
+
|
|
455
|
+
@event.listens_for(Engine, "after_cursor_execute")
|
|
456
|
+
def after_cursor_execute(
|
|
457
|
+
conn, cursor, statement, parameters, context, executemany
|
|
458
|
+
):
|
|
459
|
+
total = time.time() - conn.info["query_start_time"].pop(-1)
|
|
460
|
+
app.logger.debug("Total Time: %f", total)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def configure_default_logging(app: Flask):
|
|
464
|
+
# Load default logging config
|
|
465
|
+
logging.config.dictConfig(app.config["LOG_DEFAULT_CONF"])
|
|
466
|
+
|
|
467
|
+
if app.config["SEND_LOGS"]:
|
|
468
|
+
configure_mail_logs(app)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def configure_mail_logs(app: Flask, formatter: logging.Formatter | None = None):
|
|
472
|
+
from logging.handlers import SMTPHandler
|
|
473
|
+
|
|
474
|
+
if formatter is None:
|
|
475
|
+
formatter = logging.Formatter(
|
|
476
|
+
"%(asctime)s %(levelname)-7s %(name)-25s %(message)s"
|
|
477
|
+
)
|
|
478
|
+
mail_handler = SMTPHandler(
|
|
479
|
+
app.config["MAIL_SERVER"],
|
|
480
|
+
app.config["MAIL_DEFAULT_SENDER"],
|
|
481
|
+
app.config["ADMINS"],
|
|
482
|
+
"application error, no admins specified",
|
|
483
|
+
(app.config["MAIL_USERNAME"], app.config["MAIL_PASSWORD"]),
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
mail_handler.setLevel(logging.ERROR)
|
|
487
|
+
mail_handler.setFormatter(formatter)
|
|
488
|
+
app.logger.addHandler(mail_handler)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def load_plugins(app: Flask):
|
|
492
|
+
pluggy.add_hookspecs(spec)
|
|
493
|
+
|
|
494
|
+
# have to find all the flaskbb modules that are loaded this way
|
|
495
|
+
# otherwise sys.modules might change while we're iterating it
|
|
496
|
+
# because of imports and that makes Python very unhappy
|
|
497
|
+
# we are not interested in duplicated plugins or invalid ones
|
|
498
|
+
# ('None' - appears on py2) and thus using a set
|
|
499
|
+
flaskbb_modules = set(
|
|
500
|
+
module for name, module in sys.modules.items() if name.startswith("flaskbb")
|
|
501
|
+
)
|
|
502
|
+
for module in flaskbb_modules:
|
|
503
|
+
pluggy.register(module, internal=True)
|
|
504
|
+
|
|
505
|
+
try:
|
|
506
|
+
with app.app_context():
|
|
507
|
+
plugins = db.session.execute(db.select(PluginRegistry)).scalars().all()
|
|
508
|
+
|
|
509
|
+
except (OperationalError, ProgrammingError) as exc:
|
|
510
|
+
logger.debug(
|
|
511
|
+
"Database is not setup correctly or has not been setup yet.",
|
|
512
|
+
exc_info=exc,
|
|
513
|
+
)
|
|
514
|
+
# load plugins even though the database isn't setup correctly
|
|
515
|
+
# i.e. when creating the initial database and wanting to install
|
|
516
|
+
# the plugins migration as well
|
|
517
|
+
pluggy.load_setuptools_entrypoints("flaskbb_plugins")
|
|
518
|
+
return
|
|
519
|
+
|
|
520
|
+
for plugin in plugins:
|
|
521
|
+
if not plugin.enabled:
|
|
522
|
+
pluggy.set_blocked(plugin.name)
|
|
523
|
+
|
|
524
|
+
pluggy.load_setuptools_entrypoints("flaskbb_plugins")
|
|
525
|
+
pluggy.hook.flaskbb_extensions(app=app)
|
|
526
|
+
|
|
527
|
+
loaded_names = set([p[0] for p in pluggy.list_name_plugin()])
|
|
528
|
+
registered_names: set[str] = set([p.name for p in plugins])
|
|
529
|
+
unregistered = [
|
|
530
|
+
PluginRegistry(name=name)
|
|
531
|
+
for name in loaded_names - registered_names
|
|
532
|
+
# ignore internal FlaskBB modules
|
|
533
|
+
if not name.startswith("flaskbb.") and name != "flaskbb"
|
|
534
|
+
]
|
|
535
|
+
with app.app_context():
|
|
536
|
+
db.session.add_all(unregistered)
|
|
537
|
+
db.session.commit()
|
|
538
|
+
|
|
539
|
+
removed = 0
|
|
540
|
+
if app.config["REMOVE_DEAD_PLUGINS"]:
|
|
541
|
+
removed = remove_zombie_plugins_from_db()
|
|
542
|
+
logger.info("Removed Plugins: {}".format(removed))
|
|
543
|
+
|
|
544
|
+
# we need a copy of it because of
|
|
545
|
+
# RuntimeError: dictionary changed size during iteration
|
|
546
|
+
tasks = celery.tasks.copy()
|
|
547
|
+
disabled_plugins = [p.__package__ for p in pluggy.get_disabled_plugins()]
|
|
548
|
+
for task_name, task in tasks.items():
|
|
549
|
+
if task.__module__.split(".")[0] in disabled_plugins:
|
|
550
|
+
logger.debug("Unregistering task: '{}'".format(task))
|
|
551
|
+
celery.tasks.unregister(task_name)
|