Begin with model

This commit is contained in:
2023-06-05 12:06:56 +02:00
parent 7c2bcbb2f0
commit ae04ea5426
29 changed files with 806 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

0
app/__init__.py Normal file
View File

BIN
app/app.db Normal file

Binary file not shown.

39
app/app.py Normal file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python
from flask import Flask
from flask_bootstrap import Bootstrap5
from flask_login import LoginManager
from .config import Config
from .models import User, db
from .results.main import results
from .main import main
bootstrap = Bootstrap5()
login_manager = LoginManager()
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
def make_shell_context():
return {"db": db, "User": User}
def create_app():
app = Flask(__name__)
bootstrap.init_app(app)
# app.register_blueprint(results)
app.config.from_object(Config)
db.init_app(app)
login_manager.init_app(app)
login_manager.login_view = "main.login"
app.jinja_env.auto_reload = True
app.register_blueprint(results, url_prefix="/results")
app.register_blueprint(main)
app.shell_context_processor(make_shell_context)
with app.app_context():
db.create_all()
return app

17
app/config.py Normal file
View File

@@ -0,0 +1,17 @@
import os
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, ".env"))
class Config(object):
FRAMEWORKS = ["bootstrap", "bulma"]
FRAMEWORK = os.environ.get("FRAMEWORK") or FRAMEWORKS[0]
OUTPUT = os.environ.get("OUTPUT") or "local" # local or docker
TEMPLATES_AUTO_RELOAD = True
SECRET_KEY = os.environ.get("SECRET_KEY") or "really-hard-to-guess-key"
SQLALCHEMY_DATABASE_URI = os.environ.get(
"DATABASE_URL"
) or "sqlite:///" + os.path.join(basedir, "app.db")
SQLALCHEMY_TRACK_MODIFICATIONS = False

27
app/forms.py Normal file
View File

@@ -0,0 +1,27 @@
from flask_wtf import FlaskForm
from wtforms import (
StringField,
PasswordField,
BooleanField,
SubmitField,
)
from wtforms.validators import (
DataRequired,
Length,
)
class LoginForm(FlaskForm):
username = StringField(
"Username", validators=[DataRequired(), Length(1, 20)]
)
password = PasswordField(
"Password", validators=[DataRequired(), Length(4, 150)]
)
remember_me = BooleanField("Remember me")
submit = SubmitField()
class BenchmarkSelect(FlaskForm):
submit = SubmitField("Select")

58
app/main.py Normal file
View File

@@ -0,0 +1,58 @@
from flask import (
Blueprint,
render_template,
url_for,
flash,
redirect,
request,
)
from flask_login import login_user, current_user, logout_user, login_required
from werkzeug.urls import url_parse
from .forms import LoginForm
from .models import User, Benchmark
main = Blueprint("main", __name__)
@main.route("/")
@main.route("/index")
def index():
if current_user.is_authenticated:
if current_user.admin:
benchmarks = Benchmark.query.all()
else:
benchmarks = [current_user.benchmark]
else:
benchmarks = []
return render_template("index.html", benchmarks=benchmarks)
@main.route("/config")
@login_required
def config():
return render_template("config.html")
@main.route("/login", methods=["GET", "POST"])
def login():
if current_user.is_authenticated:
return redirect(url_for("main.index"))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash("Invalid username or password")
return redirect(url_for("main.login"))
login_user(user, remember=form.remember_me.data)
flash("Logged in successfully.")
next_page = request.args.get("next")
if not next_page or url_parse(next_page).netloc != "":
next_page = url_for("main.index")
return redirect(next_page)
return render_template("login.html", title="Sign In", form=form)
@main.route("/logout")
def logout():
logout_user()
return redirect(url_for("main.index"))

40
app/models.py Normal file
View File

@@ -0,0 +1,40 @@
from hashlib import md5
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
db = SQLAlchemy()
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
admin = db.Column(db.Boolean, default=False)
password_hash = db.Column(db.String(128))
benchmark_id = db.Column(db.Integer, db.ForeignKey("benchmark.id"))
benchmark = db.relationship("Benchmark")
def __repr__(self):
return "<User {} {}>".format(self.username, self.email)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def avatar(self, size):
digest = md5(self.email.lower().encode("utf-8")).hexdigest()
return "https://www.gravatar.com/avatar/{}?d=identicon&s={}".format(
digest, size
)
class Benchmark(db.Model):
__tablename__ = "benchmark"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), index=True, unique=True)
description = db.Column(db.String(120), index=False, unique=False)
folder = db.Column(db.String(128), index=False, unique=True)

