Add login functionality and basic HTML pages
This commit is contained in:
parent
a0a5384e5b
commit
15ac576058
|
@ -12,6 +12,7 @@ flask-sqlalchemy = "*"
|
|||
pandas = "*"
|
||||
iso3166 = "*"
|
||||
flask-wtf = "*"
|
||||
flask-login = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.8"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "f4ff138e539e1c5bcdc3a3f5046b1d5087a0f0315d1a18181fc2c6eeb1227671"
|
||||
"sha256": "234ca1fc7cbb10534d17febc8a9d0320ecb5c6317351bcd97e161c860201b5a5"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -31,6 +31,13 @@
|
|||
"index": "pypi",
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"flask-login": {
|
||||
"hashes": [
|
||||
"sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"flask-sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327",
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
from flask import Flask
|
||||
from config import Config
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import LoginManager
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(Config)
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
login = LoginManager(app)
|
||||
login.login_view = "login"
|
||||
|
||||
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 subprocess import run
|
||||
from database.constants import DB_USER, DB_PW, DB_NAME
|
||||
from app import db, login
|
||||
from flask_login import UserMixin
|
||||
from werkzeug.security import check_password_hash
|
||||
|
||||
|
||||
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))
|
||||
name = db.Column(db.String(60))
|
||||
annual_data = db.relationship("Annual_Data")
|
||||
|
||||
def __init__(self, uid, country, name):
|
||||
self.uid = uid
|
||||
def __init__(self, id, country, name):
|
||||
self.id = id
|
||||
self.country = country
|
||||
self.name = name
|
||||
|
||||
|
@ -18,7 +18,7 @@ class Glacier(db.Model):
|
|||
class Annual_Data(db.Model):
|
||||
__tablename__ = "annual_data"
|
||||
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)
|
||||
length = db.Column(db.Float)
|
||||
elevation = db.Column(db.Float)
|
||||
|
@ -30,15 +30,22 @@ class Annual_Data(db.Model):
|
|||
self.elevation = elevation
|
||||
|
||||
|
||||
class User(db.Model):
|
||||
uid = db.Column(db.Integer, primary_key=True)
|
||||
class User(UserMixin, db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
registration_date = db.Column(
|
||||
db.DateTime, nullable=False, server_default=db.func.now()
|
||||
)
|
||||
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):
|
||||
self.uid = uid
|
||||
def __init__(self, id, username, password_hash):
|
||||
self.id = id
|
||||
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 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("/index")
|
||||
def index():
|
||||
user = {"username": "Bolaji"}
|
||||
posts = [
|
||||
{"author": {"username": "Miloud"}, "body": "Beautiful day in Meknes!"},
|
||||
{"author": {"username": "Sebtaoui"}, "body": "The Farkouss movie was lit!"},
|
||||
]
|
||||
return render_template("index.html", title="Home", user=user, posts=posts)
|
||||
return render_template("index.html", title="Home Page")
|
||||
|
||||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
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 %}
|
||||
</head>
|
||||
<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>
|
||||
{% block content %}{% endblock %}
|
||||
</body>
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<ul>
|
||||
{% for message in messages %}
|
||||
<li>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %} </body>
|
||||
</html>
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Hi, {{ user.username }}!</h1>
|
||||
{% for post in posts %}
|
||||
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
|
||||
{% endfor %}
|
||||
<h1>IGDB: International Glacier Database</h1>
|
||||
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.
|
||||
{% 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):
|
||||
SQLALCHEMY_DATABASE_URI = CONNECTION_URI
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SECRET_KEY = "trolaso"
|
||||
|
|
|
@ -3,6 +3,7 @@ from pandas import DataFrame, concat, read_csv
|
|||
from csv import QUOTE_NONNUMERIC
|
||||
from database.constants import ADMIN_PW
|
||||
from os import path
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
|
||||
def country_conversion(political_unit) -> str:
|
||||
|
@ -40,7 +41,7 @@ def rename_fields(df_list):
|
|||
new_fields = {
|
||||
"POLITICAL_UNIT": "country",
|
||||
"NAME": "name",
|
||||
"WGMS_ID": "uid",
|
||||
"WGMS_ID": "id",
|
||||
"YEAR": "year",
|
||||
"MEDIAN_ELEVATION": "elevation",
|
||||
"AREA": "surface",
|
||||
|
@ -57,7 +58,11 @@ def create_databases(df):
|
|||
"annual_data": "../data/annual_data.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 = {
|
||||
"glacier": df[["POLITICAL_UNIT", "NAME", "WGMS_ID"]].drop_duplicates(),
|
||||
"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
|
||||
2665,2014,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
|
||||
"Antarctica","BAHIA DEL DIABLO",2665
|
||||
"Antarctica","BELLINGSHAUSEN",6833
|
||||
|
|
|
|
@ -1,2 +1,2 @@
|
|||
"uid","username","password"
|
||||
7843,"admin","fuckmonsanto"
|
||||
"id","username","password_hash"
|
||||
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
|
||||
- País - /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
|
||||
- ID del glaciar (Compatible con la WGMS) - /Cadena de 5 caracteres/
|
||||
- ID del glaciar (Compatible con la WGMS) - /Cadena de 20 caracteres/
|
||||
- Área - /Decimal/
|
||||
- Volumen - /Decimal/
|
||||
- Altura - /Decimal/
|
||||
- Año - /Decimal/
|
||||
- Año - /Entero de 11 dígitos/
|
||||
|
||||
3. *RD3*: Datos de cambio de un glaciar
|
||||
- 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
|
||||
3. *RD3*: Datos del administrador
|
||||
- ID - /Entero de 11 dígitos/
|
||||
- Fecha y hora de alta - /Fecha y hora en formato yyyy-mm-dd hh:mm/
|
||||
- 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
|
||||
|
||||
|
|
Loading…
Reference in New Issue