wcs/wcs/ctl/management/commands/convert_to_sql.py

155 lines
6.0 KiB
Python

# w.c.s. - web application for online forms
# Copyright (C) 2005-2018 Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
import io
import os
import sys
import traceback
import psycopg2
from django.core.management.base import BaseCommand, CommandError
from django.utils.encoding import force_bytes
from wcs import sql
from wcs.formdef import FormDef
from wcs.qommon.misc import localstrftime
from wcs.qommon.publisher import get_publisher_class
from wcs.qommon.storage import atomic_write
from wcs.users import User
class Command(BaseCommand):
help = 'Setup postgresql connection parameters and migrate existing objects.'
def add_arguments(self, parser):
parser.add_argument('-d', '--domain', required=True)
parser.add_argument('--database', required=True)
parser.add_argument('--host')
parser.add_argument('--port', type=int)
parser.add_argument('--user')
parser.add_argument('--password')
def handle(self, **options):
self.publisher = self.get_publisher(options['domain'])
if self.publisher.is_using_postgresql():
raise CommandError('tenant already using postgresql')
self.setup_connection(**options)
sql.get_connection(new=True)
self.store_users()
self.store_forms()
self.publisher.write_cfg()
self.enable_connection()
self.publisher.initialize_sql()
self.publisher.cleanup()
def get_publisher(self, domain):
publisher_class = get_publisher_class()
if domain not in publisher_class.get_tenants():
raise CommandError('unknown tenant')
publisher = publisher_class.create_publisher()
publisher.app_dir = os.path.join(publisher.app_dir, domain)
publisher.set_config()
return publisher
def setup_connection(self, **kwargs):
options = {}
for k in ['host', 'port', 'database', 'user', 'password']:
if k in kwargs:
options[k] = kwargs.get(k)
self.publisher.cfg['postgresql'] = options
def enable_connection(self):
if not self.publisher.site_options.has_option('options', 'postgresql'):
if not self.publisher.site_options.has_section('options'):
self.publisher.site_options.add_section('options')
self.publisher.site_options.set('options', 'postgresql', 'true')
options_file = os.path.join(self.publisher.app_dir, 'site-options.cfg')
stringio = io.StringIO()
self.publisher.site_options.write(stringio)
atomic_write(options_file, force_bytes(stringio.getvalue()))
def store_users(self):
errors = []
print('converting users')
sql.do_user_table()
count = User.count()
for i, user_id in enumerate(User.keys()):
user = User.get(user_id)
user.__class__ = sql.SqlUser
try:
user.store()
except AssertionError:
errors.append((user, traceback.format_exc()))
self.update_progress(100 * i / count)
sql.SqlUser.fix_sequences()
if errors:
with open('error_user.log', 'w') as error_log:
for user, trace in errors:
error_log.write('user_id %s\n' % user.id)
error_log.write(trace)
error_log.write('-' * 80)
error_log.write('\n\n')
print('There were some errors, see error_user.log for details.')
def store_forms(self):
errors = []
for formdef in FormDef.select():
print('converting %s' % formdef.name)
sql.do_formdef_tables(formdef, rebuild_views=True, rebuild_global_views=True)
data_class = formdef.data_class(mode='files')
count = data_class.count()
# load all objects a first time, to allow the migrate() code to be
# run and the eventual changes properly saved.
for id in data_class.keys():
formdata = data_class.get(id)
delattr(sys.modules['formdef'], formdef.data_class_name)
# once this is done, reload and store everything in postgresql
sql_data_class = formdef.data_class(mode='sql')
for i, id in enumerate(data_class.keys()):
formdata = data_class.get(id)
formdata._formdef = formdef
formdata._evolution = formdata.evolution
formdata.__class__ = sql_data_class
try:
formdata.store()
except (AssertionError, psycopg2.DataError):
errors.append((formdata, traceback.format_exc()))
self.update_progress(100 * i / count)
sql_data_class.fix_sequences()
if errors:
with open('error_formdata.log', 'w') as error_log:
for formdata, trace in errors:
error_log.write(
'%s %s - %s\n' % (formdata.formdef, formdata.id, localstrftime(formdata.receipt_time))
)
error_log.write(trace)
error_log.write('-' * 80)
error_log.write('\n\n')
print('There were some errors, see error_formdata.log.')
def update_progress(self, progress, num_columns=120):
sys.stdout.write(
'[%s] %s%%\r'
% (('#' * int((num_columns - 10) * progress / 100)).ljust(num_columns - 15), progress)
)