45
app/results/main.py Normal file
View File

@@ -0,0 +1,45 @@
import os
from benchmark.Utils import Files, Folders
from benchmark.ResultsBase import StubReport
from flask import Blueprint, current_app, send_file
from flask import render_template, current_app, request, redirect, url_for
from flask_login import login_required
# import json
# import shutil
# import xlsxwriter
# from benchmark.ResultsFiles import Excel, ReportDatasets
# from benchmark.Datasets import Datasets
results = Blueprint("results", __name__, template_folder="templates")
@results.route("/select")
@login_required
def select(compare="False"):
# Get a list of files in a directory
files = {}
names = Files.get_all_results(hidden=False)
for name in names:
report = StubReport(os.path.join(Folders.results, name))
report.report()
files[name] = {
"duration": report.duration,
"score": report.score,
"title": report.title,
}
candidate = current_app.config["FRAMEWORKS"].copy()
candidate.remove(current_app.config["FRAMEWORK"])
return render_template(
"select.html",
files=files,
candidate=candidate[0],
framework=current_app.config["FRAMEWORK"],
compare=compare.capitalize() == "True",
)
@results.route("/datasets")
@login_required
def datasets(compare="False"):
return render_template("test.html")

212
app/results/results.ant.py Normal file
View File

@@ -0,0 +1,212 @@
#!/usr/bin/env python
# import os
# import json
# import shutil
# import xlsxwriter
# from benchmark.Utils import Files, Folders
# from benchmark.Arguments import EnvData
# from benchmark.ResultsBase import StubReport
# from benchmark.ResultsFiles import Excel, ReportDatasets
# from benchmark.Datasets import Datasets
# from flask import Blueprint, current_app, send_file
# from flask import render_template, request, redirect, url_for
from flask import Blueprint, render_template
results = Blueprint("results", __name__, template_folder="results")
# FRAMEWORK = "framework"
# FRAMEWORKS = "frameworks"
# OUTPUT = "output"
# TEST = "test"
# class AjaxResponse:
# def __init__(self, success, file_name, code=200):
# self.success = success
# self.file_name = file_name
# self.code = code
# def to_string(self):
# return (
# json.dumps(
# {
# "success": self.success,
# "file": self.file_name,
# "output": current_app.config[OUTPUT],
# }
# ),
# self.code,
# {"ContentType": "application/json"},
# )
# def process_data(file_name, compare, data):
# report = StubReport(
# os.path.join(Folders.results, file_name), compare=compare
# )
# new_list = []
# for result in data["results"]:
# symbol = report._compute_status(result["dataset"], result["score"])
# result["symbol"] = symbol if symbol != " " else "&nbsp;"
# new_list.append(result)
# data["results"] = new_list
# # Compute summary with explanation of symbols
# summary = {}
# for key, value in report._compare_totals.items():
# summary[key] = (report._status_meaning(key), value)
# return summary
@results.route("/results/<compare>")
def results(compare="False"):
# # Get a list of files in a directory
# files = {}
# names = Files.get_all_results(hidden=False)
# for name in names:
# report = StubReport(os.path.join(Folders.results, name))
# report.report()
# files[name] = {
# "duration": report.duration,
# "score": report.score,
# "title": report.title,
# }
# candidate = current_app.config[FRAMEWORKS].copy()
# candidate.remove(current_app.config[FRAMEWORK])
# return render_template(
# "select.html",
# files=files,
# candidate=candidate[0],
# framework=current_app.config[FRAMEWORK],
# compare=compare.capitalize() == "True",
# )
return render_template("test.html")
"""
@results.route("/datasets/<compare>")
@results.route("datasets")
def datasets(compare=False):
dt = Datasets()
datos = []
for dataset in dt:
datos.append(dt.get_attributes(dataset))
return render_template(
"datasets.html",
datasets=datos,
compare=compare,
framework=current_app.config[FRAMEWORK],
)
@results.route("/showfile/<file_name>/<compare>")
def showfile(file_name, compare, back=None):
compare = compare.capitalize() == "True"
back = request.args["url"] if back is None else back
print(f"back [{back}]")
with open(os.path.join(Folders.results, file_name)) as f:
data = json.load(f)
try:
summary = process_data(file_name, compare, data)
except Exception as e:
return render_template("error.html", message=str(e), compare=compare)
return render_template(
"report.html",
data=data,
file=file_name,
summary=summary,
framework=current_app.config[FRAMEWORK],
back=back,
)
@results.route("/show", methods=["post"])
def show():
selected_file = request.form["selected-file"]
compare = request.form["compare"]
return showfile(
file_name=selected_file,
compare=compare,
back=url_for(
"main.index", compare=compare, output=current_app.config[OUTPUT]
),
)
@results.route("/excel", methods=["post"])
def excel():
selected_files = request.json["selectedFiles"]
compare = request.json["compare"]
book = None
if selected_files[0] == "datasets":
# Create a list of datasets
report = ReportDatasets(excel=True, output=False)
report.report()
excel_name = os.path.join(Folders.excel, Files.datasets_report_excel)
if current_app.config[OUTPUT] == "local":
Files.open(excel_name, test=current_app.config[TEST])
return AjaxResponse(True, Files.datasets_report_excel).to_string()
try:
for file_name in selected_files:
file_name_result = os.path.join(Folders.results, file_name)
if book is None:
file_excel = os.path.join(Folders.excel, Files.be_list_excel)
book = xlsxwriter.Workbook(
file_excel, {"nan_inf_to_errors": True}
)
excel = Excel(
file_name=file_name_result,
book=book,
compare=compare,
)
excel.report()
except Exception as e:
if book is not None:
book.close()
return AjaxResponse(
False, "Could not create excel file, " + str(e)
).to_string()
if book is not None:
book.close()
if current_app.config[OUTPUT] == "local":
Files.open(file_excel, test=current_app.config[TEST])
return AjaxResponse(True, Files.be_list_excel).to_string()
@results.route("/download/<file_name>")
def download(file_name):
src = os.path.join(Folders.current, Folders.excel, file_name)
dest = os.path.join(
Folders.src(), "scripts", "app", "static", "excel", file_name
)
shutil.copyfile(src, dest)
return send_file(dest, as_attachment=True)
@results.route("/config/<framework>/<compare>")
def config(framework, compare):
if framework not in current_app.config[FRAMEWORKS]:
message = f"framework {framework} not supported"
return render_template("error.html", message=message)
env = EnvData()
env.load()
env.args[FRAMEWORK] = framework
env.save()
current_app.config[FRAMEWORK] = framework
return redirect(url_for("main.index", compare=compare))
@results.route("/best_results/<file>/<compare>")
def best_results(file, compare):
compare = compare.capitalize() == "True"
try:
with open(os.path.join(Folders.results, file)) as f:
data = json.load(f)
except Exception as e:
return render_template("error.html", message=str(e), compare=compare)
return render_template(
"report_best.html",
data=data,
compare=compare,
framework=current_app.config[FRAMEWORK],
)
"""

