views.py 21.9 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
8
import logging
9
import subprocess
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
import tabulator
19
from backports.datetime_fromisoformat import MonkeyPatch
20
from commonmark import commonmark
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
21
from flask import make_response, redirect, render_template, request, url_for
22

23
from validata_core import compute_badge, messages
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
24

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

29 30
MonkeyPatch.patch_fromisoformat()

31 32
log = logging.getLogger(__name__)

33

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

37
    def __init__(self, url=None, name=None, ref=None, spec=None, versions=None, doc_url=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
44
        self.versions = versions
45
        self.doc_url = doc_url
46 47

    @staticmethod
48
    def from_parameters(parameter_dict, schema_catalog_map):
49 50
        """Initializes schema instance from requests dict and tableschema catalog (for name ref)
        """
51
        schema_url, schema_name, schema_ref, versions, doc_url = None, None, None, None, None
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')

62 63 64 65 66 67 68 69 70 71 72 73
            # Check schema name
            chunks = schema_name.split('.')
            if len(chunks) != 2:
                return None

            section_code, ref_name = chunks

            # Look for schema catalog first
            table_schema_catalog = schema_catalog_map.get(section_code)
            if table_schema_catalog is None:
                return None

74
            # Unknown schema name?
75
            table_schema_reference = table_schema_catalog.reference_by_name.get(ref_name)
76 77 78
            if table_schema_reference is None:
                return None

79 80 81
            # Git refs
            if table_schema_reference:
                versions = table_schema_reference.get_refs()
82
                doc_url = table_schema_reference.doc_url
83 84
            options = {'ref': schema_ref} if schema_ref else {}
            schema_url = table_schema_reference.get_schema_url(**options)
85 86 87 88 89

        # else???
        else:
            return None

90 91
        return SchemaInstance(url=schema_url, name=schema_name, ref=schema_ref,
                              spec=schema_from_url(schema_url), versions=versions, doc_url=doc_url)
92 93 94 95 96 97 98 99 100 101 102 103

    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
        }


104
def extract_source_data(source: ValidataResource, preview_rows_nb=5):
105
    """ Computes table preview """
106 107 108

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

111 112
    header = None
    rows = []
Pierre Dittgen's avatar
Pierre Dittgen committed
113
    nb_rows = 0
114

115 116 117 118
    # 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()
Pierre Dittgen's avatar
Pierre Dittgen committed
119
    with tabulator.Stream(tabulator_source, **tabulator_options) as stream:
120 121
        for row in stream:
            if header is None:
122
                header = ['' if v is None else v for v in row]
123
            else:
124
                rows.append(list(map(stringify, row)))
Pierre Dittgen's avatar
Pierre Dittgen committed
125
                nb_rows += 1
126
    preview_rows_nb = min(preview_rows_nb, nb_rows)
127 128
    return {'header': header,
            'rows_nb': nb_rows,
129 130 131
            'data_rows': rows,
            'preview_rows_nb': preview_rows_nb,
            'preview_rows': rows[:preview_rows_nb]}
132 133


Pierre Dittgen's avatar
Pierre Dittgen committed
134 135
def improve_errors(errors):
    """Add context to errors, converts markdown content to HTML"""
136

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

Pierre Dittgen's avatar
Pierre Dittgen committed
140 141 142 143
        # 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
144

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

Pierre Dittgen's avatar
Pierre Dittgen committed
147 148 149
        # Set default title if no title
        if not 'title' in err:
            update_keys['title'] = '[{}]'.format(err['code'])
Pierre Dittgen's avatar
Pierre Dittgen committed
150

Pierre Dittgen's avatar
Pierre Dittgen committed
151 152 153 154
        # 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
155

Pierre Dittgen's avatar
Pierre Dittgen committed
156 157 158
        # 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
159

Pierre Dittgen's avatar
Pierre Dittgen committed
160 161 162
        # Message content
        md_content = '*content soon available*' if not 'content' in err else err['content']
        update_keys['content'] = commonmark(md_content)
163

Pierre Dittgen's avatar
Pierre Dittgen committed
164
        return {**err, **update_keys}
165

Pierre Dittgen's avatar
Pierre Dittgen committed
166
    return list(map(improve_err, errors))
167 168


Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
169
def create_validata_ui_report(validata_core_report, schema_dict):
170 171 172 173 174 175
    """ 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"
176
        - error messages are improved
177
    """
Pierre Dittgen's avatar
Pierre Dittgen committed
178
    report = copy.deepcopy(validata_core_report)
179 180 181 182 183 184 185 186 187

    # 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']
188 189 190
    # 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
191 192 193 194
    # Handy col_count info
    headers = report['table'].get('headers', [])
    report['table']['col_count'] = len(headers)

Pierre Dittgen's avatar
Pierre Dittgen committed
195
    # Computes column info
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
196 197
    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
198 199 200
    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]
201

202
    # Provide better (french) messages
Pierre Dittgen's avatar
Pierre Dittgen committed
203 204
    errors = improve_errors(report['table']['errors'])
    del report['table']['errors']
205

206 207 208 209 210 211 212
    # 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:
213
        if err['tag'] == 'structure':
214 215 216 217
            report['table']['errors']['structure'].append(err)
        else:
            report['table']['errors']['body'].append(err)

Pierre Dittgen's avatar
Pierre Dittgen committed
218
    # Checks if there are structure errors different to invalid-column-delimiter
Pierre Dittgen's avatar
Pierre Dittgen committed
219 220 221
    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)
222

Pierre Dittgen's avatar
Pierre Dittgen committed
223 224 225 226 227 228 229 230
    # 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
231
        field_names = [f['name'] for f in schema_dict.get('fields', [])]
232
        has_case_errors = False
Pierre Dittgen's avatar
Pierre Dittgen committed
233 234
        for t in itertools.zip_longest(headers, field_names, fillvalue=''):
            status = 'ok' if t[0] == t[1] else 'ko'
235 236
            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
237
            column_comparison_table.append((*t, status))
238 239 240 241 242
        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
243 244
    report['table']['column_comparison_needed'] = column_comparison_needed

Pierre Dittgen's avatar
Pierre Dittgen committed
245 246 247 248
    # Group body errors by row id
    rows = []
    current_row_id = 0
    for err in report['table']['errors']['body']:
249 250
        if not 'row-number' in err:
            print('ERR', err)
Pierre Dittgen's avatar
Pierre Dittgen committed
251 252 253 254 255 256 257 258 259 260 261 262 263
        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
264
    report['table']['errors']['body_by_rows'] = rows
Pierre Dittgen's avatar
Pierre Dittgen committed
265

266 267 268 269 270 271 272 273 274
    # 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
275 276 277
    return report


Pierre Dittgen's avatar
Pierre Dittgen committed
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
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
293
    p = (1 - badge['error-ratio']) * 100.0
Pierre Dittgen's avatar
Pierre Dittgen committed
294 295 296 297 298 299 300 301
    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)
302 303
    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
304 305


306
def validate(schema_instance: SchemaInstance, source: ValidataResource):
307 308
    """ Validate source and display report """

309
    # Validation is done through http call to validata-api
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
310 311 312 313
    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
314 315

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

318
    try:
319
        if source.type == 'url':
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
320
            params = {
321
                "schema": schema_instance.url,
322
                "url": source.url,
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
323 324 325 326
            }
            req = requests.get(api_url, params=params, headers=headers)

        else:
327
            files = {'file': (source.filename, source.build_reader())}
328
            data = {"schema": schema_instance.url}
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
329 330 331 332 333
            req = requests.post(api_url, data=data, files=files, headers=headers)
    except requests.ConnectionError as err:
        logging.exception(err)
        flash_error(str(err))
        return redirect(url_for('home'))
334

335 336 337 338 339 340 341
    if not req.ok:
        flash_error("Une erreur s'est produite côté serveur :-(")
        return redirect(compute_validation_form_url(schema_instance))

    json_response = req.json()
    validata_core_report = json_response['report']

Pierre Dittgen's avatar
Pierre Dittgen committed
342
    # Computes badge from report and badge configuration
343
    badge = compute_badge(validata_core_report, config.BADGE_CONFIG)
Pierre Dittgen's avatar
Pierre Dittgen committed
344 345
    badge_url, badge_msg = get_badge_url_and_message(badge)

346 347 348 349 350
    source_errors = [
        err
        for err in validata_core_report['tables'][0]['errors']
        if err['code'] in {'source-error', 'unknown-csv-dialect'}
    ]
