diff --git a/requirements.txt b/requirements.txt index e9afbec0..be338751 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ wheel Flask>=2,<3 flask_cors Flask-Reuploaded==0.3.2 -Flask-WTF==1.0.0 +Flask-WTF==1.2.1 email-validator==1.1.3 Flask-Mail>=0.9.1 requests==2.31.0 diff --git a/subscribie/__init__.py b/subscribie/__init__.py index e52d239b..e4d2ae58 100644 --- a/subscribie/__init__.py +++ b/subscribie/__init__.py @@ -44,6 +44,11 @@ import click from jinja2 import Template from .models import PaymentProvider, Person, Company, Module, Plan, PriceList +from .bootstrap_app import ( + migrate_database, + set_app_default_settings, + set_plans_default_category, +) load_dotenv(verbose=True) @@ -63,6 +68,19 @@ def create_app(test_config=None): app.config.update(os.environ) app.config["PERMANENT_SESSION_LIFETIME"] = PERMANENT_SESSION_LIFETIME + with app.app_context(): + database.init_app(app) + # Initialize flask_migrate using Migrate + # Note: Migrate configures the flask_migrate addon- it does not + # *perform* a migration + Migrate(app, database) + + # Perform a programatic database migration during application boot + # Note: flask_migrate calls database migrations 'upgrades'. + migrate_database() + set_app_default_settings() + set_plans_default_category() + if test_config is not None: app.config.update(test_config) @@ -152,9 +170,6 @@ def register_modules(): app.add_url_rule("/", "index", views.__getattribute__("choose")) with app.app_context(): - database.init_app(app) - Migrate(app, database) - try: payment_provider = PaymentProvider.query.first() if payment_provider is None: diff --git a/subscribie/blueprints/admin/__init__.py b/subscribie/blueprints/admin/__init__.py index 0126be6f..db45d516 100644 --- a/subscribie/blueprints/admin/__init__.py +++ b/subscribie/blueprints/admin/__init__.py @@ -1202,17 +1202,9 @@ def connect_tawk_manually(): @login_required def add_custom_code(): setting = Setting.query.first() - if setting is None: - setting = Setting() - database.session.add(setting) - if request.method == "POST": custom_code = request.form.get("code", None) if custom_code is not None: - setting = Setting.query.first() - if setting is None: - setting = Setting() - database.session.add(setting) setting.custom_code = custom_code database.session.commit() flash("Custom code added") diff --git a/subscribie/bootstrap_app.py b/subscribie/bootstrap_app.py new file mode 100644 index 00000000..c0d25cf3 --- /dev/null +++ b/subscribie/bootstrap_app.py @@ -0,0 +1,55 @@ +from pathlib import Path +from flask_migrate import upgrade +from .logger import logger # noqa: F401 +import logging +from flask import current_app +from subscribie.database import database +from subscribie.models import Setting, Plan, Category + +log = logging.getLogger(__name__) + + +def migrate_database(): + """Migrate database when app first boots""" + log.info("Migrating database") + upgrade( + directory=Path(current_app.config["SUBSCRIBIE_REPO_DIRECTORY"] + "/migrations") + ) + + +def set_app_default_settings(): + """Pre-populate the Settings model with the column insertion defaults. + + If the Settings model is empty, pre-populate the database with the column + insertion defaults (see model.py -> class Settings). + + Note this does not relate to .env settings. See README.md + The Settings stored in the Settings database model are for + user controllable settings which may be changed at runtime + without an application restart. + + Issue #1262 + https://github.com/Subscribie/subscribie/issues/1262 + """ + setting = Setting.query.first() + if setting is None: + setting = Setting() + database.session.add(setting) + database.session.commit() + + +def set_plans_default_category(): + """Add all plans to a default category if they are not associated with one""" + # Add all plans to one + if Category.query.count() == 0: # If no categories, create default + category = Category() + # Note this string is not translated since is populated + # during bootstrap. category.name titles may be edited in the + # admin dashboard in the 'Manage Categories' section + category.name = "Make your choice" + # Add all plans to this category + plans = Plan.query.all() + for plan in plans: + plan.category = category + database.session.add(category) + database.session.commit() diff --git a/subscribie/views.py b/subscribie/views.py index d56c1c5e..7febd3e7 100644 --- a/subscribie/views.py +++ b/subscribie/views.py @@ -17,9 +17,7 @@ Markup, ) from .models import Company, Plan, Integration, Page, Category, Setting, PaymentProvider -from flask_migrate import upgrade from subscribie.blueprints.style import inject_custom_style -from subscribie.database import database from subscribie.signals import register_signal_handlers from subscribie.blueprints.admin.stats import ( get_number_of_active_subscribers, @@ -45,15 +43,6 @@ register_signal_handlers() -@bp.before_app_first_request -def migrate_database(): - """Migrate database when app first boots""" - log.info("Migrating database") - upgrade( - directory=Path(current_app.config["SUBSCRIBIE_REPO_DIRECTORY"] + "/migrations") - ) - - @bp.before_app_request def on_each_request(): # Detect country code if present in the request from proxy @@ -110,19 +99,6 @@ def on_each_request(): f'session fallback_default_country_active is: {session["fallback_default_country_active"]}' # noqa: E501 ) - # Add all plans to one - if Category.query.count() == 0: # If no categories, create default - category = Category() - # Note this string is not translated since is populated - # during bootstrap. category.name titles may be edited in the - # admin dashboard in the 'Manage Categories' section - category.name = "Make your choice" - # Add all plans to this category - plans = Plan.query.all() - for plan in plans: - plan.category = category - database.session.add(category) - @bp.before_app_request def check_if_inside_iframe():