View File

@@ -0,0 +1,50 @@
{%- macro get_button_tag(icon_name, method, visible=True, name="") -%}
<button class="btn btn-primary btn-small" onclick="{{ method }}" {{ "" if visible else "hidden='true'" }} {{ "" if name=="" else "name='" + name +"'"}}><i class="mdi mdi-{{ icon_name }}"></i>
</button>
{%- endmacro -%}
<table id="file-table"
class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>Model</th>
<th>Metric</th>
<th>Platform</th>
<th>Date</th>
<th>Time</th>
<th>Stratified</th>
<th>Title</th>
<th>Score</th>
<th>
<button class="btn btn-primary btn-small btn-danger"
onclick="setCheckBoxes(false)">
<i class="mdi mdi-checkbox-multiple-blank"></i>
</button>
<button class="btn btn-primary btn-small btn-success"
onclick="setCheckBoxes(true)">
<i class="mdi mdi-checkbox-multiple-marked"></i>
</button>
</tr>
</thead>
<tbody>
{% for file, data in files.items() %}
{% set parts = file.split('_') %}
{% set stratified = parts[6].split('.')[0] %}
<tr id="{{ file }}">
<td>{{ parts[2] }}</td>
<td>{{ parts[1] }}</td>
<td>{{ parts[3] }}</td>
<td>{{ parts[4] }}</td>
<td>{{ parts[5] }}</td>
<td>{{ 'True' if stratified =='1' else 'False' }}</td>
<td>{{ "%s" % data["title"] }}</td>
<td class="text-end">{{ "%.6f" % data["score"] }}</td>
<td>
{{ get_button_tag("table-eye", "showFile('" ~ file ~ "') ") | safe }}
{% set file_best = "best_results_" ~ parts[1] ~ "_" ~ parts[2] ~ ".json" %}
{{ get_button_tag("star-circle-outline", "redirectDouble('best_results', '" ~ file_best ~ "') ", visible=False, name="best_buttons") | safe }}
<input type="checkbox" name="selected_files" value="{{ file }}" />
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block content %}
{% include "_table_select.html" %}
{% endblock %}
{% block jscript %}
{{ super() }}
<script src="https://cdn.datatables.net/1.10.25/js/jquery.dataTables.min.js"></script>
<script src="{{ url_for('static', filename="js/select.js") }}"></script>
{% endblock %}

