mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 19:46:46 +00:00
Maintenance Mode Improvements (#6451)
* Custom migration step in tasks.py - Add custom management command - Wraps migration step in maintenance mode * Rename custom management command to "runmigrations" - Add command to isRunningMigrations * Add new data checks * Update database readiness checks - Set maintenance mode while performing certain management commands * Remove unused import * Re-add syncdb command * Log warning msg * Catch another potential error vector
This commit is contained in:
parent
8b62f7b2c0
commit
633fbd37bd
@ -8,7 +8,7 @@ from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
|||||||
from maintenance_mode.backends import AbstractStateBackend
|
from maintenance_mode.backends import AbstractStateBackend
|
||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
import InvenTree.helpers
|
import InvenTree.ready
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
@ -3,60 +3,73 @@
|
|||||||
- This is crucial after importing any fixtures, etc
|
- This is crucial after importing any fixtures, etc
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from maintenance_mode.core import maintenance_mode_on, set_maintenance_mode
|
||||||
|
|
||||||
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
"""Rebuild all database models which leverage the MPTT structure."""
|
"""Rebuild all database models which leverage the MPTT structure."""
|
||||||
|
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
"""Rebuild all database models which leverage the MPTT structure."""
|
"""Rebuild all database models which leverage the MPTT structure."""
|
||||||
|
with maintenance_mode_on():
|
||||||
|
self.rebuild_models()
|
||||||
|
|
||||||
|
set_maintenance_mode(False)
|
||||||
|
|
||||||
|
def rebuild_models(self):
|
||||||
|
"""Rebuild all MPTT models in the database."""
|
||||||
# Part model
|
# Part model
|
||||||
try:
|
try:
|
||||||
print('Rebuilding Part objects')
|
logger.info('Rebuilding Part objects')
|
||||||
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
|
|
||||||
Part.objects.rebuild()
|
Part.objects.rebuild()
|
||||||
except Exception:
|
except Exception:
|
||||||
print('Error rebuilding Part objects')
|
logger.info('Error rebuilding Part objects')
|
||||||
|
|
||||||
# Part category
|
# Part category
|
||||||
try:
|
try:
|
||||||
print('Rebuilding PartCategory objects')
|
logger.info('Rebuilding PartCategory objects')
|
||||||
|
|
||||||
from part.models import PartCategory
|
from part.models import PartCategory
|
||||||
|
|
||||||
PartCategory.objects.rebuild()
|
PartCategory.objects.rebuild()
|
||||||
except Exception:
|
except Exception:
|
||||||
print('Error rebuilding PartCategory objects')
|
logger.info('Error rebuilding PartCategory objects')
|
||||||
|
|
||||||
# StockItem model
|
# StockItem model
|
||||||
try:
|
try:
|
||||||
print('Rebuilding StockItem objects')
|
logger.info('Rebuilding StockItem objects')
|
||||||
|
|
||||||
from stock.models import StockItem
|
from stock.models import StockItem
|
||||||
|
|
||||||
StockItem.objects.rebuild()
|
StockItem.objects.rebuild()
|
||||||
except Exception:
|
except Exception:
|
||||||
print('Error rebuilding StockItem objects')
|
logger.info('Error rebuilding StockItem objects')
|
||||||
|
|
||||||
# StockLocation model
|
# StockLocation model
|
||||||
try:
|
try:
|
||||||
print('Rebuilding StockLocation objects')
|
logger.info('Rebuilding StockLocation objects')
|
||||||
|
|
||||||
from stock.models import StockLocation
|
from stock.models import StockLocation
|
||||||
|
|
||||||
StockLocation.objects.rebuild()
|
StockLocation.objects.rebuild()
|
||||||
except Exception:
|
except Exception:
|
||||||
print('Error rebuilding StockLocation objects')
|
logger.info('Error rebuilding StockLocation objects')
|
||||||
|
|
||||||
# Build model
|
# Build model
|
||||||
try:
|
try:
|
||||||
print('Rebuilding Build objects')
|
logger.info('Rebuilding Build objects')
|
||||||
|
|
||||||
from build.models import Build
|
from build.models import Build
|
||||||
|
|
||||||
Build.objects.rebuild()
|
Build.objects.rebuild()
|
||||||
except Exception:
|
except Exception:
|
||||||
print('Error rebuilding Build objects')
|
logger.info('Error rebuilding Build objects')
|
||||||
|
19
InvenTree/InvenTree/management/commands/runmigrations.py
Normal file
19
InvenTree/InvenTree/management/commands/runmigrations.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
"""Check if there are any pending database migrations, and run them."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from InvenTree.tasks import check_for_migrations
|
||||||
|
|
||||||
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Check if there are any pending database migrations, and run them."""
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
"""Check for any pending database migrations."""
|
||||||
|
logger.info('Checking for pending database migrations')
|
||||||
|
check_for_migrations(force=True, reload_registry=False)
|
||||||
|
logger.info('Database migrations complete')
|
@ -10,13 +10,45 @@ def isInTestMode():
|
|||||||
|
|
||||||
|
|
||||||
def isImportingData():
|
def isImportingData():
|
||||||
"""Returns True if the database is currently importing data, e.g. 'loaddata' command is performed."""
|
"""Returns True if the database is currently importing (or exporting) data, e.g. 'loaddata' command is performed."""
|
||||||
return 'loaddata' in sys.argv
|
return any((x in sys.argv for x in ['flush', 'loaddata', 'dumpdata']))
|
||||||
|
|
||||||
|
|
||||||
def isRunningMigrations():
|
def isRunningMigrations():
|
||||||
"""Return True if the database is currently running migrations."""
|
"""Return True if the database is currently running migrations."""
|
||||||
return any((x in sys.argv for x in ['migrate', 'makemigrations', 'showmigrations']))
|
return any(
|
||||||
|
(
|
||||||
|
x in sys.argv
|
||||||
|
for x in ['migrate', 'makemigrations', 'showmigrations', 'runmigrations']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def isRebuildingData():
|
||||||
|
"""Return true if any of the rebuilding commands are being executed."""
|
||||||
|
return any(
|
||||||
|
(
|
||||||
|
x in sys.argv
|
||||||
|
for x in ['prerender', 'rebuild_models', 'rebuild_thumbnails', 'rebuild']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def isRunningBackup():
|
||||||
|
"""Return true if any of the backup commands are being executed."""
|
||||||
|
return any(
|
||||||
|
(
|
||||||
|
x in sys.argv
|
||||||
|
for x in [
|
||||||
|
'backup',
|
||||||
|
'restore',
|
||||||
|
'dbbackup',
|
||||||
|
'dbresotore',
|
||||||
|
'mediabackup',
|
||||||
|
'mediarestore',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def isInWorkerThread():
|
def isInWorkerThread():
|
||||||
@ -58,26 +90,30 @@ def canAppAccessDatabase(
|
|||||||
There are some circumstances where we don't want the ready function in apps.py
|
There are some circumstances where we don't want the ready function in apps.py
|
||||||
to touch the database
|
to touch the database
|
||||||
"""
|
"""
|
||||||
|
# Prevent database access if we are running backups
|
||||||
|
if isRunningBackup():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Prevent database access if we are importing data
|
||||||
|
if isImportingData():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Prevent database access if we are rebuilding data
|
||||||
|
if isRebuildingData():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Prevent database access if we are running migrations
|
||||||
|
if not allow_plugins and isRunningMigrations():
|
||||||
|
return False
|
||||||
|
|
||||||
# If any of the following management commands are being executed,
|
# If any of the following management commands are being executed,
|
||||||
# prevent custom "on load" code from running!
|
# prevent custom "on load" code from running!
|
||||||
excluded_commands = [
|
excluded_commands = [
|
||||||
'flush',
|
|
||||||
'loaddata',
|
|
||||||
'dumpdata',
|
|
||||||
'check',
|
'check',
|
||||||
'createsuperuser',
|
'createsuperuser',
|
||||||
'wait_for_db',
|
'wait_for_db',
|
||||||
'prerender',
|
|
||||||
'rebuild_models',
|
|
||||||
'rebuild_thumbnails',
|
|
||||||
'makemessages',
|
'makemessages',
|
||||||
'compilemessages',
|
'compilemessages',
|
||||||
'backup',
|
|
||||||
'dbbackup',
|
|
||||||
'mediabackup',
|
|
||||||
'restore',
|
|
||||||
'dbrestore',
|
|
||||||
'mediarestore',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if not allow_shell:
|
if not allow_shell:
|
||||||
@ -88,12 +124,7 @@ def canAppAccessDatabase(
|
|||||||
excluded_commands.append('test')
|
excluded_commands.append('test')
|
||||||
|
|
||||||
if not allow_plugins:
|
if not allow_plugins:
|
||||||
excluded_commands.extend([
|
excluded_commands.extend(['collectstatic'])
|
||||||
'makemigrations',
|
|
||||||
'showmigrations',
|
|
||||||
'migrate',
|
|
||||||
'collectstatic',
|
|
||||||
])
|
|
||||||
|
|
||||||
for cmd in excluded_commands:
|
for cmd in excluded_commands:
|
||||||
if cmd in sys.argv:
|
if cmd in sys.argv:
|
||||||
|
@ -644,7 +644,7 @@ def get_migration_plan():
|
|||||||
|
|
||||||
|
|
||||||
@scheduled_task(ScheduledTask.DAILY)
|
@scheduled_task(ScheduledTask.DAILY)
|
||||||
def check_for_migrations():
|
def check_for_migrations(force: bool = False, reload_registry: bool = True):
|
||||||
"""Checks if migrations are needed.
|
"""Checks if migrations are needed.
|
||||||
|
|
||||||
If the setting auto_update is enabled we will start updating.
|
If the setting auto_update is enabled we will start updating.
|
||||||
@ -659,8 +659,9 @@ def check_for_migrations():
|
|||||||
|
|
||||||
logger.info('Checking for pending database migrations')
|
logger.info('Checking for pending database migrations')
|
||||||
|
|
||||||
# Force plugin registry reload
|
if reload_registry:
|
||||||
registry.check_reload()
|
# Force plugin registry reload
|
||||||
|
registry.check_reload()
|
||||||
|
|
||||||
plan = get_migration_plan()
|
plan = get_migration_plan()
|
||||||
|
|
||||||
@ -674,7 +675,7 @@ def check_for_migrations():
|
|||||||
set_pending_migrations(n)
|
set_pending_migrations(n)
|
||||||
|
|
||||||
# Test if auto-updates are enabled
|
# Test if auto-updates are enabled
|
||||||
if not get_setting('INVENTREE_AUTO_UPDATE', 'auto_update'):
|
if not force and not get_setting('INVENTREE_AUTO_UPDATE', 'auto_update'):
|
||||||
logger.info('Auto-update is disabled - skipping migrations')
|
logger.info('Auto-update is disabled - skipping migrations')
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -706,6 +707,7 @@ def check_for_migrations():
|
|||||||
set_maintenance_mode(False)
|
set_maintenance_mode(False)
|
||||||
logger.info('Manually released maintenance mode')
|
logger.info('Manually released maintenance mode')
|
||||||
|
|
||||||
# We should be current now - triggering full reload to make sure all models
|
if reload_registry:
|
||||||
# are loaded fully in their new state.
|
# We should be current now - triggering full reload to make sure all models
|
||||||
registry.reload_plugins(full_reload=True, force_reload=True, collect=True)
|
# are loaded fully in their new state.
|
||||||
|
registry.reload_plugins(full_reload=True, force_reload=True, collect=True)
|
||||||
|
@ -677,6 +677,16 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
setting = cls(key=key, **kwargs)
|
setting = cls(key=key, **kwargs)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
except (OperationalError, ProgrammingError):
|
||||||
|
if not key.startswith('_'):
|
||||||
|
logger.warning("Database is locked, cannot set setting '%s'", key)
|
||||||
|
# Likely the DB is locked - not much we can do here
|
||||||
|
return
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception(
|
||||||
|
"Error setting setting '%s' for %s: %s", key, str(cls), str(type(exc))
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
# Enforce standard boolean representation
|
# Enforce standard boolean representation
|
||||||
if setting.is_bool():
|
if setting.is_bool():
|
||||||
@ -703,6 +713,10 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
attempts=attempts - 1,
|
attempts=attempts - 1,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
except (OperationalError, ProgrammingError):
|
||||||
|
logger.warning("Database is locked, cannot set setting '%s'", key)
|
||||||
|
# Likely the DB is locked - not much we can do here
|
||||||
|
pass
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# Some other error
|
# Some other error
|
||||||
logger.exception(
|
logger.exception(
|
||||||
|
@ -12,6 +12,8 @@ from django.conf import settings
|
|||||||
from django.core.exceptions import AppRegistryNotReady
|
from django.core.exceptions import AppRegistryNotReady
|
||||||
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
||||||
|
|
||||||
|
from maintenance_mode.core import maintenance_mode_on, set_maintenance_mode
|
||||||
|
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
import InvenTree.ready
|
import InvenTree.ready
|
||||||
|
|
||||||
@ -32,13 +34,10 @@ class LabelConfig(AppConfig):
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
if InvenTree.ready.isRunningMigrations():
|
if not InvenTree.ready.canAppAccessDatabase(allow_test=False):
|
||||||
return
|
return # pragma: no cover
|
||||||
|
|
||||||
if (
|
with maintenance_mode_on():
|
||||||
InvenTree.ready.canAppAccessDatabase(allow_test=False)
|
|
||||||
and not InvenTree.ready.isImportingData()
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
self.create_labels() # pragma: no cover
|
self.create_labels() # pragma: no cover
|
||||||
except (
|
except (
|
||||||
@ -52,6 +51,8 @@ class LabelConfig(AppConfig):
|
|||||||
'Database was not ready for creating labels', stacklevel=2
|
'Database was not ready for creating labels', stacklevel=2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_maintenance_mode(False)
|
||||||
|
|
||||||
def create_labels(self):
|
def create_labels(self):
|
||||||
"""Create all default templates."""
|
"""Create all default templates."""
|
||||||
# Test if models are ready
|
# Test if models are ready
|
||||||
|
@ -11,6 +11,8 @@ from django.conf import settings
|
|||||||
from django.core.exceptions import AppRegistryNotReady
|
from django.core.exceptions import AppRegistryNotReady
|
||||||
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
||||||
|
|
||||||
|
from maintenance_mode.core import maintenance_mode_on, set_maintenance_mode
|
||||||
|
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
@ -32,36 +34,36 @@ class ReportConfig(AppConfig):
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
if InvenTree.ready.isRunningMigrations():
|
if not InvenTree.ready.canAppAccessDatabase(allow_test=False):
|
||||||
return
|
return # pragma: no cover
|
||||||
|
|
||||||
# Configure logging for PDF generation (disable "info" messages)
|
# Configure logging for PDF generation (disable "info" messages)
|
||||||
logging.getLogger('fontTools').setLevel(logging.WARNING)
|
logging.getLogger('fontTools').setLevel(logging.WARNING)
|
||||||
logging.getLogger('weasyprint').setLevel(logging.WARNING)
|
logging.getLogger('weasyprint').setLevel(logging.WARNING)
|
||||||
|
|
||||||
# Create entries for default report templates
|
with maintenance_mode_on():
|
||||||
if (
|
self.create_reports()
|
||||||
InvenTree.ready.canAppAccessDatabase(allow_test=False)
|
|
||||||
and not InvenTree.ready.isImportingData()
|
set_maintenance_mode(False)
|
||||||
|
|
||||||
|
def create_reports(self):
|
||||||
|
"""Create default report templates."""
|
||||||
|
try:
|
||||||
|
self.create_default_test_reports()
|
||||||
|
self.create_default_build_reports()
|
||||||
|
self.create_default_bill_of_materials_reports()
|
||||||
|
self.create_default_purchase_order_reports()
|
||||||
|
self.create_default_sales_order_reports()
|
||||||
|
self.create_default_return_order_reports()
|
||||||
|
self.create_default_stock_location_reports()
|
||||||
|
except (
|
||||||
|
AppRegistryNotReady,
|
||||||
|
IntegrityError,
|
||||||
|
OperationalError,
|
||||||
|
ProgrammingError,
|
||||||
):
|
):
|
||||||
try:
|
# Database might not yet be ready
|
||||||
self.create_default_test_reports()
|
warnings.warn('Database was not ready for creating reports', stacklevel=2)
|
||||||
self.create_default_build_reports()
|
|
||||||
self.create_default_bill_of_materials_reports()
|
|
||||||
self.create_default_purchase_order_reports()
|
|
||||||
self.create_default_sales_order_reports()
|
|
||||||
self.create_default_return_order_reports()
|
|
||||||
self.create_default_stock_location_reports()
|
|
||||||
except (
|
|
||||||
AppRegistryNotReady,
|
|
||||||
IntegrityError,
|
|
||||||
OperationalError,
|
|
||||||
ProgrammingError,
|
|
||||||
):
|
|
||||||
# Database might not yet be ready
|
|
||||||
warnings.warn(
|
|
||||||
'Database was not ready for creating reports', stacklevel=2
|
|
||||||
)
|
|
||||||
|
|
||||||
def create_default_reports(self, model, reports):
|
def create_default_reports(self, model, reports):
|
||||||
"""Copy default report files across to the media directory."""
|
"""Copy default report files across to the media directory."""
|
||||||
|
5
tasks.py
5
tasks.py
@ -369,10 +369,9 @@ def migrate(c):
|
|||||||
print('Running InvenTree database migrations...')
|
print('Running InvenTree database migrations...')
|
||||||
print('========================================')
|
print('========================================')
|
||||||
|
|
||||||
manage(c, 'makemigrations')
|
# Run custom management command which wraps migrations in "maintenance mode"
|
||||||
manage(c, 'migrate --noinput')
|
manage(c, 'runmigrations', pty=True)
|
||||||
manage(c, 'migrate --run-syncdb')
|
manage(c, 'migrate --run-syncdb')
|
||||||
manage(c, 'check')
|
|
||||||
|
|
||||||
print('========================================')
|
print('========================================')
|
||||||
print('InvenTree database migrations completed!')
|
print('InvenTree database migrations completed!')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user