Projet

Général

Profil

0002-add-management-command-convert_to_sql-20410.patch

Christophe Siraut, 21 juin 2018 17:15

Télécharger (10,4 ko)

Voir les différences:

Subject: [PATCH 2/2] add management command convert_to_sql (#20410)

 tests/test_convert_to_sql.py                  | 133 +++++++++++++++++++++++++
 wcs/ctl/management/commands/convert_to_sql.py | 137 ++++++++++++++++++++++++++
 2 files changed, 270 insertions(+)
 create mode 100644 tests/test_convert_to_sql.py
 create mode 100644 wcs/ctl/management/commands/convert_to_sql.py
tests/test_convert_to_sql.py
1
import os
2
import random
3
import psycopg2
4
import pytest
5
from django.core.management import get_commands
6
from django.core.management import call_command
7
from django.core.management.base import CommandError
8
from utilities import clean_temporary_pub
9
from utilities import create_temporary_pub
10
from utilities import force_connections_close
11
from test_api import local_user
12
from wcs.sql import cleanup_connection
13
from wcs.formdef import FormDef
14
from wcs.fields import BoolField
15

  
16

  
17
@pytest.fixture
18
def formdeffix():
19
    FormDef.wipe()
20
    formdef = FormDef()
21
    formdef.name = 'testform'
22
    formdef.description = 'plop'
23
    formdef.fields = [BoolField(id='1')]
24
    formdef.store()
25

  
26
    data_class = formdef.data_class()
27
    for value in (True, True, True, False):
28
        formdata = data_class()
29
        formdata.data = {'1': value}
30
        formdata.store()
31

  
32
    return formdef
33

  
34

  
35
@pytest.fixture(scope='module')
36
def cursor():
37
    conn = psycopg2.connect(user=os.environ['USER'])
38
    conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
39
    cur = conn.cursor()
40
    yield cur
41
    cur.close()
42

  
43

  
44
@pytest.fixture
45
def database(cursor):
46
    dbname = 'wcstests%d' % random.randint(0, 100000)
47
    cursor.execute('CREATE DATABASE %s' % dbname)
48
    yield dbname
49
    cleanup_connection()
50
    cursor.execute('DROP DATABASE %s' % dbname)
51

  
52

  
53
@pytest.fixture(params=['pickle', 'sql'])
54
def pub(request):
55
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
56
    yield pub
57
    cleanup_connection()
58
    force_connections_close()
59
    clean_temporary_pub()
60

  
61

  
62
def test_command_exists():
63
    assert 'convert_to_sql' in get_commands()
64

  
65

  
66
def test_unknown_publisher_fails():
67
    with pytest.raises(CommandError) as excinfo:
68
        call_command('convert_to_sql', '-d', 'unknown.net',
69
                     '--database', 'foobar')
70
    assert excinfo.value.message == 'unknown tenant'
71

  
72

  
73
def test_failing_connection(pub):
74
    if pub.is_using_postgresql():
75
        return
76
    with pytest.raises(psycopg2.OperationalError) as excinfo:
77
        call_command('convert_to_sql', '-d', 'example.net',
78
                     '--database', 'foobar', '--port', '666')
79
    assert 'could not connect' in excinfo.value.message
80

  
81

  
82
def test_database_does_not_exist(pub):
83
    if pub.is_using_postgresql():
84
        return
85
    new_database = 'test_{}'.format(random.randint(1000, 9999))
86
    with pytest.raises(psycopg2.OperationalError) as excinfo:
87
        call_command('convert_to_sql', '-d', 'example.net',
88
                     '--database', new_database)
89
    assert 'does not exist' in excinfo.value.message
90

  
91

  
92
def test_already_migrated_fails(pub):
93
    if pub.is_using_postgresql():
94
        with pytest.raises(CommandError) as excinfo:
95
            call_command('convert_to_sql', '-d', 'example.net',
96
                         '--database', 'foobar')
97
        assert excinfo.value.message == 'tenant already using postgresql'
98

  
99

  
100
def test_setup_database(pub, database):
101
    if pub.is_using_postgresql():
102
        return
103
    call_command('convert_to_sql', '-d', 'example.net',
104
                 '--database', database)
105
    pub.set_config()
106
    assert pub.cfg['postgresql'].get('database') == database
107

  
108

  
109
def test_migration(pub, database):
110
    if not pub.is_using_postgresql():
111
        assert 'postgresql' not in pub.cfg
112
        call_command('convert_to_sql', '-d', 'example.net',
113
                     '--database', database)
114
        pub.set_config()
115
        assert 'postgresql' in pub.cfg
116

  
117

  
118
def test_data_is_migrated(pub, database, local_user, formdeffix):
119
    if pub.is_using_postgresql():
120
        return
121

  
122
    pub.load_site_options()
123
    assert not pub.site_options.has_option('options', 'postgresql')
124
    call_command('convert_to_sql', '-d', 'example.net', '--database', database)
125
    pub.load_site_options()
126
    assert pub.site_options.has_option('options', 'postgresql')
127
    assert len(
128
        pub.user_class.get_users_with_name_identifier('0123456789')) == 1
129

  
130
    formdefs = FormDef.select()
131
    assert len(formdefs) == 1
132
    data_class = formdefs[0].data_class(mode='sql')
133
    assert len(data_class.keys()) == 4
wcs/ctl/management/commands/convert_to_sql.py
1
# w.c.s. - web application for online forms
2
# Copyright (C) 2005-2017  Entr'ouvert
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16

  
17
import os
18
import sys
19
import traceback
20
from django.core.management.base import CommandError
21
from wcs import sql
22
from wcs.users import User
23
from wcs.formdef import FormDef
24
from wcs.sql import get_connection
25
from django.core.management.base import BaseCommand
26
from qommon.publisher import get_publisher_class
27

  
28

  
29
class Command(BaseCommand):
30

  
31
    help = 'Setup postgresql connection parameters and migrate existing objects.'
32

  
33
    def add_arguments(self, parser):
34
        parser.add_argument('-d', '--domain', required=True)
35
        parser.add_argument('--database', required=True)
36
        parser.add_argument('--host')
37
        parser.add_argument('--port', type=int)
38
        parser.add_argument('--user')
39
        parser.add_argument('--password')
40

  
41
    def handle(self, **options):
42
        self.publisher = self.get_publisher(options['domain'])
43
        if self.publisher.is_using_postgresql():
44
            raise CommandError('tenant already using postgresql')
45

  
46
        self.setup_connection(**options)
47
        get_connection(new=True)
48
        self.store_users()
49
        self.store_forms()
50
        self.publisher.write_cfg()
51
        self.enable_connection()
52
        self.publisher.cleanup()
53

  
54
    def get_publisher(self, domain):
55
        publisher_class = get_publisher_class()
56
        publisher = publisher_class.create_publisher()
57
        if domain not in publisher.get_tenants():
58
            raise CommandError('unknown tenant')
59

  
60
        publisher.app_dir = os.path.join(publisher.app_dir, domain)
61
        publisher.set_config()
62
        return publisher
63

  
64
    def setup_connection(self, **kwargs):
65
        options = {}
66
        for k in ['host', 'port', 'database', 'user', 'password']:
67
            if k in kwargs:
68
                options[k] = kwargs.get(k)
69
        self.publisher.cfg['postgresql'] = options
70

  
71
    def enable_connection(self):
72
        if not self.publisher.site_options.has_option('options', 'postgresql'):
73
            self.publisher.site_options.set('options', 'postgresql', 'true')
74
            options_file = os.path.join(self.publisher.app_dir, 'site-options.cfg')
75
            self.publisher.site_options.write(open(options_file, 'w'))
76

  
77
    def store_users(self):
78
        errors = []
79
        print('converting users')
80
        sql.do_user_table()
81
        for i, user_id in enumerate(User.keys()):
82
            user = User.get(user_id)
83
            user.__class__ = sql.SqlUser
84
            try:
85
                user.store()
86
            except AssertionError:
87
                errors.append((user, traceback.format_exc()))
88
        sql.SqlUser.fix_sequences()
89

  
90
        if errors:
91
            error_log = open('error_user.log', 'w')
92
            for user, trace in errors:
93
                error_log.write('user_id {}'.format(user.id))
94
                error_log.write(trace)
95
                error_log.write('-'*80)
96
            error_log.close()
97
            print('There were some errors, see error_user.log for details.')
98

  
99
    def store_forms(self):
100
        errors = []
101
        for formdef in FormDef.select():
102
            print('converting %s' % formdef.name)
103
            sql.do_formdef_tables(formdef, rebuild_views=True,
104
                                  rebuild_global_views=True)
105
            data_class = formdef.data_class(mode='files')
106

  
107
            # load all objects a first time, to allow the migrate() code to be
108
            # run and the eventual changes properly saved.
109
            for id in data_class.keys():
110
                formdata = data_class.get(id)
111
            delattr(sys.modules['formdef'], formdef.url_name.title())
112

  
113
            # once this is done, reload and store everything in postgresql
114
            sql_data_class = formdef.data_class(mode='sql')
115
            for i, id in enumerate(data_class.keys()):
116
                formdata = data_class.get(id)
117
                formdata._formdef = formdef
118
                formdata._evolution = formdata.evolution
119
                formdata.__class__ = sql_data_class
120
                try:
121
                    formdata.store()
122
                except AssertionError:
123
                    errors.append((formdata, traceback.format_exc()))
124
            sql_data_class.fix_sequences()
125

  
126
        sql.do_tracking_code_table()
127
        sql.do_session_table()
128
        sql.do_meta_table()
129

  
130
        if errors:
131
            error_log = open('error_formdata.log', 'w')
132
            for formdata, trace in errors:
133
                error_log.write('{} {}'.format(formdata.fromdef, formdata.id))
134
                error_log.write(trace)
135
                error_log.write('-'*80)
136
            error_log.close()
137
            print('There were some errors, see error_formdata.log.')
0
-