52
app/static/css/main.css Normal file
View File

@@ -0,0 +1,52 @@
.alternate-font {
font-family: Arial;
}
tbody {
font-family: Courier;
}
.tag {
cursor: pointer;
}
.ajaxLoading {
cursor: progress !important;
}
#file-table tbody tr.selected td {
background-color: #0dcaf0;
color: white;
}
#report-table tbody tr.selected td {
background-color: #0dcaf0;
color: white;
}
.btn-small {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
body {
padding-bottom: 20px;
background-color: #ffb878;
}
.navbar {
margin-bottom: 20px;
}
pre {
background: #ddd;
padding: 10px;
}
h2 {
margin-top: 20px;
}
footer {
margin: 20px;
}

View File

@@ -0,0 +1,29 @@
function excelFiles(selectedFiles, compare) {
var data = {
"selectedFiles": selectedFiles,
"compare": compare
};
// send data to server with ajax post
$.ajax({
type:'POST',
url:'/excel',
data: JSON.stringify(data),
contentType: "application/json",
dataType: 'json',
success: function(data){
if (data.success) {
if (data.output == "local") {
alert("Se ha generado el archivo " + data.file);
} else {
window.open('/download/' + data.file, "_blank");
}
} else {
alert(data.file);
}
},
error: function (xhr, ajaxOptions, thrownError) {
var mensaje = JSON.parse(xhr.responseText || '{\"mensaje\": \"Error indeterminado\"}');
alert(mensaje.mensaje);
}
});
}

97
app/static/js/select.js Normal file
View File

@@ -0,0 +1,97 @@
$(document).ready(function () {
var table = $("#file-table").DataTable({
paging: true,
searching: true,
ordering: true,
info: true,
"select.items": "row",
pageLength: 25,
columnDefs: [
{
targets: 8,
orderable: false,
},
],
//"language": {
// "lengthMenu": "_MENU_"
//}
});
$('#file-table').on( 'draw.dt', function () {
enable_disable_best_buttons();
} );
// Check if row is selected
$("#file-table tbody").on("click", "tr", function () {
if ($(this).hasClass("selected")) {
$(this).removeClass("selected");
} else {
table
.$("tr.selected")
.removeClass("selected");
$(this).addClass("selected");
}
});
// Show file with doubleclick
$("#file-table tbody").on("dblclick", "tr", function () {
showFile($(this).attr("id"));
});
$(document).ajaxStart(function () {
$("body").addClass("ajaxLoading");
});
$(document).ajaxStop(function () {
$("body").removeClass("ajaxLoading");
});
$('#compare').change(function() {
enable_disable_best_buttons();
});
enable_disable_best_buttons();
});
function enable_disable_best_buttons(){
if ($('#compare').is(':checked')) {
$("[name='best_buttons']").addClass("tag is-link is-normal");
$("[name='best_buttons']").removeAttr("hidden");
} else {
$("[name='best_buttons']").removeClass("tag is-link is-normal");
$("[name='best_buttons']").attr("hidden", true);
}
}
function showFile(selectedFile) {
var form = $(
'<form action="/show" method="post">' +
'<input type="hidden" name="selected-file" value="' +
selectedFile +
'" />' +
'<input type="hidden" name="compare" value=' +
$("#compare").is(":checked") +
" />" +
"</form>"
);
$("body").append(form);
form.submit();
}
function excel() {
var checkbox = document.getElementsByName("selected_files");
var selectedFiles = [];
for (var i = 0; i < checkbox.length; i++) {
if (checkbox[i].checked) {
selectedFiles.push(checkbox[i].value);
}
}
if (selectedFiles.length == 0) {
alert("Select at least one file");
return;
}
var compare = $("#compare").is(":checked");
excelFiles(selectedFiles, compare);
}
function setCheckBoxes(value) {
var checkbox = document.getElementsByName("selected_files");
for (i = 0; i < checkbox.length; i++) {
checkbox[i].checked = value;
}
}
function redirectDouble(route, parameter) {
location.href = "/"+ route + "/" + parameter + "/" + $("#compare").is(":checked");
}
function redirectSimple(route) {
location.href = "/" + route + "/" + $("#compare").is(":checked");
}

30
app/templates/_nav.html Normal file
View File

@@ -0,0 +1,30 @@
{% from 'bootstrap5/nav.html' import render_nav_item %}
<nav class="navbar navbar-expand-sm navbar-light bg-light mb-4 justify-content-end">
<div class="container">
<button class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left side of navbar -->
<ul class="navbar-nav me-auto">
{{ render_nav_item('main.index', 'Home') }}
</ul>
<ul class="navbar-nav justify-content-end">
{{ render_nav_item('results.select', 'Results') }}
{{ render_nav_item('results.datasets', 'Datasets') }}
{{ render_nav_item('main.config', 'Config') }}
{% if current_user.is_authenticated %}
{{ render_nav_item('main.logout', current_user.username + '(Logout) ') }}
{% else %}
{{ render_nav_item('main.login', 'Login') }}
{% endif %}
</ul>
</div>
</div>
</nav>

32
app/templates/base.html Normal file
View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% block styles %}
{{ bootstrap.load_css() }}
<link rel="stylesheet" href="/css/elina.css">
<link rel="stylesheet"
href="{{ url_for('static', filename='css/main.css') }}">
{% endblock %}
<title>Benchmark</title>
{% endblock %}
</head>
<body>
{% include "_nav.html" %}
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}<div class="alert alert-info" role="alert">{{ message }}</div>{% endfor %}
{% endif %}
{% endwith %}
<div class="container">
{% block content %}{% endblock %}
</div>
</body>
{% block jscript %}
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
{{ bootstrap.load_js() }}
{% endblock %}
</html>

