Projet

Général

Profil

0002-move-convert_to_sql-to-management-command-20410.patch

Christophe Siraut, 27 juin 2018 10:51

Télécharger (10,5 ko)

Voir les différences:

Subject: [PATCH 2/3] move convert_to_sql to management command (#20410)

 tests/test_convert_to_sql.py                  | 121 +++++++++++++++++++++
 wcs/ctl/management/commands/convert_to_sql.py | 151 ++++++++++++++++++++++++++
 2 files changed, 272 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 collections
2
import os
3
import random
4
import psycopg2
5
import pytest
6

  
7
from django.core.management import get_commands
8
from django.core.management import call_command
9
from django.core.management.base import CommandError
10

  
11
from wcs.sql import cleanup_connection
12
from wcs.formdef import FormDef
13
from wcs.fields import BoolField
14

  
15
from utilities import clean_temporary_pub
16
from utilities import create_temporary_pub
17
from utilities import force_connections_close
18
from test_api import local_user
19

  
20

  
21
@pytest.fixture
22
def formdeffix():
23
    FormDef.wipe()
24
    formdef = FormDef()
25
    formdef.name = 'testform'
26
    formdef.description = 'plop'
27
    formdef.fields = [BoolField(id='1')]
28
    formdef.store()
29

  
30
    data_class = formdef.data_class()
31
    for value in (True, True, True, False):
32
        formdata = data_class()
33
        formdata.data = {'1': value}
34
        formdata.store()
35

  
36
    return formdef
37

  
38

  
39
@pytest.fixture(scope='module')
40
def cursor():
41
    conn = psycopg2.connect(user=os.environ.get('USER'))
42
    conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
43
    cur = conn.cursor()
44
    yield cur
45
    cur.close()
46

  
47

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

  
56

  
57
@pytest.fixture()
58
def pub(request):
59
    pub = create_temporary_pub(sql_mode=False)
60
    yield pub
61
    clean_temporary_pub()
62

  
63

  
64
def test_command_exists():
65
    assert 'convert_to_sql' in get_commands()
66

  
67

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

  
73

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

  
79

  
80
def test_database_does_not_exist(pub):
81
    new_database = 'test_%s' % random.randint(1000, 9999)
82
    with pytest.raises(psycopg2.OperationalError) as excinfo:
83
        call_command('convert_to_sql', '-d', 'example.net', '--database', new_database)
84
    assert 'exist' in excinfo.value.message  # works for english + french postgresql
85

  
86

  
87
def test_already_migrated_fails():
88
    pub = create_temporary_pub(sql_mode=True)
89
    with pytest.raises(CommandError) as excinfo:
90
        call_command('convert_to_sql', '-d', 'example.net', '--database', 'foobar')
91
    assert excinfo.value.message == 'tenant already using postgresql'
92
    cleanup_connection()
93
    force_connections_close()
94
    clean_temporary_pub()
95

  
96

  
97
def test_setup_database(pub, database):
98
    call_command('convert_to_sql', '-d', 'example.net', '--database', database)
99
    pub.set_config()
100
    assert pub.cfg['postgresql'].get('database') == database
101

  
102

  
103
def test_migration(pub, database):
104
    assert 'postgresql' not in pub.cfg
105
    call_command('convert_to_sql', '-d', 'example.net',
106
                    '--database', database)
107
    pub.set_config()
108
    assert 'postgresql' in pub.cfg
109

  
110

  
111
def test_data_is_migrated(pub, database, local_user, formdeffix):
112
    pub.load_site_options()
113
    assert not pub.site_options.has_option('options', 'postgresql')
114
    call_command('convert_to_sql', '-d', 'example.net', '--database', database)
115
    pub.load_site_options()
116
    assert pub.site_options.has_option('options', 'postgresql')
117
    assert len(pub.user_class.get_users_with_name_identifier('0123456789')) == 1
118
    formdefs = FormDef.select()
119
    assert len(formdefs) == 1
120
    data_class = formdefs[0].data_class(mode='sql')
121
    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-2018  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 StringIO
20
import traceback
21

  
22
from django.core.management.base import BaseCommand
23
from django.core.management.base import CommandError
24

  
25
from qommon.publisher import get_publisher_class
26

  
27
from wcs import sql
28
from wcs.formdef import FormDef
29
from wcs.qommon.storage import atomic_write
30
from wcs.users import User
31

  
32

  
33
class Command(BaseCommand):
34

  
35
    help = 'Setup postgresql connection parameters and migrate existing objects.'
36

  
37
    def add_arguments(self, parser):
38
        parser.add_argument('-d', '--domain', required=True)
39
        parser.add_argument('--database', required=True)
40
        parser.add_argument('--host')
41
        parser.add_argument('--port', type=int)
42
        parser.add_argument('--user')
43
        parser.add_argument('--password')
44

  
45
    def handle(self, **options):
46
        self.publisher = self.get_publisher(options['domain'])
47
        if self.publisher.is_using_postgresql():
48
            raise CommandError('tenant already using postgresql')
49

  
50
        self.setup_connection(**options)
51
        sql.get_connection(new=True)
52
        self.store_users()
53
        self.store_forms()
54
        self.publisher.write_cfg()
55
        self.enable_connection()
56
        self.publisher.cleanup()
57

  
58
    def get_publisher(self, domain):
59
        publisher_class = get_publisher_class()
60
        publisher = publisher_class.create_publisher()
61
        if domain not in publisher.get_tenants():
62
            raise CommandError('unknown tenant')
63

  
64
        publisher.app_dir = os.path.join(publisher.app_dir, domain)
65
        publisher.set_config()
66
        return publisher
67

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

  
75
    def enable_connection(self):
76
        if not self.publisher.site_options.has_option('options', 'postgresql'):
77
            self.publisher.site_options.set('options', 'postgresql', 'true')
78
            options_file = os.path.join(self.publisher.app_dir, 'site-options.cfg')
79
            stringio = StringIO.StringIO()
80
            self.publisher.site_options.write(stringio)
81
            atomic_write(options_file, stringio.getvalue())
82

  
83
    def store_users(self):
84
        errors = []
85
        print('converting users')
86
        sql.do_user_table()
87
        count = User.count()
88
        for i, user_id in enumerate(User.keys()):
89
            user = User.get(user_id)
90
            user.__class__ = sql.SqlUser
91
            try:
92
                user.store()
93
            except AssertionError:
94
                errors.append((user, traceback.format_exc()))
95
            self.update_progress(100*i/count)
96
        sql.SqlUser.fix_sequences()
97

  
98
        if errors:
99
            error_log = open('error_user.log', 'w')
100
            for user, trace in errors:
101
                error_log.write('user_id %s' % user.id)
102
                error_log.write(trace)
103
                error_log.write('-'*80)
104
            error_log.close()
105
            print('There were some errors, see error_user.log for details.')
106

  
107
    def store_forms(self):
108
        errors = []
109
        for formdef in FormDef.select():
110
            print('converting %s' % formdef.name)
111
            sql.do_formdef_tables(formdef, rebuild_views=True,
112
                                  rebuild_global_views=True)
113
            data_class = formdef.data_class(mode='files')
114
            count = data_class.count()
115

  
116
            # load all objects a first time, to allow the migrate() code to be
117
            # run and the eventual changes properly saved.
118
            for id in data_class.keys():
119
                formdata = data_class.get(id)
120
            delattr(sys.modules['formdef'], formdef.url_name.title())
121

  
122
            # once this is done, reload and store everything in postgresql
123
            sql_data_class = formdef.data_class(mode='sql')
124
            for i, id in enumerate(data_class.keys()):
125
                formdata = data_class.get(id)
126
                formdata._formdef = formdef
127
                formdata._evolution = formdata.evolution
128
                formdata.__class__ = sql_data_class
129
                try:
130
                    formdata.store()
131
                except AssertionError:
132
                    errors.append((formdata, traceback.format_exc()))
133
                self.update_progress(100*i/count)
134
            sql_data_class.fix_sequences()
135

  
136
        sql.do_tracking_code_table()
137
        sql.do_session_table()
138
        sql.do_meta_table()
139

  
140
        if errors:
141
            error_log = open('error_formdata.log', 'w')
142
            for formdata, trace in errors:
143
                error_log.write('%s %s' % (formdata.fromdef, formdata.id))
144
                error_log.write(trace)
145
                error_log.write('-'*80)
146
            error_log.close()
147
            print('There were some errors, see error_formdata.log.')
148

  
149
    def update_progress(self, progress, num_columns=120):
150
        sys.stdout.write('[%s] %s%%\r' % (
151
            ('#'*((num_columns-10)*progress/100)).ljust(num_columns-15), progress))
0
-