diff --git a/Design.org b/Design.org index 8e7d2ab..ac691ca 100644 --- a/Design.org +++ b/Design.org @@ -46,6 +46,13 @@ CLOSED: [2019-11-01 Fri 00:34] - [X] Entity-Relationship ** Implementation *** TODO Backend [2/4] [50%] +**** TODO Flask Application [2/3] [66%] +- [ ] Plots with pandas +- [X] Login for admin +- [X] Tables with pandas +**** TODO Possible additions [1/2] [50%] +- [ ] Text search for glaciers +- [X] Form seach for a year **** DONE Database [3/3] [100%] CLOSED: [2020-01-03 Fri 00:44] - [X] Connection @@ -56,15 +63,12 @@ CLOSED: [2020-01-08 Wed 03:18] - [X] Select useful fiels - [X] Convert PU to Country (ISO 3166) - [X] Insert into database -**** TODO Flask Application [1/4] [25%] -- [ ] Arithmetic operations for yearly changes -- [ ] Tables with pandas -- [ ] Plots with pandas -- [X] Login for admin -**** TODO Possible additions [0/2] [0%] -- [ ] Text search for glaciers -- [ ] Form seach for a year -*** TODO Frontend [1/2] [50%] +*** TODO Documentation [0/2] [0%] +- [ ] Code +- [ ] Explanations (uid as Varchar, ORM) +*** TODO Deployment [0/1] [0%] +- [ ] Virtualenv installation script +*** DONE Frontend [2/2] [100%] +CLOSED: [2020-01-09 Thu 11:09] - [X] Flask-Bootstrap -- [ ] Find CSS -*** TODO Documentation +- [X] Find CSS diff --git a/code/app/forms.py b/code/app/forms.py index 55a4d2d..d30268a 100644 --- a/code/app/forms.py +++ b/code/app/forms.py @@ -1,5 +1,5 @@ from flask_wtf import FlaskForm -from wtforms import StringField, PasswordField, BooleanField, SubmitField +from wtforms import BooleanField, PasswordField, SelectField, StringField, SubmitField from wtforms.validators import DataRequired @@ -8,3 +8,39 @@ class LoginForm(FlaskForm): password = PasswordField("Password", validators=[DataRequired()]) remember_me = BooleanField("Remember Me") submit = SubmitField("Sign In") + + +class YearForm(FlaskForm): + year_list = [ + ("2011", 2011), + ("2012", 2012), + ("2013", 2013), + ("2014", 2014), + ("2015", 2015), + ("2016", 2016), + ("2017", 2017), + ("2018", 2018), + ] + year = SelectField("Year", validators=[DataRequired()], choices=year_list) + submit = SubmitField("Search") + + +class IntervalForm(FlaskForm): + year_list = [ + ("2011", 2011), + ("2012", 2012), + ("2013", 2013), + ("2014", 2014), + ("2015", 2015), + ("2016", 2016), + ("2017", 2017), + ("2018", 2018), + ] + name = StringField("Glacier Name") + lower_bound = SelectField( + "First year", validators=[DataRequired()], choices=year_list + ) + upper_bound = SelectField( + "Second year", validators=[DataRequired()], choices=year_list + ) + submit = SubmitField("Search") diff --git a/code/app/routes.py b/code/app/routes.py index 12f8de4..1f69e5b 100644 --- a/code/app/routes.py +++ b/code/app/routes.py @@ -1,9 +1,10 @@ -from app import app -from app.forms import LoginForm -from app.models import User +from app import app, db +from app.forms import LoginForm, YearForm, IntervalForm +from app.models import User, Annual_Data, Glacier from flask import flash, redirect, render_template, url_for, request from flask_login import current_user, login_user, logout_user, login_required from werkzeug.urls import url_parse +from processing.tabulate import create_table @app.route("/") @@ -53,9 +54,24 @@ def data(): return render_template("data.html", title="Data") -@app.route("/tables") -def tables(): - return render_template("data.html", title="Data") +@app.route("/table_selection", methods=["GET", "POST"]) +def table_selection(): + form = YearForm() + if form.validate_on_submit(): + annual_data = ( + db.session.query(Annual_Data).filter_by(year=form.year.data).statement + ) + if annual_data is None: + flash("Invalid query, please try again") + return redirect(url_for("table_selection")) + table = create_table(annual_data) + return render_template("table.html", table=table, title="Table") + return render_template("table_selection.html", title="Data", form=form) + + +@app.route("/table") +def table(): + return render_template("table.html", table=table, title="Table") @app.route("/plots") diff --git a/code/app/templates/admin.html b/code/app/templates/admin.html index 97dd31b..18a7bca 100644 --- a/code/app/templates/admin.html +++ b/code/app/templates/admin.html @@ -1,7 +1,16 @@ {% extends "base.html" %} {% block content %} -

Hey, {{ current_user.username }}!

- Do you want to nuke the database? -
  • Yes, I want to burn down the world
  • +

    Hey, {{ current_user.username }}!

    +

    Do you want to nuke the database?

    +
  • +

    + Yes, I want to burn down the world +

    +
  • +
  • +

    + Nah, show me some cool data +

    +
  • {% endblock %} diff --git a/code/app/templates/base.html b/code/app/templates/base.html index 25aeece..9a4478d 100644 --- a/code/app/templates/base.html +++ b/code/app/templates/base.html @@ -1,7 +1,7 @@ {% extends 'bootstrap/base.html' %} {% block title %} -IGDB +{% if title %}{{ title }} - IGDB{% else %}IGDB{% endif %} {% endblock %} {% block styles %} diff --git a/code/app/templates/data.html b/code/app/templates/data.html index be64ffd..ce3d756 100644 --- a/code/app/templates/data.html +++ b/code/app/templates/data.html @@ -1,8 +1,15 @@ {% extends "base.html" %} {% block content %} -

    Hey, {{ current_user.username }}!

    - Wanna check out some cool data? -
  • -
  • +

    What do you want to check out?

    +
  • +

    + Tables +

    +
  • +
  • +

    + Plots +

    +
  • {% endblock %} diff --git a/code/app/templates/table.html b/code/app/templates/table.html new file mode 100644 index 0000000..b8dc318 --- /dev/null +++ b/code/app/templates/table.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} +{% import 'bootstrap/wtf.html' as wtf %} + +{% block app_content %} +

    Table

    + {{ table|safe }} +{% endblock %} diff --git a/code/app/templates/table_selection.html b/code/app/templates/table_selection.html new file mode 100644 index 0000000..cf1a6aa --- /dev/null +++ b/code/app/templates/table_selection.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% import 'bootstrap/wtf.html' as wtf %} + +{% block app_content %} +

    Table selection

    +
    +
    + {{ wtf.quick_form(form) }} +
    +
    +
    +{% endblock %} diff --git a/code/database/export.py b/code/database/export.py index 550585a..a7e86f9 100644 --- a/code/database/export.py +++ b/code/database/export.py @@ -1,19 +1,9 @@ +from app import db from app.models import Annual_Data, Glacier, User -from database.constants import DB_NAME, DB_PW, DB_USER from pandas import DataFrame, read_csv -from sqlalchemy import create_engine, engine -def create_connection() -> engine: - host = "localhost:3306" - connection_uri = "mysql+pymysql://{user}:{pw}@{url}/{db}".format( - user=DB_USER, pw=DB_PW, url=host, db=DB_NAME - ) - engine = create_engine(connection_uri) - return engine - - -def create_dataframes() -> DataFrame: +def create_dataframes() -> {DataFrame}: files = { "glacier": "../data/glacier.csv", "annual_data": "../data/annual_data.csv", @@ -25,19 +15,18 @@ def create_dataframes() -> DataFrame: return df_list -def insert_data(df_list, conn): +def insert_data(df_list): models = [Glacier, Annual_Data, User] for model in models: if model.query.first() is not None: return for key, value in df_list.items(): - value.to_sql(key, con=conn, index=False, if_exists="append") + value.to_sql(key, con=db.engine, index=False, if_exists="append") def main(): - conn = create_connection() df_list = create_dataframes() - insert_data(df_list, conn) + insert_data(df_list) if __name__ == "__main__": diff --git a/code/igdb.py b/code/igdb.py index d42dfa6..bd0b550 100644 --- a/code/igdb.py +++ b/code/igdb.py @@ -1,5 +1,6 @@ from app import app from database import db_setup, export, parser +from processing import tabulate db_setup.main() parser.main() diff --git a/code/processing/__init__.py b/code/processing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/code/processing/tabulate.py b/code/processing/tabulate.py new file mode 100644 index 0000000..1fea6ab --- /dev/null +++ b/code/processing/tabulate.py @@ -0,0 +1,18 @@ +from app import db +from pandas import DataFrame, read_sql, wide_to_long + + +def create_dataframe(query) -> DataFrame: + df = read_sql(sql=query, con=db.engine) + return df + + +def render_table(df) -> str: + table = df.to_html(classes=["table-bordered", "table-striped", "table-hover"]) + return table + + +def create_table(query) -> str: + df = create_dataframe(query) + html_table = render_table(df) + return html_table