views.py 21 KB
Newer Older
1
2
3
"""
    Routes
"""
4
import copy
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
5
import io
Pierre Dittgen's avatar
Pierre Dittgen committed
6
import itertools
7
import json
Christophe Benz's avatar
Christophe Benz committed
8
import logging
9
import subprocess
Christophe Benz's avatar
Christophe Benz committed
10
import tempfile
11
from datetime import datetime
12
from operator import itemgetter
13
from pathlib import Path
14
from urllib.parse import quote_plus, urlencode
15

Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
16
import requests
17
import tableschema
18
from backports.datetime_fromisoformat import MonkeyPatch
19
from commonmark import commonmark
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
20
from flask import make_response, redirect, render_template, request, url_for
21
from validata_core import compute_badge, messages
22

Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
23
24
import tabulator

25
from . import app, config, ui_config, table_schema_catalog, schema_from_url
26
from .ui_util import flash_error, flash_warning
27
from .validata_util import ValidataResource, URLValidataResource, UploadedFileValidataResource
28

29
30
MonkeyPatch.patch_fromisoformat()

Christophe Benz's avatar
Christophe Benz committed
31
32
log = logging.getLogger(__name__)

33

34
35
36
class SchemaInstance():
    """Handly class to handle schema information"""

Pierre Dittgen's avatar
Pierre Dittgen committed
37
    def __init__(self, url=None, name=None, ref=None, spec=None, versions=None):
38
39
40
41
42
43
        """This function is not intended to be called directly
        but via from_parameters() static method!"""
        self.url = url
        self.name = name
        self.ref = ref
        self.spec = spec
Pierre Dittgen's avatar
Pierre Dittgen committed
44
        self.versions = versions
45
46
47
48
49

    @staticmethod
    def from_parameters(parameter_dict, table_schema_catalog):
        """Initializes schema instance from requests dict and tableschema catalog (for name ref)
        """
Pierre Dittgen's avatar
Pierre Dittgen committed
50
        schema_url, schema_name, schema_ref, versions = None, None, None, None
51
52
53
54
55
56
57
58
59
60
61

        # From schema_url
        if 'schema_url' in parameter_dict:
            schema_url = parameter_dict["schema_url"]

        # from schema_name (and schema_ref)
        elif 'schema_name' in parameter_dict:
            schema_name = parameter_dict['schema_name']
            schema_ref = parameter_dict.get('schema_ref')

            # Unknown schema name?
62
            table_schema_reference = table_schema_catalog.reference_by_name.get(schema_name)
63
64
65
            if table_schema_reference is None:
                return None

Pierre Dittgen's avatar
Pierre Dittgen committed
66
67
68
69
70
            # Git refs
            if table_schema_reference:
                versions = table_schema_reference.get_refs()
            options = {'ref': schema_ref} if schema_ref else {}
            schema_url = table_schema_reference.get_schema_url(**options)
71
72
73
74
75

        # else???
        else:
            return None

Pierre Dittgen's avatar
Pierre Dittgen committed
76
        return SchemaInstance(schema_url, schema_name, schema_ref, schema_from_url(schema_url), versions)
77
78
79
80
81
82
83
84
85
86
87
88

    def request_parameters(self):
        if self.name:
            return {
                'schema_name': self.name,
                'schema_ref': '' if self.ref is None else self.ref
            }
        return {
            'schema_url': self.url
        }


89
def extract_source_data(source: ValidataResource, preview_rows_nb=5):
90
    """ Computes table preview """
91
92
93

    def stringify(val):
        """ Transform value into string """
Pierre Dittgen's avatar
Pierre Dittgen committed
94
        return '' if val is None else str(val)
95

96
97
    header = None
    rows = []
Pierre Dittgen's avatar
Pierre Dittgen committed
98
    nb_rows = 0
99

100
101
102
103
104
    # if source.format == "csv":
    #     options['delimiter'] = csv_helpers.detect_dialect(source.source, format=source.format, scheme=source.scheme,
    #                                                       custom_loaders=custom_loaders).delimiter
    tabulator_source, tabulator_options = source.build_tabulator_stream_args()
    with tabulator.Stream(tabulator_source, tabulator_options) as stream:
105
106
        for row in stream:
            if header is None:
107
                header = ['' if v is None else v for v in row]
108
            else:
109
                rows.append(list(map(stringify, row)))
Pierre Dittgen's avatar
Pierre Dittgen committed
110
                nb_rows += 1
111
    preview_rows_nb = min(preview_rows_nb, nb_rows)
112
113
    return {'header': header,
            'rows_nb': nb_rows,
114
115
116
            'data_rows': rows,
            'preview_rows_nb': preview_rows_nb,
            'preview_rows': rows[:preview_rows_nb]}
117
118


Pierre Dittgen's avatar
Pierre Dittgen committed
119
120
def improve_errors(errors):
    """Add context to errors, converts markdown content to HTML"""
121

Pierre Dittgen's avatar
Pierre Dittgen committed
122
123
    def improve_err(err):
        """Adds context info based on row-nb presence and converts content to HTML"""
124

Pierre Dittgen's avatar
Pierre Dittgen committed
125
126
127
128
        # Context
        update_keys = {
            'context': 'body' if 'row-number' in err and not err['row-number'] is None else 'table',
        }
Pierre Dittgen's avatar
Pierre Dittgen committed
129

Pierre Dittgen's avatar
Pierre Dittgen committed
130
        # markdown to HTML (with default values for 'title' and 'content')
Pierre Dittgen's avatar
Pierre Dittgen committed
131

Pierre Dittgen's avatar
Pierre Dittgen committed
132
133
134
        # Set default title if no title
        if not 'title' in err:
            update_keys['title'] = '[{}]'.format(err['code'])
Pierre Dittgen's avatar
Pierre Dittgen committed
135

Pierre Dittgen's avatar
Pierre Dittgen committed
136
137
138
139
        # Convert message to markdown only if no content
        # => for pre-checks errors
        if 'message' in err and not 'content' in err:
            update_keys['message'] = commonmark(err['message'])
Pierre Dittgen's avatar
Pierre Dittgen committed
140

Pierre Dittgen's avatar
Pierre Dittgen committed
141
142
143
        # Else, default message
        elif not 'message' in err or err['message'] is None:
            update_keys['message'] = '[{}]'.format(err['code'])
Pierre Dittgen's avatar
Pierre Dittgen committed
144

Pierre Dittgen's avatar
Pierre Dittgen committed
145
146
147
        # Message content
        md_content = '*content soon available*' if not 'content' in err else err['content']
        update_keys['content'] = commonmark(md_content)
148

Pierre Dittgen's avatar
Pierre Dittgen committed
149
        return {**err, **update_keys}
150

Pierre Dittgen's avatar
Pierre Dittgen committed
151
    return list(map(improve_err, errors))
152
153


Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
154
def create_validata_ui_report(validata_core_report, schema_dict):
155
156
157
158
159
160
    """ Creates an error report easier to handle and display in templates:
        - only one table
        - errors are contextualized
        - error-counts is ok
        - errors are grouped by lines
        - errors are separated into "structure" and "body"
161
        - error messages are improved
162
    """
Pierre Dittgen's avatar
Pierre Dittgen committed
163
    report = copy.deepcopy(validata_core_report)
164
165
166
167
168
169
170
171
172

    # One table is enough
    del report['table-count']
    report['table'] = report['tables'][0]
    del report['tables']
    del report['table']['error-count']
    del report['table']['time']
    del report['table']['valid']
    del report['valid']
173
174
175
    # use _ instead of - to ease information picking in jinja2 template
    report['table']['row_count'] = report['table']['row-count']

Pierre Dittgen's avatar
Pierre Dittgen committed
176
177
178
179
    # Handy col_count info
    headers = report['table'].get('headers', [])
    report['table']['col_count'] = len(headers)

Pierre Dittgen's avatar
Pierre Dittgen committed
180
    # Computes column info
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
181
182
    fields_dict = {f['name']: (f.get('title', 'titre non défini'), f.get('description', ''))
                   for f in schema_dict.get('fields', [])}
Pierre Dittgen's avatar
Pierre Dittgen committed
183
184
185
    report['table']['headers_title'] = [fields_dict[h][0] if h in fields_dict else 'colonne inconnue' for h in headers]
    report['table']['headers_description'] = [fields_dict[h][1]
                                              if h in fields_dict else 'Cette colonne n\'est pas définie dans le schema' for h in headers]
186

187
    # Provide better (french) messages
Pierre Dittgen's avatar
Pierre Dittgen committed
188
189
    errors = improve_errors(report['table']['errors'])
    del report['table']['errors']
190

191
192
193
194
195
196
197
    # Count errors
    report['error_count'] = len(errors)
    del report['error-count']

    # Then group them in 2 groups : structure and body
    report['table']['errors'] = {'structure': [], 'body': []}
    for err in errors:
198
        if err['tag'] == 'structure':
199
200
201
202
            report['table']['errors']['structure'].append(err)
        else:
            report['table']['errors']['body'].append(err)

Pierre Dittgen's avatar
Pierre Dittgen committed
203
    # Checks if there are structure errors different to invalid-column-delimiter
Pierre Dittgen's avatar
Pierre Dittgen committed
204
205
206
    structure_errors = report['table']['errors']['structure']
    report['table']['do_display_body_errors'] = len(structure_errors) == 0 or \
        all(err['code'] == 'invalid-column-delimiter' for err in structure_errors)
207

Pierre Dittgen's avatar
Pierre Dittgen committed
208
209
210
211
212
213
214
215
    # Checks if a column comparison is needed
    header_errors = ('missing-headers', 'extra-headers', 'wrong-headers-order')
    structure_errors = [{**err, 'in_column_comp': err['code'] in header_errors} for err in structure_errors]
    report['table']['errors']['structure'] = structure_errors
    column_comparison_needed = any(err['in_column_comp'] == True for err in structure_errors)
    column_comparison_table = []
    if column_comparison_needed:
        column_comparison_table = []
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
216
        field_names = [f['name'] for f in schema_dict.get('fields', [])]
217
        has_case_errors = False
Pierre Dittgen's avatar
Pierre Dittgen committed
218
219
        for t in itertools.zip_longest(headers, field_names, fillvalue=''):
            status = 'ok' if t[0] == t[1] else 'ko'
220
221
            if not has_case_errors and status == 'ko' and t[0].lower() == t[1].lower():
                has_case_errors = True
Pierre Dittgen's avatar
Pierre Dittgen committed
222
            column_comparison_table.append((*t, status))
223
224
225
226
227
        info = {}
        info['table'] = column_comparison_table
        info['has_missing'] = len(headers) < len(field_names)
        info['has_case_errors'] = has_case_errors
        report['table']['column_comparison_info'] = info
Pierre Dittgen's avatar
Pierre Dittgen committed
228
229
    report['table']['column_comparison_needed'] = column_comparison_needed

Pierre Dittgen's avatar
Pierre Dittgen committed
230
231
232
233
    # Group body errors by row id
    rows = []
    current_row_id = 0
    for err in report['table']['errors']['body']:
234
235
        if not 'row-number' in err:
            print('ERR', err)
Pierre Dittgen's avatar
Pierre Dittgen committed
236
237
238
239
240
241
242
243
244
245
246
247
248
        row_id = err['row-number']
        del err['row-number']
        del err['context']
        if row_id != current_row_id:
            current_row_id = row_id
            rows.append({'row_id': current_row_id, 'errors': {}})

        column_id = err.get('column-number')
        if column_id is not None:
            del err['column-number']
            rows[-1]['errors'][column_id] = err
        else:
            rows[-1]['errors']['row'] = err
Pierre Dittgen's avatar
Pierre Dittgen committed
249
    report['table']['errors']['body_by_rows'] = rows
Pierre Dittgen's avatar
Pierre Dittgen committed
250

251
252
253
254
255
256
257
258
259
    # Sort by error names in statistics
    stats = report['table']['error-stats']
    code_title_map = messages.ERROR_MESSAGE_DEFAULT_TITLE
    for key in ('structure-errors', 'value-errors'):
        # convert dict into tuples with french title instead of error code
        # and sorts by title
        stats[key]['count-by-code'] = sorted([(code_title_map.get(k, k), v) for k, v in stats[key]['count-by-code'].items()],
                                             key=itemgetter(0))

Pierre Dittgen's avatar
Pierre Dittgen committed
260
261
262
    return report


Pierre Dittgen's avatar
Pierre Dittgen committed
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
def compute_badge_message_and_color(badge):
    """Computes message and color from badge information"""
    structure = badge['structure']
    body = badge.get('body')

    # Bad structure, stop here
    if structure == 'KO':
        return (
            'structure invalide', 'red')

    # No body error
    if body == 'OK':
        return ('structure invalide', 'orange') if structure == 'WARN' else ('valide', 'green')

    # else compute quality ratio percent
Christophe Benz's avatar
Christophe Benz committed
278
    p = (1 - badge['error-ratio']) * 100.0
Pierre Dittgen's avatar
Pierre Dittgen committed
279
280
281
282
283
284
285
286
    msg = 'cellules valides : {:.1f}%'.format(p)
    return (msg, 'red') if body == 'KO' else (msg, 'orange')


