Commit ee45f7d3 authored by André Anjos's avatar André Anjos

Merge branch 'restore_validate_sqlite_version' into 'master'

Restore command: validate sqlite version

See merge request !279
parents 300b3942 968128e5
Pipeline #28119 passed with stages
in 16 minutes and 47 seconds
...@@ -27,113 +27,137 @@ ...@@ -27,113 +27,137 @@
import logging import logging
logger = logging.getLogger(__name__)
import os import os
import shutil import shutil
import tarfile import tarfile
import tempfile import tempfile
import django.core.serializers.base
from django.contrib.auth.models import User
from django.core.management import call_command from django.core.management import call_command
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.conf import settings from django.conf import settings
from django.apps import apps, registry from django.apps import apps, registry
from django.db import connection
from .backup import APPS
# Overrides deserialization to affect OneToOneFields for Users correctly # Overrides deserialization to affect OneToOneFields for Users correctly
import django.core.serializers.base
from django.contrib.auth.models import User
from ....navigation.models import Agreement from ....navigation.models import Agreement
from ....accounts.models import AccountSettings from ....accounts.models import AccountSettings
from ....experiments.models import Experiment, Block from ....experiments.models import Experiment
from .backup import APPS
logger = logging.getLogger(__name__)
_original_save = django.core.serializers.base.DeserializedObject.save _original_save = django.core.serializers.base.DeserializedObject.save
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if isinstance(self.object, Agreement): if isinstance(self.object, Agreement):
# if the user in question has an agreement, delete and write new # if the user in question has an agreement, delete and write new
user = User.objects.get(pk=self.object.user_id) user = User.objects.get(pk=self.object.user_id)
if hasattr(user, 'agreement'): user.agreement.delete() if hasattr(user, "agreement"):
user.agreement.delete()
elif isinstance(self.object, AccountSettings): elif isinstance(self.object, AccountSettings):
# if the user in question has account settings, just update it # if the user in question has account settings, just update it
user = User.objects.get(pk=self.object.owner_id) user = User.objects.get(pk=self.object.owner_id)
if hasattr(user, 'accountsettings'): user.accountsettings.delete() if hasattr(user, "accountsettings"):
user.accountsettings.delete()
# use the built-in function for storing the new object # use the built-in function for storing the new object
_original_save(self, *args, **kwargs) _original_save(self, *args, **kwargs)
django.core.serializers.base.DeserializedObject.save = save django.core.serializers.base.DeserializedObject.save = save
class Command(BaseCommand): class Command(BaseCommand):
help = 'Resets the current database with a (stored) backup' help = "Resets the current database with a (stored) backup"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument('-e', '--exclude', dest='exclude', action='append', parser.add_argument(
default=[], help='An app_label to exclude (use multiple ' \ "-e",
'--exclude to exclude multiple apps).') "--exclude",
dest="exclude",
parser.add_argument('backup', type=str, action="append",
help='The backup you wish to restore from') default=[],
help="An app_label to exclude (use multiple "
"--exclude to exclude multiple apps).",
)
parser.add_argument(
"backup", type=str, help="The backup you wish to restore from"
)
def handle(self, *ignored, **arguments): def handle(self, *ignored, **arguments):
if connection.vendor == "sqlite":
import sqlite3 # noqa
from pkg_resources import parse_version # noqa
if parse_version(sqlite3.sqlite_version) >= parse_version("3.26"):
raise CommandError(
"Version 3.26 of sqlite is known to break the restore function.\n"
"More information:\n"
"https://code.djangoproject.com/ticket/29182\n"
"https://www.sqlite.org/src/info/f44bc7a8b3fac82a\n"
"https://bugs.debian.org/915626#27"
)
# Setup this command's logging level # Setup this command's logging level
global logger global logger
arguments['verbosity'] = int(arguments['verbosity']) arguments["verbosity"] = int(arguments["verbosity"])
if arguments['verbosity'] >= 1: if arguments["verbosity"] >= 1:
if arguments['verbosity'] == 1: logger.setLevel(logging.INFO) if arguments["verbosity"] == 1:
elif arguments['verbosity'] >= 2: logger.setLevel(logging.DEBUG) logger.setLevel(logging.INFO)
elif arguments["verbosity"] >= 2:
logger.setLevel(logging.DEBUG)
try: try:
tmpdir = tempfile.mkdtemp('.backup', 'beat.web-') tmpdir = tempfile.mkdtemp(".backup", "beat.web-")
# extracts the tarball on the temporary directory # extracts the tarball on the temporary directory
logger.info("Reading archive `%s'" % arguments['backup']) logger.info("Reading archive `%s'" % arguments["backup"])
with tarfile.open(arguments['backup']) as tar: with tarfile.open(arguments["backup"]) as tar:
tar.extractall(tmpdir) tar.extractall(tmpdir)
exclude = arguments.get('exclude') exclude = arguments.get("exclude")
# remove uninstalled apps # remove uninstalled apps
global APPS
installed_apps = registry.apps.all_models.keys() installed_apps = registry.apps.all_models.keys()
exclude += [app for app in APPS if app not in installed_apps] exclude += [app for app in APPS if app not in installed_apps]
arguments = dict( arguments = dict(verbosity=arguments.get("verbosity"), interactive=False)
verbosity=arguments.get('verbosity'),
interactive=False,
)
# reset database contents # reset database contents
logger.info("Loading initial data...") logger.info("Loading initial data...")
call_command('migrate', **arguments) call_command("migrate", **arguments)
# loads the initial data # loads the initial data
srcfile = os.path.join(tmpdir, 'initial.json') srcfile = os.path.join(tmpdir, "initial.json")
logger.info("Loading unspecified initial data <- `%s'" % \ logger.info("Loading unspecified initial data <- `%s'" % srcfile)
srcfile) call_command("loaddata", srcfile, **arguments)
call_command('loaddata', srcfile, **arguments)
# reset all user tokens (so they can be re-inserted) # reset all user tokens (so they can be re-inserted)
apps.get_model('authtoken.Token').objects.all().delete() apps.get_model("authtoken.Token").objects.all().delete()
# and loads the apps respecting the imposed order # and loads the apps respecting the imposed order
for app in APPS: for app in APPS:
if app in exclude: continue if app in exclude:
continue
# copy prefix data # copy prefix data
path = os.path.join(settings.PREFIX, app) path = os.path.join(settings.PREFIX, app)
srcdir = os.path.join(tmpdir, 'prefix', app) srcdir = os.path.join(tmpdir, "prefix", app)
if os.path.exists(srcdir): if os.path.exists(srcdir):
logger.info("Restoring core objects for `%s' <- " \ logger.info(
"`%s'" % (app, srcdir)) "Restoring core objects for `%s' <- " "`%s'" % (app, srcdir)
)
if os.path.exists(path): if os.path.exists(path):
logger.info("Removing `%s'..." % path) logger.info("Removing `%s'..." % path)
shutil.rmtree(path) shutil.rmtree(path)
...@@ -141,10 +165,9 @@ class Command(BaseCommand): ...@@ -141,10 +165,9 @@ class Command(BaseCommand):
else: else:
logger.info("No core objects found for `%s'" % app) logger.info("No core objects found for `%s'" % app)
srcfile = os.path.join(tmpdir, '%s.json' % app) srcfile = os.path.join(tmpdir, "%s.json" % app)
logger.info("Loading data for `%s' <- `%s'" % \ logger.info("Loading data for `%s' <- `%s'" % (app, srcfile))
(app, srcfile)) call_command("loaddata", srcfile, **arguments)
call_command('loaddata', srcfile, **arguments)
# creates the cache directory # creates the cache directory
cache = settings.CACHE_ROOT cache = settings.CACHE_ROOT
...@@ -152,8 +175,6 @@ class Command(BaseCommand): ...@@ -152,8 +175,6 @@ class Command(BaseCommand):
os.makedirs(cache) os.makedirs(cache)
logger.info("Created directory `%s'" % cache) logger.info("Created directory `%s'" % cache)
finally: finally:
# removes the temporary directory # removes the temporary directory
...@@ -163,4 +184,5 @@ class Command(BaseCommand): ...@@ -163,4 +184,5 @@ class Command(BaseCommand):
# JobSplit are not backed-up because of circular dependence issues # JobSplit are not backed-up because of circular dependence issues
# on the "experiments" app). # on the "experiments" app).
transient = (Experiment.RUNNING, Experiment.CANCELLING) transient = (Experiment.RUNNING, Experiment.CANCELLING)
for e in Experiment.objects.filter(status__in=transient): e.reset() for e in Experiment.objects.filter(status__in=transient):
e.reset()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment