views.py 20.5 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
Pierre Dittgen's avatar
Pierre Dittgen committed
21
22
from validata_core import compute_badge, csv_helpers, messages
from validata_core.loaders import custom_loaders
23

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

26
from . import app, config, ui_config, table_schema_catalog, schema_from_url
27
28
from .ui_util import flash_error, flash_warning
from .validata_util import ValidataSource
29

30
31
MonkeyPatch.patch_fromisoformat()

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

34

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class SchemaInstance():
    """Handly class to handle schema information"""

    def __init__(self, url=None, name=None, ref=None, spec=None):
        """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

    @staticmethod
    def from_parameters(parameter_dict, table_schema_catalog):
        """Initializes schema instance from requests dict and tableschema catalog (for name ref)
        """
        schema_url, schema_name, schema_ref = None, None, None

        # 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?
            table_schema_reference = table_schema_catalog.references.get(schema_name)
            if table_schema_reference is None:
                return None

            schema_url = table_schema_reference.get_schema_url()

        # else???
        else:
            return None

        return SchemaInstance(schema_url, schema_name, schema_ref, schema_from_url(schema_url))

    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
        }


85
def extract_source_data(source: ValidataSource, preview_rows_nb=5):
86
    """ Computes table preview """
87
88
89

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

92
93
    header = None
    rows = []
Pierre Dittgen's avatar
Pierre Dittgen committed
94
    nb_rows = 0
95

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


Pierre Dittgen's avatar
Pierre Dittgen committed
116
117
def improve_errors(errors):
    """Add context to errors, converts markdown content to HTML"""
118

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

Pierre Dittgen's avatar
Pierre Dittgen committed
122
123
124
125
        # 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
126

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

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

Pierre Dittgen's avatar
Pierre Dittgen committed
133
134
135
136
        # 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
137

Pierre Dittgen's avatar
Pierre Dittgen committed
138
139
140
        # 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
141

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

Pierre Dittgen's avatar
Pierre Dittgen committed
146
        return {**err, **update_keys}
147

Pierre Dittgen's avatar
Pierre Dittgen committed
148
    return list(map(improve_err, errors))
149
150


Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
151
def create_validata_ui_report(validata_core_report, schema_dict):
152
153
154
155
156
157
    """ 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"
158
        - error messages are improved