def get_badge_url_and_message(badge):
    """Gets badge url from badge information"""

    msg, color = compute_badge_message_and_color(badge)
287
288
    return ('{}static/v1.svg?label=Validata&message={}&color={}'.format(
        config.SHIELDS_IO_BASE_URL, quote_plus(msg), color), msg)
Pierre Dittgen's avatar
Pierre Dittgen committed
289
290


291
def validate(schema_instance: SchemaInstance, source: ValidataResource):
292
293
    """ Validate source and display report """

294
    # Validation is done through http call to validata-api
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
295
296
297
298
    if config.API_VALIDATE_ENDPOINT is None:
        flash_error("No Validate endpoint defined :-(")
        return redirect(url_for("custom_validator"))
    api_url = config.API_VALIDATE_ENDPOINT
299
300

    # Useful to receive response as JSON
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
301
302
    headers = {"Accept": "application/json"}

Pierre Dittgen's avatar
Pierre Dittgen committed
303
    try:
304
        if source.type == 'url':
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
305
            params = {
306
                "schema": schema_instance.url,
307
                "url": source.url,
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
308
309
310
311
            }
            req = requests.get(api_url, params=params, headers=headers)

        else:
312
            files = {'file': (source.filename, source.build_reader())}
313
            data = {"schema": schema_instance.url}
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
314
315
            req = requests.post(api_url, data=data, files=files, headers=headers)

Pierre Dittgen's avatar
Pierre Dittgen committed
316
317
318
319
320
321
322
        # 400
        if req.status_code == 400:
            json_response = req.json()
            flash_error("Une erreur est survenue durant la validation: {}"
                        .format(json_response.get('message')))
            return redirect(url_for("home"))

Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
323
        if not req.ok:
Pierre Dittgen's avatar
Pierre Dittgen committed
324
            flash_error("Un erreur s'est produite côté serveur :-(")
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
325
            return redirect(url_for("home"))
326

Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
327
328
329
        json_response = req.json()
        validata_core_report = json_response['report']
        schema_dict = json_response['schema']
330

Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
331
332
333
334
    except requests.ConnectionError as err:
        logging.exception(err)
        flash_error(str(err))
        return redirect(url_for('home'))
335

Pierre Dittgen's avatar
Pierre Dittgen committed
336
    # Computes badge from report and badge configuration
337
    badge = compute_badge(validata_core_report, config.BADGE_CONFIG)
Pierre Dittgen's avatar
Pierre Dittgen committed
338
339
    badge_url, badge_msg = get_badge_url_and_message(badge)

340
341
342
343
344
345
    source_errors = [err for err in validata_core_report['tables'][0]['errors'] if err['code'] == 'source-error']
    if source_errors:
        err = source_errors[0]
        msg = "l'encodage du fichier est invalide. Veuillez le corriger" if 'charmap' in err[
            'message'] else err['message']
        flash_error('Erreur de source : {}'.format(msg))
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
346
        return redirect(url_for('custom_validator'))
347

348
    source_data = extract_source_data(source)
349

Pierre Dittgen's avatar
Pierre Dittgen committed
350
    # handle report date
351
    report_datetime = datetime.fromisoformat(validata_core_report['date']).astimezone()
352

Pierre Dittgen's avatar
Pierre Dittgen committed
353
    # Enhance validata_core_report
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
354
    validata_report = create_validata_ui_report(validata_core_report, schema_dict)
Pierre Dittgen's avatar
Pierre Dittgen committed
355

Pierre Dittgen's avatar
Pierre Dittgen committed
356
    # Display report to the user
357
    validator_form_url = compute_validation_form_url(schema_instance)
Pierre Dittgen's avatar
Pierre Dittgen committed
358
    schema_info, validator_title = compute_schema_info(schema_instance.spec, schema_instance.url)
359
    pdf_report_url = url_for('pdf_report')+'?'+urlencode(schema_instance.request_parameters())
360
    return render_template('validation_report.html', title='Rapport de validation',
361
362
                           schema_info=schema_info, report=validata_report,
                           pdf_report_url=pdf_report_url,
363
                           validation_date=report_datetime.strftime('le %d/%m/%Y à %Hh%M'),
364
                           source=source, source_type=source.type, source_data=source_data,
Pierre Dittgen's avatar
Pierre Dittgen committed
365
                           schema_current_version=schema_instance.ref,
366
                           print_mode=request.args.get('print', 'false') == 'true',
Pierre Dittgen's avatar
Pierre Dittgen committed
367
                           badge_url=badge_url, badge_msg=badge_msg,
368
                           report_str=json.dumps(validata_report, sort_keys=True, indent=2),
Pierre Dittgen's avatar
Pierre Dittgen committed
369
                           breadcrumbs=[{'url': url_for('home'), 'title': 'Accueil'},
Pierre Dittgen's avatar
Pierre Dittgen committed
370
                                        {'url': validator_form_url, 'title': validator_title},
Pierre Dittgen's avatar
Pierre Dittgen committed
371
                                        ])
