Add login functionality and basic HTML pages
This commit is contained in:
parent
a0a5384e5b
commit
15ac576058
|
@ -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"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
|
@ -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))
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Hi, {{ current_user.username }}!</h1>
|
||||||
|
Do you want to nuke the database?
|
||||||
|
{% endblock %}
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
|
@ -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"
|
||||||
|
|
|
@ -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"]],
|
||||||
|
|
|
@ -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,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,2 +1,2 @@
|
||||||
"uid","username","password"
|
"id","username","password_hash"
|
||||||
7843,"admin","fuckmonsanto"
|
7843,"admin","pbkdf2:sha256:150000$YedV5Qqy$2faccb322cfb1a546d68286de06ebee0888ffafae1f0444073de85d0c59c3748"
|
||||||
|
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue