Commit 8f981bba authored by Pierre Dittgen's avatar Pierre Dittgen

Website structure is ok. Time to really validate and display CSV data

parent 79dddda6
Pipeline #5 failed with stages
__pycache__/
......@@ -3,4 +3,21 @@
Next generation validata ui, rid of goodtables-ui constraints and using improved goodtables-py.
## Dependencies
- [Flask](http://flask.pocoo.org/)
\ No newline at end of file
- [Flask](http://flask.pocoo.org/)
We recommand using [virtualenv](https://virtualenv.pypa.io/en/stable/) to install dependencies inside application sandbox
Install the project dependencies:
```bash
pip install -e .
```
## Development
Start the web server:
```bash
export FLASK_APP=validata-ui-next
export FLASK_ENV=development
flask run
* Running on http://127.0.0.1:5000/
```
\ No newline at end of file
#!/usr/bin/env python3
"""Run validata ui"""
from setuptools import setup
classifiers = """\
Development Status :: 4 - Beta
Intended Audience :: Developers
Operating System :: OS Independent
Programming Language :: Python
Topic :: Software Development :: Libraries :: Python Modules
"""
setup(
name='validata_ui_next',
version='0.0.1',
author='Pierre Dittgen',
author_email='pierre.dittgen@jailbreak.paris',
classifiers=[classifier for classifier in classifiers.split('\n') if classifier],
description=__doc__,
packages=['validata_ui_next'],
include_package_data=True,
install_requires=[
'flask',
'requests',
'ujson'
]
)
#!/usr/bin/env python3
from pathlib import Path
from flask import Flask
from validata_ui_next.validate import ValidatorHelper
# Schemas settings
schemas_config = {
"scdl-adresses": {
"schema_json_url": "https://git.opendatafrance.net/scdl/adresses/raw/master/schema-scdl-adresses.json",
"custom_checks_json_url": "https://git.opendatafrance.net/scdl/adresses/raw/master/custom-checks.json"
},
"scdl-deliberations": {
"schema_json_url": "https://git.opendatafrance.net/scdl/deliberations/raw/master/schema.json",
"custom_checks_json_url": "https://git.opendatafrance.net/scdl/deliberations/raw/master/custom-checks.json"
},
"scdl-marches-publics": {
"schema_json_url": "https://git.opendatafrance.net/scdl/marches-publics/raw/pdi/schema.json",
"custom_checks_json_url": "https://git.opendatafrance.net/scdl/marches-publics/raw/pdi/custom-checks.json"
},
"scdl-prenoms": {
"schema_json_url": "https://github.com/CharlesNepote/liste-prenoms-nouveaux-nes/raw/v1.1.1/prenom-schema.json",
# "custom_checks_json_url": "https://git.opendatafrance.net/scdl/prenoms/raw/master/custom-checks.json"
},
"scdl-subventions": {
"schema_json_url": "https://git.opendatafrance.net/scdl/subventions/raw/master/schema.json",
"custom_checks_json_url": "https://git.opendatafrance.net/scdl/subventions/raw/master/custom-checks.json"
},
}
ValidatorHelper.init(schemas_config, Path('/tmp'))
# Flask things
app = Flask(__name__)
app.secret_key = 'MyPr3ci0u5$€cr€t'
import validata_ui_next.views
#!/usr/bin/env python3
"""
Validata-ui-next entry-point
"""
import json
import os
from collections import OrderedDict
from pathlib import Path
from flask import Flask, flash, redirect, render_template, request, url_for
app = Flask(__name__)
app.secret_key = 'MyPr3ci0u5$€cr€t'
schemas_config = {
"scdl-prenoms": {
"schema_json_url": "https://git.openqqq.com/",
"custom_checks_json_url": ""}
}
VALIDATOR_JSON_FILE = 'static/data/scdl-validators.json'
class Validators:
validator_map = OrderedDict()
@classmethod
def init(cls, validators_file):
""" Static init """
val_list = json.load(open(validators_file))
cls.validator_map = OrderedDict([(val['code'], val) for val in val_list])
@classmethod
def list(cls):
""" Validator list """
return [val for _, val in cls.validator_map.items()]
@classmethod
def is_valid_code(cls, code):
""" Test if validator code exist """
return code in cls.validator_map
@classmethod
def is_val_enabled(cls, val):
ret = val and val.get('enable', 'true') == 'true'
return ret
@classmethod
def info(cls, code):
""" Return information on validator """
return cls.validator_map.get(code)
# TODO externalize util classes
def validate_url(url):
return "TODO: valider l'url "+url
# return render_template('validation_report.html')
def validate_file(file):
return "TODO: valider le fichier "+file
# return render_template('validation_report.html')
def flash_error(msg):
flash(msg, 'danger')
def flash_warning(msg):
flash(msg, 'warning')
def flash_success(msg):
flash(msg, 'success')
def flash_info(msg):
flash(msg, 'info')
Validators.init(VALIDATOR_JSON_FILE)
@app.route('/')
def home():
validators = [{**val, 'enabled': Validators.is_val_enabled(val)} for val in Validators.list()]
return render_template('home.html', title='Accueil', validators=validators)
@app.route('/about')
def about():
return render_template('about.html', title='À propos',
breadcrumbs=[{'url': url_for('home'), 'title': 'Accueil'}, ])
@app.route('/validators')
def validators():
return redirect(url_for('home'))
@app.route('/validators/<val_code>', methods=['GET', 'POST'])
def scdl_validator(val_code):
if not Validators.is_valid_code(val_code):
flash_error('Validateur [{}] inconnu'.format(val_code))
return redirect(url_for('home'))
if not Validators.is_val_enabled(Validators.info(val_code)):
flash_error('Pas de validateur pour le schéma [{}]'.format(val_code))
return redirect(url_for('home'))
if request.method == 'GET':
val_info = Validators.info(val_code)
input_param = request.args.get('input')
# First form display
if input_param is None or input_param not in ('url', 'example'):
return render_template('validator.html', title=val_info['name'],
val_info=val_info,
breadcrumbs=[{'url': url_for('home'), 'title': 'Accueil'}, ])
# Process URL
else:
url = request.args.get('url')
if url is None or url == '':
flash_error("Vous n'avez pas indiqué d'url à valider")
return redirect(url_for('scdl_validator', val_code=val_code))
return validate_url(url)
else: # POST
input_param = request.form.get('input')
if input_param is None:
flash_error('Source non définie')
return redirect(url_for('scdl_validator', val_code=val_code))
# 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")
return redirect(url_for('scdl_validator', val_code=val_code))
f.save(os.path.join('/tmp', f.filename))
return validate_file(f.filename)
return 'Bizarre, vous avez dit bizarre ?'
if __name__ == '__main__':
app.run()
/*!
* Bootstrap Grid v4.1.3 (https://getbootstrap.com/)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
@-ms-viewport {
width: device-width;
}
html {
box-sizing: border-box;
-ms-overflow-style: scrollbar;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
.container {
width: 100%;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
@media (min-width: 576px) {
.container {
max-width: 540px;
}
}
@media (min-width: 768px) {
.container {
max-width: 720px;
}
}
@media (min-width: 992px) {
.container {
max-width: 960px;
}
}
@media (min-width: 1200px) {
.container {
max-width: 1140px;
}
}
.container-fluid {
width: 100%;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
.row {
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px;
}
.no-gutters {
margin-right: 0;
margin-left: 0;
}
.no-gutters > .col,
.no-gutters > [class*="col-"] {
padding-right: 0;
padding-left: 0;
}
.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,
.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,
.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,
.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,
.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,
.col-xl-auto {
position: relative;
width: 100%;
min-height: 1px;
padding-right: 15px;
padding-left: 15px;
}
.col {
-ms-flex-preferred-size: 0;
flex-basis: 0;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 100%;
}
.col-auto {
-ms-flex: 0 0 auto;
flex: 0 0 auto;
width: auto;
max-width: none;
}
.col-1 {
-ms-flex: 0 0 8.333333%;
flex: 0 0 8.333333%;
max-width: 8.333333%;
}
.col-2 {
-ms-flex: 0 0 16.666667%;
flex: 0 0 16.666667%;
max-width: 16.666667%;
}
.col-3 {
-ms-flex: 0 0 25%;
flex: 0 0 25%;
max-width: 25%;
}
.col-4 {
-ms-flex: 0 0 33.333333%;
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
.col-5 {
-ms-flex: 0 0 41.666667%;
flex: 0 0 41.666667%;
max-width: 41.666667%;
}
.col-6 {
-ms-flex: 0 0 50%;
flex: 0 0 50%;
max-width: 50%;
}
.col-7 {
-ms-flex: 0 0 58.333333%;
flex: 0 0 58.333333%;
max-width: 58.333333%;
}
.col-8 {
-ms-flex: 0 0 66.666667%;
flex: 0 0 66.666667%;
max-width: 66.666667%;
}
.col-9 {
-ms-flex: 0 0 75%;
flex: 0 0 75%;
max-width: 75%;
}
.col-10 {
-ms-flex: 0 0 83.333333%;
flex: 0 0 83.333333%;
max-width: 83.333333%;
}
.col-11 {
-ms-flex: 0 0 91.666667%;
flex: 0 0 91.666667%;
max-width: 91.666667%;
}
.col-12 {
-ms-flex: 0 0 100%;
flex: 0 0 100%;
max-width: 100%;
}
.order-first {
-ms-flex-order: -1;
order: -1;
}
.order-last {
-ms-flex-order: 13;
order: 13;
}
.order-0 {
-ms-flex-order: 0;
order: 0;
}
.order-1 {
-ms-flex-order: 1;
order: 1;
}
.order-2 {
-ms-flex-order: 2;
order: 2;
}
.order-3 {
-ms-flex-order: 3;
order: 3;
}
.order-4 {
-ms-flex-order: 4;
order: 4;
}
.order-5 {
-ms-flex-order: 5;
order: 5;
}
.order-6 {
-ms-flex-order: 6;
order: 6;
}
.order-7 {
-ms-flex-order: 7;
order: 7;
}
.order-8 {
-ms-flex-order: 8;
order: 8;
}
.order-9 {
-ms-flex-order: 9;
order: 9;
}
.order-10 {
-ms-flex-order: 10;
order: 10;
}
.order-11 {
-ms-flex-order: 11;
order: 11;
}
.order-12 {
-ms-flex-order: 12;
order: 12;
}
.offset-1 {
margin-left: 8.333333%;
}
.offset-2 {
margin-left: 16.666667%;
}
.offset-3 {
margin-left: 25%;
}
.offset-4 {
margin-left: 33.333333%;
}
.offset-5 {
margin-left: 41.666667%;
}
.offset-6 {
margin-left: 50%;
}
.offset-7 {
margin-left: 58.333333%;
}
.offset-8 {
margin-left: 66.666667%;
}
.offset-9 {
margin-left: 75%;
}
.offset-10 {
margin-left: 83.333333%;
}
.offset-11 {
margin-left: 91.666667%;
}
@media (min-width: 576px) {
.col-sm {
-ms-flex-preferred-size: 0;
flex-basis: 0;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 100%;
}
.col-sm-auto {
-ms-flex: 0 0 auto;
flex: 0 0 auto;
width: auto;
max-width: none;
}
.col-sm-1 {
-ms-flex: 0 0 8.333333%;
flex: 0 0 8.333333%;
max-width: 8.333333%;
}
.col-sm-2 {
-ms-flex: 0 0 16.666667%;
flex: 0 0 16.666667%;
max-width: 16.666667%;
}
.col-sm-3 {
-ms-flex: 0 0 25%;
flex: 0 0 25%;
max-width: 25%;
}
.col-sm-4 {
-ms-flex: 0 0 33.333333%;
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
.col-sm-5 {
-ms-flex: 0 0 41.666667%;
flex: 0 0 41.666667%;
max-width: 41.666667%;
}
.col-sm-6 {
-ms-flex: 0 0 50%;
flex: 0 0 50%;
max-width: 50%;
}
.col-sm-7 {
-ms-flex: 0 0 58.333333%;
flex: 0 0 58.333333%;
max-width: 58.333333%;
}
.col-sm-8 {
-ms-flex: 0 0 66.666667%;
flex: 0 0 66.666667%;
max-width: 66.666667%;
}
.col-sm-9 {
-ms-flex: 0 0 75%;
flex: 0 0 75%;
max-width: 75%;
}
.col-sm-10 {
-ms-flex: 0 0 83.333333%;
flex: 0 0 83.333333%;
max-width: 83.333333%;
}
.col-sm-11 {
-ms-flex: 0 0 91.666667%;
flex: 0 0 91.666667%;
max-width: 91.666667%;
}
.col-sm-12 {
-ms-flex: 0 0 100%;
flex: 0 0 100%;
max-width: 100%;
}
.order-sm-first {
-ms-flex-order: -1;
order: -1;
}
.order-sm-last {
-ms-flex-order: 13;
order: 13;
}
.order-sm-0 {
-ms-flex-order: 0;
order: 0;