372
373


374
375
def bytes_data(f):
    """ Gets bytes data from Werkzeug FileStorage instance """
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
376
    iob = io.BytesIO()
377
378
379
380
381
    f.save(iob)
    iob.seek(0)
    return iob.getvalue()


382
383
384
385
386
def hydrate_ui_config(ui_config, table_schema_catalog):

    hydrated_ui_config = ui_config.copy()

    table_schema_ref_list = []
387
    for ref in table_schema_catalog.references:
388
        table_schema = schema_from_url(ref.get_schema_url())
389
        info = {
390
            "name": ref.name,
391
392
393
394
395
396
397
398
399
            **{k: v for k, v in table_schema.descriptor.items() if k != 'fields'}
        }
        table_schema_ref_list.append(info)

    # TODO: change this hard-coded affectation
    hydrated_ui_config['sections'][0]['catalog'] = table_schema_ref_list

    return hydrated_ui_config

400
401
402
403
404
405
# Routes


@app.route('/')
def home():
    """ Home page """
Christophe Benz's avatar
Christophe Benz committed
406
    flash_warning('Ce service est fourni en mode beta - certains problèmes peuvent subsister - nous mettons tout en œuvre pour améliorer son fonctionnement en continu.')
407
    return render_template('home.html', title='Accueil', config=hydrate_ui_config(ui_config, table_schema_catalog))
408
409


Pierre Dittgen's avatar
Pierre Dittgen committed
410
411
@app.route('/pdf')
def pdf_report():
412
    """PDF report generation"""
413
    err_prefix = 'Erreur de génération du rapport PDF'
414
415
416

    url_param = request.args.get('url')
    if not url_param:
417
        flash_error(err_prefix + ': URL non fournie')
Pierre Dittgen's avatar
Pierre Dittgen committed
418
        return redirect(url_for('home'))
419

420
421
422
    schema_instance = SchemaInstance.from_parameters(request.args, table_schema_catalog)
    if schema_instance is None:
        flash_error(err_prefix + ': Information de schema non fournie')
Pierre Dittgen's avatar
Pierre Dittgen committed
423
424
        return redirect(url_for('home'))

425
426
427
428
429
430
431
432
433
434
435
    # Compute pdf url report
    base_url = url_for('custom_validator', _external=True)
    parameter_dict = {
        'input': 'url',
        'print': 'true',
        'url': url_param,
        **schema_instance.request_parameters()
    }
    validation_url = base_url + '?' + urlencode(parameter_dict)

    # Create temp file to save validation report
Pierre Dittgen's avatar
Pierre Dittgen committed
436
    with tempfile.NamedTemporaryFile(prefix='validata_{}_report_'.format(datetime.now().timestamp()), suffix='.pdf') as tmpfile:
Christophe Benz's avatar
Christophe Benz committed
437
        tmp_pdf_report = Path(tmpfile.name)
438

439
    # Use chromium headless to generate PDF from validation report page
440
441
442
443
    cmd = ['chromium', '--headless', '--disable-gpu',
           '--print-to-pdf={}'.format(str(tmp_pdf_report)), validation_url]
    result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    if result.returncode != 0:
444
445
        flash_error(err_prefix)
        log.error("Command %r returned an error: %r", cmd, result.stdout.decode('utf-8'))
446
447
        if tmp_pdf_report.exists():
            tmp_pdf_report.unlink()
Pierre Dittgen's avatar
Pierre Dittgen committed
448
        return redirect(url_for('home'))
449

450
    # Send PDF report
Pierre Dittgen's avatar
Pierre Dittgen committed
451
    pdf_filename = 'Rapport de validation {}.pdf'.format(datetime.now().strftime('%d-%m-%Y %Hh%M'))
Christophe Benz's avatar
Christophe Benz committed
452
    response = make_response(tmp_pdf_report.read_bytes())
Pierre Dittgen's avatar
Pierre Dittgen committed
453
    response.headers.set('Content-disposition', 'attachment', filename=pdf_filename)