View File

@@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block content %}
<h1>Home</h1>
<p>Welcome to the home page!</p>
{% endblock content %}

13
app/templates/index.html Normal file
View File

@@ -0,0 +1,13 @@
{% extends "base.html" %}
{% block content %}
{% if benchmarks %}
<h5>Benchmarks</h5>
<ul>
{% for benchmark in benchmarks %}
<li>
<a href="">{{ benchmark.name }} {{ benchmark.folder }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

6
app/templates/login.html Normal file
View File

@@ -0,0 +1,6 @@
{% extends 'base.html' %}
{% from 'bootstrap5/form.html' import render_form %}
{% block content %}
<h2>Login</h2>
{{ render_form(form) }}
{% endblock content %}

26
dbseed.py Normal file
View File

@@ -0,0 +1,26 @@
from app.models import Benchmark, db, User
from app import app
app = app.create_app()
with app.app_context():
db.drop_all()
db.create_all()
b = Benchmark(
name="discretizbench",
folder="proyects/discretizbench",
description="Experiments with local discretization and Bayesian classifiers",
)
u = User(username="rmontanana", email="rmontanana@gmail.com", admin=True)
u.set_password("patata")
u1 = User(
username="guest",
email="guest@example.com",
admin=False,
benchmark_id=1,
)
u1.set_password("guest")
db.session.add(b)
db.session.add(u)
db.session.add(u1)
db.session.commit()

16
pyproject.toml Normal file
View File

@@ -0,0 +1,16 @@
[tool.black]
line-length = 79
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
)/
'''

3
run.py Normal file
View File

@@ -0,0 +1,3 @@
from app import app
app.create_app().run(debug=True)