Add login functionality and basic HTML pages

This commit is contained in:
coolneng 2020-01-09 03:17:31 +01:00
parent a0a5384e5b
commit 15ac576058
Signed by: coolneng
GPG Key ID: 9893DA236405AF57
16 changed files with 147 additions and 48 deletions

View File

@ -12,6 +12,7 @@ flask-sqlalchemy = "*"
pandas = "*" pandas = "*"
iso3166 = "*" iso3166 = "*"
flask-wtf = "*" flask-wtf = "*"
flask-login = "*"
[requires] [requires]
python_version = "3.8" python_version = "3.8"

9
code/Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "f4ff138e539e1c5bcdc3a3f5046b1d5087a0f0315d1a18181fc2c6eeb1227671" "sha256": "234ca1fc7cbb10534d17febc8a9d0320ecb5c6317351bcd97e161c860201b5a5"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -31,6 +31,13 @@
"index": "pypi", "index": "pypi",
"version": "==1.1.1" "version": "==1.1.1"
}, },
"flask-login": {
"hashes": [
"sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec"
],
"index": "pypi",
"version": "==0.4.1"
},
"flask-sqlalchemy": { "flask-sqlalchemy": {
"hashes": [ "hashes": [
"sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327", "sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327",

View File

@ -1,10 +1,12 @@
from flask import Flask from flask import Flask
from config import Config from config import Config
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(Config) app.config.from_object(Config)
db = SQLAlchemy(app) db = SQLAlchemy(app)
login = LoginManager(app)
login.login_view = "login"
from app import routes, models from app import routes, models

10
code/app/forms.py Normal file
View File

@ -0,0 +1,10 @@
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
username = StringField("Username", validators=[DataRequired()])
password = PasswordField("Password", validators=[DataRequired()])
remember_me = BooleanField("Remember Me")
submit = SubmitField("Sign In")

View File

@ -1,16 +1,16 @@
from app import db from app import db, login
from subprocess import run from flask_login import UserMixin
from database.constants import DB_USER, DB_PW, DB_NAME from werkzeug.security import check_password_hash
class Glacier(db.Model): class Glacier(db.Model):
uid = db.Column(db.String(5), primary_key=True) id = db.Column(db.String(20), primary_key=True)
country = db.Column(db.String(60)) country = db.Column(db.String(60))
name = db.Column(db.String(60)) name = db.Column(db.String(60))
annual_data = db.relationship("Annual_Data") annual_data = db.relationship("Annual_Data")
def __init__(self, uid, country, name): def __init__(self, id, country, name):
self.uid = uid self.id = id
self.country = country self.country = country
self.name = name self.name = name
@ -18,7 +18,7 @@ class Glacier(db.Model):
class Annual_Data(db.Model): class Annual_Data(db.Model):
__tablename__ = "annual_data" __tablename__ = "annual_data"
year = db.Column(db.Integer, primary_key=True) year = db.Column(db.Integer, primary_key=True)
uid = db.Column(db.ForeignKey("glacier.uid"), primary_key=True) id = db.Column(db.ForeignKey("glacier.id"), primary_key=True)
surface = db.Column(db.Float) surface = db.Column(db.Float)
length = db.Column(db.Float) length = db.Column(db.Float)
elevation = db.Column(db.Float) elevation = db.Column(db.Float)
@ -30,15 +30,22 @@ class Annual_Data(db.Model):
self.elevation = elevation self.elevation = elevation
class User(db.Model): class User(UserMixin, db.Model):
uid = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
registration_date = db.Column( registration_date = db.Column(
db.DateTime, nullable=False, server_default=db.func.now() db.DateTime, nullable=False, server_default=db.func.now()
) )
username = db.Column(db.String(20), nullable=False, unique=True) username = db.Column(db.String(20), nullable=False, unique=True)
password = db.Column(db.String(60)) password_hash = db.Column(db.String(128), unique=True)
def __init__(self, uid, username, password): def __init__(self, id, username, password_hash):
self.uid = uid self.id = id
self.username = username self.username = username
self.password = password self.password_hash = password_hash
def check_password(self, password):
return check_password_hash(self.password_hash, password)
@login.user_loader
def load_user(id):
return User.query.get(int(id))

View File

@ -1,13 +1,42 @@
from app import app from app import app
from flask import render_template from app.forms import LoginForm
from app.models import User
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
@app.route("/") @app.route("/")
@app.route("/index") @app.route("/index")
def index(): def index():
user = {"username": "Bolaji"} return render_template("index.html", title="Home Page")
posts = [
{"author": {"username": "Miloud"}, "body": "Beautiful day in Meknes!"},
{"author": {"username": "Sebtaoui"}, "body": "The Farkouss movie was lit!"}, @app.route("/login", methods=["GET", "POST"])
] def login():
return render_template("index.html", title="Home", user=user, posts=posts) if current_user.is_authenticated:
return redirect(url_for("admin"))
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("login"))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get("next")
if not next_page or url_parse(next_page).netloc != "":
next_page = url_for("admin")
return redirect(next_page)
return render_template("login.html", title="Sign In", form=form)
@app.route("/logout")
def logout():
logout_user()
return redirect(url_for("index"))
@app.route("/admin")
@login_required
def admin():
return render_template("admin.html", title="Admin Page")

View File

@ -0,0 +1,6 @@
{% extends "base.html" %}
{% block content %}
<h1>Hi, {{ current_user.username }}!</h1>
Do you want to nuke the database?
{% endblock %}

View File

@ -7,8 +7,24 @@
{% endif %} {% endif %}
</head> </head>
<body> <body>
<div>IGDB: <a href="/index">Home</a></div> <div>IGDB:
<a href="{{ url_for('index') }}">Home</a>
{% if current_user.is_anonymous %}
<a href="{{ url_for('login') }}">Login</a>
{% else %}
<a href="{{ url_for('admin') }}">Administration</a>
<a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</div>
<hr> <hr>
{% block content %}{% endblock %} {% with messages = get_flashed_messages() %}
</body> {% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %} </body>
</html> </html>

View File

@ -1,8 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<h1>Hi, {{ user.username }}!</h1> <h1>IGDB: International Glacier Database</h1>
{% for post in posts %} The IGDB is a database, that uses data from the <a href="https://dx.doi.org/10.5904/wgms-fog-2019-12">WGMS</a> to illustrate the consequences of climate change.
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,24 @@
{% extends "base.html" %}
{% block content %}
<h1>Sign In</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}<br>
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}

View File

@ -4,3 +4,4 @@ from database.constants import CONNECTION_URI
class Config(object): class Config(object):
SQLALCHEMY_DATABASE_URI = CONNECTION_URI SQLALCHEMY_DATABASE_URI = CONNECTION_URI
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = "trolaso"

View File

@ -3,6 +3,7 @@ from pandas import DataFrame, concat, read_csv
from csv import QUOTE_NONNUMERIC from csv import QUOTE_NONNUMERIC
from database.constants import ADMIN_PW from database.constants import ADMIN_PW
from os import path from os import path
from werkzeug.security import generate_password_hash
def country_conversion(political_unit) -> str: def country_conversion(political_unit) -> str:
@ -40,7 +41,7 @@ def rename_fields(df_list):
new_fields = { new_fields = {
"POLITICAL_UNIT": "country", "POLITICAL_UNIT": "country",
"NAME": "name", "NAME": "name",
"WGMS_ID": "uid", "WGMS_ID": "id",
"YEAR": "year", "YEAR": "year",
"MEDIAN_ELEVATION": "elevation", "MEDIAN_ELEVATION": "elevation",
"AREA": "surface", "AREA": "surface",
@ -57,7 +58,11 @@ def create_databases(df):
"annual_data": "../data/annual_data.csv", "annual_data": "../data/annual_data.csv",
"user": "../data/user.csv", "user": "../data/user.csv",
} }
user = {"uid": [7843], "username": ["admin"], "password": [ADMIN_PW]} user = {
"id": [7843],
"username": ["admin"],
"password_hash": [generate_password_hash(ADMIN_PW)],
}
dataframes = { dataframes = {
"glacier": df[["POLITICAL_UNIT", "NAME", "WGMS_ID"]].drop_duplicates(), "glacier": df[["POLITICAL_UNIT", "NAME", "WGMS_ID"]].drop_duplicates(),
"annual_data": df[["WGMS_ID", "YEAR", "AREA", "MEDIAN_ELEVATION", "LENGTH"]], "annual_data": df[["WGMS_ID", "YEAR", "AREA", "MEDIAN_ELEVATION", "LENGTH"]],

View File

@ -1,4 +1,4 @@
"uid","year","surface","elevation","length" "id","year","surface","elevation","length"
10452,2018,1.68,"",2.1 10452,2018,1.68,"",2.1
2665,2014,12.9,390.0,7.6 2665,2014,12.9,390.0,7.6
2665,2015,12.9,390.0,7.6 2665,2015,12.9,390.0,7.6

1 uid id year surface elevation length
2 10452 2018 1.68 2.1
3 2665 2014 12.9 390.0 7.6
4 2665 2015 12.9 390.0 7.6

View File

@ -1,4 +1,4 @@
"country","name","uid" "country","name","id"
"Afghanistan","PIR YAKH",10452 "Afghanistan","PIR YAKH",10452
"Antarctica","BAHIA DEL DIABLO",2665 "Antarctica","BAHIA DEL DIABLO",2665
"Antarctica","BELLINGSHAUSEN",6833 "Antarctica","BELLINGSHAUSEN",6833

1 country name uid id
2 Afghanistan PIR YAKH 10452
3 Antarctica BAHIA DEL DIABLO 2665
4 Antarctica BELLINGSHAUSEN 6833

View File

@ -1,2 +1,2 @@
"uid","username","password" "id","username","password_hash"
7843,"admin","fuckmonsanto" 7843,"admin","pbkdf2:sha256:150000$YedV5Qqy$2faccb322cfb1a546d68286de06ebee0888ffafae1f0444073de85d0c59c3748"

1 uid id username password password_hash
2 7843 7843 admin fuckmonsanto pbkdf2:sha256:150000$YedV5Qqy$2faccb322cfb1a546d68286de06ebee0888ffafae1f0444073de85d0c59c3748

View File

@ -20,27 +20,20 @@ datos relevantes para estudios acerca del cambio climático, y acotando éstos a
1. *RD1*: Datos del glaciar 1. *RD1*: Datos del glaciar
- País - /Cadena de 60 caracteres máximo/ - País - /Cadena de 60 caracteres máximo/
- Nombre del glaciar - /Cadena de 60 caracteres máximo/ - Nombre del glaciar - /Cadena de 60 caracteres máximo/
- ID del glaciar (Compatible con la WGMS) - /Cadena de 5 caracteres/ - ID del glaciar (Compatible con la WGMS) - /Cadena de 20 caracteres/
2. *RD2*: Datos anuales de un glaciar 2. *RD2*: Datos anuales de un glaciar
- ID del glaciar (Compatible con la WGMS) - /Cadena de 5 caracteres/ - ID del glaciar (Compatible con la WGMS) - /Cadena de 20 caracteres/
- Área - /Decimal/ - Área - /Decimal/
- Volumen - /Decimal/ - Volumen - /Decimal/
- Altura - /Decimal/ - Altura - /Decimal/
- Año - /Decimal/ - Año - /Entero de 11 dígitos/
3. *RD3*: Datos de cambio de un glaciar 3. *RD3*: Datos del administrador
- ID del glaciar (Compatible con la WGMS) - /Cadena de 5 caracteres/
- Variación de área - /Decimal/
- Variación de volumen - /Decimal/
- Variación de altura - /Decimal/
- Año - /Decimal de 10 dígitos/
4. *RD4*: Datos del administrador
- ID - /Entero de 11 dígitos/ - ID - /Entero de 11 dígitos/
- Fecha y hora de alta - /Fecha y hora en formato yyyy-mm-dd hh:mm/ - Fecha y hora de alta - /Fecha y hora en formato yyyy-mm-dd hh:mm/
- Nombre de usuario - /Cadena de 20 caracteres máximo/ - Nombre de usuario - /Cadena de 20 caracteres máximo/
- Contraseña - /Cadena de 60 caracteres máximo/ - Hash de la contraseña - /Cadena de 128 caracteres máximo/
*** Funcionales *** Funcionales