351 352 353 354 355
    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
356
        return redirect(url_for('custom_validator'))
357

358
    source_data = extract_source_data(source)
359

Pierre Dittgen's avatar
Pierre Dittgen committed
360
    # handle report date
361
    report_datetime = datetime.fromisoformat(validata_core_report['date']).astimezone()
362

Pierre Dittgen's avatar
Pierre Dittgen committed
363
    # Enhance validata_core_report
364
    validata_report = create_validata_ui_report(validata_core_report, schema_instance.spec)
Pierre Dittgen's avatar
Pierre Dittgen committed
365

Pierre Dittgen's avatar
Pierre Dittgen committed
366
    # Display report to the user
367
    validator_form_url = compute_validation_form_url(schema_instance)
368
    schema_info, validator_title = compute_schema_info(schema_instance.spec, schema_instance.url)
369
    pdf_report_url = url_for('pdf_report')+'?'+urlencode(schema_instance.request_parameters())
370
    return render_template('validation_report.html', title='Rapport de validation',
371 372
                           schema_info=schema_info, report=validata_report,
                           pdf_report_url=pdf_report_url,
373
                           validation_date=report_datetime.strftime('le %d/%m/%Y à %Hh%M'),
374
                           source=source, source_data=source_data,
375
                           schema_current_version=schema_instance.ref,
376
                           doc_url=schema_instance.doc_url,
377
                           print_mode=request.args.get('print', 'false') == 'true',
Pierre Dittgen's avatar
Pierre Dittgen committed
378
                           badge_url=badge_url, badge_msg=badge_msg,
379
                           report_str=json.dumps(validata_report, sort_keys=True, indent=2),
Pierre Dittgen's avatar
Pierre Dittgen committed
380
                           breadcrumbs=[{'url': url_for('home'), 'title': 'Accueil'},
Pierre Dittgen's avatar
Pierre Dittgen committed
381
                                        {'url': validator_form_url, 'title': validator_title},
Pierre Dittgen's avatar
Pierre Dittgen committed
382
                                        ])
383 384


385 386
def bytes_data(f):
    """ Gets bytes data from Werkzeug FileStorage instance """
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
387
    iob = io.BytesIO()
388 389 390 391 392
    f.save(iob)
    iob.seek(0)
    return iob.getvalue()


393
def homepage_config_with_schema_metadata(ui_config, schema_catalog_map):
394 395 396 397 398 399 400 401 402 403 404 405
    """Replace catalog url within ui_config by schema references
    containing schema metadata properties"""

    extended_ui_config = ui_config.copy()
    for section in extended_ui_config['sections']:
        section_code = section['code']
        if section_code not in schema_catalog_map:
            continue
        schema_catalog = schema_catalog_map[section_code]
        schema_list = []
        for ref in schema_catalog.references:
            # Loads default table schema for each schema reference
406
            table_schema = schema_from_url(ref.get_schema_url(check_exists=False))
407 408 409 410 411 412 413
            schema_list.append({
                'name': '{}.{}'.format(section_code, ref.name),
                # Extracts title, description, ...
                **extract_schema_metadata(table_schema)
            })
        section['catalog'] = schema_list
    return extended_ui_config
414

415 416 417 418 419 420
# Routes


@app.route('/')
def home():
    """ Home page """
421
    home_config = homepage_config_with_schema_metadata(config.HOMEPAGE_CONFIG, schema_catalog_map)
422
    return render_template('home.html', title='Accueil', config=home_config)
423 424


Pierre Dittgen's avatar
Pierre Dittgen committed
425 426
@app.route('/pdf')
def pdf_report():
427
    """PDF report generation"""
428
    err_prefix = 'Erreur de génération du rapport PDF'
429 430 431

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

435
    schema_instance = SchemaInstance.from_parameters(request.args, schema_catalog_map)
436 437
    if schema_instance is None:
        flash_error(err_prefix + ': Information de schema non fournie')
Pierre Dittgen's avatar
Pierre Dittgen committed
438 439
        return redirect(url_for('home'))

440 441 442 443 444 445 446 447 448 449 450
    # 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
451
    with tempfile.NamedTemporaryFile(prefix='validata_{}_report_'.format(datetime.now().timestamp()), suffix='.pdf') as tmpfile:
