mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-27 19:16:44 +00:00
feat(backend): clearer behaviour on missing migrations (#9527)
* feat(backend): better warning on missing migrations * add debug info to some tasks * ensure db would even be accessed before raising concerns * add more markers * Add decorator to log flow * reduce calls * reduce fnc down
This commit is contained in:
parent
9890246180
commit
9a49c9f19c
@ -1,5 +1,6 @@
|
||||
"""AppConfig for InvenTree app."""
|
||||
|
||||
import sys
|
||||
from importlib import import_module
|
||||
from pathlib import Path
|
||||
|
||||
@ -20,6 +21,7 @@ from common.settings import get_global_setting, set_global_setting
|
||||
from InvenTree.config import get_setting
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
MIGRATIONS_CHECK_DONE = False
|
||||
|
||||
|
||||
class InvenTreeConfig(AppConfig):
|
||||
@ -56,6 +58,9 @@ class InvenTreeConfig(AppConfig):
|
||||
return
|
||||
|
||||
if InvenTree.ready.canAppAccessDatabase() or settings.TESTING_ENV:
|
||||
# Ensure there are no open migrations
|
||||
self.ensure_migrations_done()
|
||||
|
||||
self.remove_obsolete_tasks()
|
||||
self.collect_tasks()
|
||||
self.start_background_tasks()
|
||||
@ -382,3 +387,14 @@ class InvenTreeConfig(AppConfig):
|
||||
from generic.states import storage
|
||||
|
||||
storage.collect()
|
||||
|
||||
def ensure_migrations_done(self=None):
|
||||
"""Ensures there are no open migrations, stop if inconsistent state."""
|
||||
global MIGRATIONS_CHECK_DONE
|
||||
if MIGRATIONS_CHECK_DONE:
|
||||
return
|
||||
|
||||
if not InvenTree.tasks.check_for_migrations():
|
||||
logger.error('INVE-W8: Database Migrations required')
|
||||
sys.exit(1)
|
||||
MIGRATIONS_CHECK_DONE = True
|
||||
|
@ -629,10 +629,12 @@ def get_migration_plan():
|
||||
|
||||
|
||||
@scheduled_task(ScheduledTask.DAILY)
|
||||
def check_for_migrations(force: bool = False, reload_registry: bool = True):
|
||||
def check_for_migrations(force: bool = False, reload_registry: bool = True) -> bool:
|
||||
"""Checks if migrations are needed.
|
||||
|
||||
If the setting auto_update is enabled we will start updating.
|
||||
|
||||
Returns bool indicating if migrations are up to date
|
||||
"""
|
||||
from plugin import registry
|
||||
|
||||
@ -654,14 +656,14 @@ def check_for_migrations(force: bool = False, reload_registry: bool = True):
|
||||
# Check if there are any open migrations
|
||||
if not plan:
|
||||
set_pending_migrations(0)
|
||||
return
|
||||
return True
|
||||
|
||||
set_pending_migrations(n)
|
||||
|
||||
# Test if auto-updates are enabled
|
||||
if not force and not get_setting('INVENTREE_AUTO_UPDATE', 'auto_update'):
|
||||
logger.info('Auto-update is disabled - skipping migrations')
|
||||
return
|
||||
return False
|
||||
|
||||
# Log open migrations
|
||||
for migration in plan:
|
||||
@ -696,6 +698,8 @@ def check_for_migrations(force: bool = False, reload_registry: bool = True):
|
||||
# are loaded fully in their new state.
|
||||
registry.reload_plugins(full_reload=True, force_reload=True, collect=True)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def email_user(user_id: int, subject: str, message: str) -> None:
|
||||
"""Send a message to a user."""
|
||||
|
@ -6,6 +6,7 @@ from django.db.utils import OperationalError, ProgrammingError
|
||||
import structlog
|
||||
|
||||
import InvenTree.ready
|
||||
from InvenTree.apps import InvenTreeConfig
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
|
||||
@ -28,6 +29,9 @@ class PartConfig(AppConfig):
|
||||
return
|
||||
|
||||
if InvenTree.ready.canAppAccessDatabase():
|
||||
# Ensure there are no open migrations
|
||||
InvenTreeConfig.ensure_migrations_done()
|
||||
|
||||
self.update_trackable_status()
|
||||
self.reset_part_pricing_flags()
|
||||
|
||||
|
46
tasks.py
46
tasks.py
@ -7,6 +7,7 @@ import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
from platform import python_version
|
||||
from typing import Optional
|
||||
@ -30,6 +31,15 @@ def is_rtd_environment():
|
||||
return is_true(os.environ.get('READTHEDOCS', 'False'))
|
||||
|
||||
|
||||
def is_deb_environment():
|
||||
"""Check if the InvenTree environment is running in a debug environment."""
|
||||
from src.backend.InvenTree.InvenTree.config import is_true
|
||||
|
||||
return is_true(os.environ.get('INVENTREE_DEBUG', 'False')) or is_true(
|
||||
os.environ.get('RUNNER_DEBUG', 'False')
|
||||
)
|
||||
|
||||
|
||||
def task_exception_handler(t, v, tb):
|
||||
"""Handle exceptions raised by tasks.
|
||||
|
||||
@ -82,6 +92,30 @@ def info(*args):
|
||||
print(wrap_color(msg, '94'))
|
||||
|
||||
|
||||
def state_logger(fn=None, method_name=None):
|
||||
"""Decorator to log state markers before/after function execution, optionally accepting arguments."""
|
||||
|
||||
def decorator(func):
|
||||
func.method_name = method_name or f'invoke task named `{func.__name__}`'
|
||||
|
||||
@wraps(func)
|
||||
def wrapped(c, *args, **kwargs):
|
||||
do_log = is_deb_environment()
|
||||
if do_log:
|
||||
info(f'# {func.method_name}| start')
|
||||
func(c, *args, **kwargs)
|
||||
if do_log:
|
||||
info(f'# {func.method_name}| done')
|
||||
|
||||
return wrapped
|
||||
|
||||
if fn and callable(fn):
|
||||
return decorator(fn)
|
||||
elif fn and isinstance(fn, str):
|
||||
method_name = fn
|
||||
return decorator
|
||||
|
||||
|
||||
def checkInvokeVersion():
|
||||
"""Check that the installed invoke version meets minimum requirements."""
|
||||
MIN_INVOKE_VERSION = '2.0.0'
|
||||
@ -315,6 +349,7 @@ def node_available(versions: bool = False, bypass_yarn: bool = False):
|
||||
return ret(yarn_passes and node_version, node_version, yarn_version)
|
||||
|
||||
|
||||
@state_logger
|
||||
def check_file_existence(filename: Path, overwrite: bool = False):
|
||||
"""Checks if a file exists and asks the user if it should be overwritten.
|
||||
|
||||
@ -335,6 +370,7 @@ def check_file_existence(filename: Path, overwrite: bool = False):
|
||||
|
||||
# Install tasks
|
||||
@task(help={'uv': 'Use UV (experimental package manager)'})
|
||||
@state_logger('TSK01')
|
||||
def plugins(c, uv=False):
|
||||
"""Installs all plugins as specified in 'plugins.txt'."""
|
||||
from src.backend.InvenTree.InvenTree.config import get_plugin_file
|
||||
@ -360,6 +396,7 @@ def plugins(c, uv=False):
|
||||
'skip_plugins': 'Skip plugin installation',
|
||||
}
|
||||
)
|
||||
@state_logger('TSK02')
|
||||
def install(c, uv=False, skip_plugins=False):
|
||||
"""Installs required python packages."""
|
||||
# Ensure path is relative to *this* directory
|
||||
@ -447,6 +484,7 @@ def rebuild_thumbnails(c):
|
||||
|
||||
|
||||
@task
|
||||
@state_logger('TSK09')
|
||||
def clean_settings(c):
|
||||
"""Clean the setting tables of old settings."""
|
||||
info('Cleaning old settings from the database')
|
||||
@ -471,6 +509,7 @@ def remove_mfa(c, mail=''):
|
||||
'skip_plugins': 'Ignore collection of plugin static files',
|
||||
}
|
||||
)
|
||||
@state_logger('TSK08')
|
||||
def static(c, frontend=False, clear=True, skip_plugins=False):
|
||||
"""Copies required static files to the STATIC_ROOT directory, as per Django requirements."""
|
||||
if frontend and node_available():
|
||||
@ -521,6 +560,7 @@ def translate(c, ignore_static=False, no_frontend=False):
|
||||
'path': 'Specify path for generated backup files (leave blank for default path)',
|
||||
}
|
||||
)
|
||||
@state_logger('TSK04')
|
||||
def backup(c, clean=False, path=None):
|
||||
"""Backup the database and media files."""
|
||||
info('Backing up InvenTree database...')
|
||||
@ -597,6 +637,7 @@ def restore(
|
||||
|
||||
|
||||
@task(post=[rebuild_models, rebuild_thumbnails])
|
||||
@state_logger('TSK05')
|
||||
def migrate(c):
|
||||
"""Performs database migrations.
|
||||
|
||||
@ -629,6 +670,7 @@ def showmigrations(c, app=''):
|
||||
'uv': 'Use UV (experimental package manager)',
|
||||
},
|
||||
)
|
||||
@state_logger('TSK03')
|
||||
def update(
|
||||
c,
|
||||
skip_backup: bool = False,
|
||||
@ -935,6 +977,7 @@ def import_fixtures(c):
|
||||
|
||||
# Execution tasks
|
||||
@task
|
||||
@state_logger('TSK10')
|
||||
def wait(c):
|
||||
"""Wait until the database connection is ready."""
|
||||
info('Waiting for database connection...')
|
||||
@ -1224,6 +1267,7 @@ def setup_test(
|
||||
'no_default': 'Do not use default settings for schema (default = off/False)',
|
||||
}
|
||||
)
|
||||
@state_logger('TSK11')
|
||||
def schema(
|
||||
c, filename='schema.yml', overwrite=False, ignore_warnings=False, no_default=False
|
||||
):
|
||||
@ -1366,6 +1410,7 @@ def frontend_check(c):
|
||||
|
||||
|
||||
@task
|
||||
@state_logger('TSK06')
|
||||
def frontend_compile(c):
|
||||
"""Generate react frontend.
|
||||
|
||||
@ -1437,6 +1482,7 @@ def frontend_server(c):
|
||||
'clean': 'Delete old files from InvenTree/web/static/web first, default: True',
|
||||
}
|
||||
)
|
||||
@state_logger('TSK07')
|
||||
def frontend_download(
|
||||
c,
|
||||
ref=None,
|
||||
|
Loading…
x
Reference in New Issue
Block a user