454
455
456
457
458
459
460
461
    response.headers.set('Content-type', 'application/pdf')
    response.headers.set('Content-length', tmp_pdf_report.stat().st_size)

    tmp_pdf_report.unlink()

    return response


Pierre Dittgen's avatar
Pierre Dittgen committed
462
def compute_schema_info(table_schema: tableschema.Schema, schema_url):
Pierre Dittgen's avatar
Pierre Dittgen committed
463
    """Factor code for validator form page"""
464
465

    schema_info = {k: v for k, v in table_schema.descriptor.items() if k != 'fields'}
Pierre Dittgen's avatar
Pierre Dittgen committed
466
    schema_info['url'] = schema_url
467
468
469
470
471
472
473
474
475
476
    title = "Schéma « {} »".format(schema_info.get('title'))
    return schema_info, title


def compute_validation_form_url(schema_instance: SchemaInstance):
    """Computes validation form url with schema URL parameter"""
    url = url_for('custom_validator')
    param_list = ['{}={}'.format(k, quote_plus(v))
                  for k, v in schema_instance.request_parameters().items()]
    return "{}?{}".format(url, '&'.join(param_list))
Pierre Dittgen's avatar
Pierre Dittgen committed
477
478


479
@app.route('/table_schema', methods=['GET', 'POST'])
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
480
def custom_validator():
481
    """Validator form"""
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
482

483
    # Check that validata-api URL is set
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
484
485
486
487
488
489
    if config.API_VALIDATE_ENDPOINT is None:
        flash_error("URL de connexion à l'API non indiquée :-(")
        return redirect(url_for('home'))

    if request.method == 'GET':

490
491
        # input is a hidden form parameter to know
        # if this is the initial page display or if the validation has been asked for
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
492
        input_param = request.args.get('input')
493
494

        # url of resource to be validated
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
495
496
        url_param = request.args.get("url")

497
498
499
        schema_instance = SchemaInstance.from_parameters(request.args, table_schema_catalog)
        if schema_instance is None:
            flash_error("Aucun schéma passé en paramètre")
Pierre Dittgen's avatar
Pierre Dittgen committed
500
501
            return redirect(url_for('home'))

Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
502
503
        # First form display
        if input_param is None:
Pierre Dittgen's avatar
Pierre Dittgen committed
504
505
506

            schema_versions = schema_instance.versions
            schema_info, title = compute_schema_info(schema_instance.spec, schema_instance.url)
Pierre Dittgen's avatar
Pierre Dittgen committed
507
            return render_template('validation_form.html', title=title,
Pierre Dittgen's avatar
Pierre Dittgen committed
508
509
                                   schema_info=schema_info, schema_versions=schema_versions,
                                   schema_current_version=schema_instance.ref,
510
                                   schema_params=schema_instance.request_parameters(),
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
511
512
513
514
515
516
                                   breadcrumbs=[{'url': url_for('home'), 'title': 'Accueil'}, ])

        # Process URL
        else:
            if url_param is None or url_param == '':
                flash_error("Vous n'avez pas indiqué d'url à valider")
517
                return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
518
            try:
519
                return validate(schema_instance, URLValidataResource(url_param))
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
520
521
522
            except tabulator.exceptions.FormatError as e:
                flash_error('Erreur : Format de ressource non supporté')
                log.info(e)
523
                return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
524
525
526
            except tabulator.exceptions.HTTPError as e:
                flash_error('Erreur : impossible d\'accéder au fichier source en ligne')
                log.info(e)
527
                return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
528
529

    else:  # POST
530
531
532

        schema_instance = SchemaInstance.from_parameters(request.form, table_schema_catalog)
        if schema_instance is None:
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
533
            flash_error('Aucun schéma défini')
Pierre Dittgen's avatar
Pierre Dittgen committed
534
535
536
537
538
            return redirect(url_for('home'))

        input_param = request.form.get('input')
        if input_param is None:
            flash_error("Vous n'avez pas indiqué de fichier à valider")
539
            return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
540
541
542
543
544
545

        # File validation
        if input_param == 'file':
            f = request.files.get('file')
            if f is None:
                flash_warning("Vous n'avez pas indiqué de fichier à valider")
546
                return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
547

548
            return validate(schema_instance, UploadedFileValidataResource(f.filename, bytes_data(f)))
Pierre Dittgen's avatar
wip    
Pierre Dittgen committed
549
550

        return 'Bizarre, vous avez dit bizarre ?'