452
        tmp_pdf_report = Path(tmpfile.name)
453

454
    # Use chromium headless to generate PDF from validation report page
455 456 457 458
    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:
459 460
        flash_error(err_prefix)
        log.error("Command %r returned an error: %r", cmd, result.stdout.decode('utf-8'))
461 462
        if tmp_pdf_report.exists():
            tmp_pdf_report.unlink()
Pierre Dittgen's avatar
Pierre Dittgen committed
463
        return redirect(url_for('home'))
464

465
    # Send PDF report
Pierre Dittgen's avatar
Pierre Dittgen committed
466
    pdf_filename = 'Rapport de validation {}.pdf'.format(datetime.now().strftime('%d-%m-%Y %Hh%M'))
467
    response = make_response(tmp_pdf_report.read_bytes())
Pierre Dittgen's avatar
Pierre Dittgen committed
468
    response.headers.set('Content-disposition', 'attachment', filename=pdf_filename)
469 470 471 472 473 474 475 476
    response.headers.set('Content-type', 'application/pdf')
    response.headers.set('Content-length', tmp_pdf_report.stat().st_size)

    tmp_pdf_report.unlink()

    return response


477 478 479 480 481
def extract_schema_metadata(table_schema: tableschema.Schema):
    """Gets author, contibutor, version...metadata from schema header"""
    return {k: v for k, v in table_schema.descriptor.items() if k != 'fields'}


482
def compute_schema_info(table_schema: tableschema.Schema, schema_url):
Pierre Dittgen's avatar
Pierre Dittgen committed
483
    """Factor code for validator form page"""
484

485 486 487 488 489 490 491 492
    # Schema URL + schema metadata info
    schema_info = {
        'url': schema_url,
        **extract_schema_metadata(table_schema)
    }
    meta_title = schema_info.get('title')

    title = "Schéma « {} »".format(meta_title if meta_title else '...')
493 494 495 496 497 498 499 500 501
    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
502 503


504
@app.route('/table-schema', methods=['GET', 'POST'])
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
505
def custom_validator():
506
    """Validator form"""
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
507

508
    # Check that validata-api URL is set
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
509 510 511 512 513 514
    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':

515 516
        # 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
517
        input_param = request.args.get('input')
518 519

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

522
        schema_instance = SchemaInstance.from_parameters(request.args, schema_catalog_map)
523 524
        if schema_instance is None:
            flash_error("Aucun schéma passé en paramètre")
Pierre Dittgen's avatar
Pierre Dittgen committed
525 526
            return redirect(url_for('home'))

Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
527 528
        # First form display
        if input_param is None:
529 530 531

            schema_versions = schema_instance.versions
            schema_info, title = compute_schema_info(schema_instance.spec, schema_instance.url)
Pierre Dittgen's avatar
Pierre Dittgen committed
532
            return render_template('validation_form.html', title=title,
533 534
                                   schema_info=schema_info, schema_versions=schema_versions,
                                   schema_current_version=schema_instance.ref,
535
                                   doc_url=schema_instance.doc_url,
536
                                   schema_params=schema_instance.request_parameters(),
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
537 538 539 540 541 542
                                   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")
543
                return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
544
            try:
545
                return validate(schema_instance, URLValidataResource(url_param))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
546 547 548
            except tabulator.exceptions.FormatError as e:
                flash_error('Erreur : Format de ressource non supporté')
                log.info(e)
549
                return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
550 551 552
            except tabulator.exceptions.HTTPError as e:
                flash_error('Erreur : impossible d\'accéder au fichier source en ligne')
                log.info(e)
553
                return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
554 555

    else:  # POST
556

557
        schema_instance = SchemaInstance.from_parameters(request.form, schema_catalog_map)
558
        if schema_instance is None:
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
559
            flash_error('Aucun schéma défini')
Pierre Dittgen's avatar
Pierre Dittgen committed
560 561 562 563 564
            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")
565
            return redirect(compute_validation_form_url(schema_instance))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
566 567 568 569 570 571

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

574
            return validate(schema_instance, UploadedFileValidataResource(f.filename, bytes_data(f)))
Pierre Dittgen's avatar
wip  
Pierre Dittgen committed
575 576

        return 'Bizarre, vous avez dit bizarre ?'