159
    """
Pierre Dittgen's avatar
Pierre Dittgen committed
160
    report = copy.deepcopy(validata_core_report)
161
162
163
164
165
166
167
168
169

    # 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']
170
171
172
    # 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
173
174
175
176
    # Handy col_count info
    headers = report['table'].get('headers', [])
    report['table']['col_count'] = len(headers)

Pierre Dittgen's avatar
Pierre Dittgen committed
177
    # Computes column info
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
178
179
    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
180
181
182
    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]
183

184
    # Provide better (french) messages
Pierre Dittgen's avatar
Pierre Dittgen committed
185
186
    errors = improve_errors(report['table']['errors'])
    del report['table']['errors']
187

188
189
190
191
192
193
194
    # 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:
195
        if err['tag'] == 'structure':
196
197
198
199
            report['table']['errors']['structure'].append(err)
        else:
            report['table']['errors']['body'].append(err)

Pierre Dittgen's avatar
Pierre Dittgen committed
200
    # Checks if there are structure errors different to invalid-column-delimiter
Pierre Dittgen's avatar
Pierre Dittgen committed
201
202
203
    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)
204

Pierre Dittgen's avatar
Pierre Dittgen committed
205
206
207
208
209
210
211
212
    # 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
213
        field_names = [f['name'] for f in schema_dict.get('fields', [])]
214
        has_case_errors = False
Pierre Dittgen's avatar
Pierre Dittgen committed
215
216
        for t in itertools.zip_longest(headers, field_names, fillvalue=''):
            status = 'ok' if t[0] == t[1] else 'ko'
217
218
            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
219
            column_comparison_table.append((*t, status))
220
221
222
223
224
        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
225
226
    report['table']['column_comparison_needed'] = column_comparison_needed

Pierre Dittgen's avatar
Pierre Dittgen committed
227
228
229
230
    # Group body errors by row id
    rows = []
    current_row_id = 0
    for err in report['table']['errors']['body']:
231
232
        if not 'row-number' in err:
            print('ERR', err)
Pierre Dittgen's avatar
Pierre Dittgen committed
233
234
235
236
237
238
239
240
241
242
243
244
245
        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
246
    report['table']['errors']['body_by_rows'] = rows
Pierre Dittgen's avatar
Pierre Dittgen committed
247

248
249
250
251
252
253
254
255
256
    # 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
257
258
259
    return report


Pierre Dittgen's avatar
Pierre Dittgen committed
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
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
275
    p = (1 - badge['error-ratio']) * 100.0
Pierre Dittgen's avatar
Pierre Dittgen committed
276
277
278
279
280
281
282
283
    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)
284
285
    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
286
287


288
def validate(schema_instance: SchemaInstance, source: ValidataSource):
289
290
    """ Validate source and display report """

291
    # Validation is done through http call to validata-api
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
292
293
294
295
    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
296
297

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

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

        else:
            files = {'file': (source.name, io.BytesIO(source.source))}
310
            data = {"schema": schema_instance.url}
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
311
312
            req = requests.post(api_url, data=data, files=files, headers=headers)

Pierre Dittgen's avatar
Pierre Dittgen committed
313
314
315
316
317
318
319
        # 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
320
        if not req.ok:
Pierre Dittgen's avatar
Pierre Dittgen committed
321
            flash_error("Un erreur s'est produite côté serveur :-(")
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
322
            return redirect(url_for("home"))
323

Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
324
325
326
        json_response = req.json()
        validata_core_report = json_response['report']
        schema_dict = json_response['schema']
327

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

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

337
338
339
340
341
342
    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
343
        return redirect(url_for('custom_validator'))
344

345
    source_data = extract_source_data(source)
346

Pierre Dittgen's avatar
Pierre Dittgen committed
347
    # handle report date
348
    report_datetime = datetime.fromisoformat(validata_core_report['date']).astimezone()
349

Pierre Dittgen's avatar
Pierre Dittgen committed
350
    # Enhance validata_core_report
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
351
    validata_report = create_validata_ui_report(validata_core_report, schema_dict)
Pierre Dittgen's avatar
Pierre Dittgen committed
352

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


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


378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
def hydrate_ui_config(ui_config, table_schema_catalog):

    hydrated_ui_config = ui_config.copy()

    table_schema_ref_list = []
    for name, ref in sorted(table_schema_catalog.references.items(), key=itemgetter(0)):
        table_schema = ref.get_table_schema()
        info = {
            "name": name,
            **{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

396
397
398
399
400
401
# Routes


@app.route('/')
def home():
    """ Home page """
Christophe Benz's avatar
Christophe Benz committed
402
    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.')
403
    return render_template('home.html', title='Accueil', config=hydrate_ui_config(ui_config, table_schema_catalog))
404
405


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

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

416
417
418
    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
419
420
        return redirect(url_for('home'))

421
422
423
424
425
426
427
428
429
430
431
    # 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
432
    with tempfile.NamedTemporaryFile(prefix='validata_{}_report_'.format(datetime.now().timestamp()), suffix='.pdf') as tmpfile:
Christophe Benz's avatar
Christophe Benz committed
433
        tmp_pdf_report = Path(tmpfile.name)
434

435
    # Use chromium headless to generate PDF from validation report page
436
437
438
439
    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:
440
441
        flash_error(err_prefix)
        log.error("Command %r returned an error: %r", cmd, result.stdout.decode('utf-8'))
442
443
        if tmp_pdf_report.exists():
            tmp_pdf_report.unlink()
Pierre Dittgen's avatar
Pierre Dittgen committed
444
        return redirect(url_for('home'))
445

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

    tmp_pdf_report.unlink()

    return response


458
def compute_schema_info(table_schema: tableschema.Schema):
Pierre Dittgen's avatar
Pierre Dittgen committed
459
    """Factor code for validator form page"""
460
461
462
463
464
465
466
467
468
469
470
471

    schema_info = {k: v for k, v in table_schema.descriptor.items() if k != 'fields'}
    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
472
473


474
@app.route('/table_schema', methods=['GET', 'POST'])
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
475
def custom_validator():
476
    """Validator form"""
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
477

478
    # Check that validata-api URL is set
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
479
480
481
482
483
484
    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':

485
486
        # 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
487
        input_param = request.args.get('input')
488
489

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

492
493
494
        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
495
496
            return redirect(url_for('home'))

Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
497
498
        # First form display
        if input_param is None:
499
            schema_info, title = compute_schema_info(schema_instance.spec)
Pierre Dittgen's avatar
Pierre Dittgen committed
500
            return render_template('validation_form.html', title=title,
501
502
                                   schema_info=schema_info,
                                   schema_params=schema_instance.request_parameters(),
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
503
504
505
506
507
508
                                   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")
509
                return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
510
            try:
511
                return validate(schema_instance, ValidataSource('url', url_param, url_param))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
512
513
514
            except tabulator.exceptions.FormatError as e:
                flash_error('Erreur : Format de ressource non supporté')
                log.info(e)
515
                return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
516
517
518
            except tabulator.exceptions.HTTPError as e:
                flash_error('Erreur : impossible d\'accéder au fichier source en ligne')
                log.info(e)
519
                return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
520
521

    else:  # POST
522
523
524

        schema_instance = SchemaInstance.from_parameters(request.form, table_schema_catalog)
        if schema_instance is None:
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
525
            flash_error('Aucun schéma défini')
Pierre Dittgen's avatar
Pierre Dittgen committed
526
527
528
529
530
            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")
531
            return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
532
533
534
535
536
537

        # 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")
538
                return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
539
540

            b_content = bytes_data(f)
541
            return validate(schema_instance, ValidataSource('file', f.filename, b_content))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
542
543

        return 'Bizarre, vous avez dit bizarre ?'