merge upstream
This commit is contained in:
commit
721ae0c79e
17 files changed
+431
-64
No files matched your search
+87
-2
@@ -8,9 +8,10 @@ import random
|
||||
|
||||
|
||||
def register_user(username, email, password, account_type="private_user"):
|
||||
email = email.strip().lower()
|
||||
with get_db() as conn:
|
||||
existing = conn.execute(
|
||||
"SELECT user_id FROM users WHERE username = ? OR email = ?",
|
||||
"SELECT user_id FROM users WHERE username = ? OR LOWER(email) = LOWER(?)",
|
||||
(username, email)
|
||||
).fetchone()
|
||||
if existing:
|
||||
@@ -61,6 +62,49 @@ def create_token_for_user(user_id):
|
||||
return token
|
||||
print("hello")
|
||||
|
||||
def create_password_reset_request(email):
|
||||
user = get_user_by_email(email)
|
||||
if not user:
|
||||
return None
|
||||
reset_token = secrets.token_hex(64)
|
||||
with get_db() as conn:
|
||||
conn.execute("""
|
||||
INSERT INTO password_resets (
|
||||
token,
|
||||
user_id,
|
||||
created_at
|
||||
)
|
||||
VALUES (?, ?, ?)
|
||||
""", (reset_token, user["user_id"], datetime.now()))
|
||||
return reset_token
|
||||
|
||||
|
||||
def get_password_reset_request(token):
|
||||
with get_db() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM password_resets WHERE token = ? LIMIT 1",
|
||||
(token,)
|
||||
).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
def reset_password(token, password):
|
||||
reset_request = get_password_reset_request(token)
|
||||
if not reset_request:
|
||||
return False
|
||||
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(14))
|
||||
with get_db() as conn:
|
||||
conn.execute(
|
||||
"UPDATE users SET password_hash = ? WHERE user_id = ?",
|
||||
(password_hash, reset_request["user_id"])
|
||||
)
|
||||
conn.execute(
|
||||
"DELETE FROM password_resets WHERE token = ?",
|
||||
(token,)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def delete_token(token):
|
||||
with get_db() as conn:
|
||||
conn.execute("""
|
||||
@@ -68,6 +112,14 @@ def delete_token(token):
|
||||
WHERE token = ?
|
||||
""", (token,))
|
||||
|
||||
def get_user_by_email(email):
|
||||
with get_db() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM users WHERE LOWER(email) = LOWER(?) LIMIT 1",
|
||||
(email,)
|
||||
).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def add_species(english, latin, body, category, extinction_risk, image_id):
|
||||
with get_db() as conn:
|
||||
try:
|
||||
@@ -424,4 +476,37 @@ def search_programs(query):
|
||||
WHERE instr(LOWER(COALESCE(CAST(title AS TEXT), '')), ?1) > 0
|
||||
OR instr(LOWER(COALESCE(CAST(description AS TEXT), '')), ?1) > 0
|
||||
""", (query.lower(),)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def update_user_settings(user_id, username, email, account_type=None):
|
||||
email = email.strip().lower()
|
||||
with get_db() as conn:
|
||||
# Check if username is taken by someone else
|
||||
existing_username = conn.execute(
|
||||
"SELECT user_id FROM users WHERE username = ? AND user_id != ?",
|
||||
(username, user_id)
|
||||
).fetchone()
|
||||
if existing_username:
|
||||
return False, "Username already taken"
|
||||
|
||||
# Check if email is taken by someone else
|
||||
existing_email = conn.execute(
|
||||
"SELECT user_id FROM users WHERE LOWER(email) = LOWER(?) AND user_id != ?",
|
||||
(email, user_id)
|
||||
).fetchone()
|
||||
if existing_email:
|
||||
return False, "Email already taken"
|
||||
|
||||
# Update the user
|
||||
if account_type is not None:
|
||||
conn.execute(
|
||||
"UPDATE users SET username = ?, email = ?, account_type = ? WHERE user_id = ?",
|
||||
(username, email, account_type, user_id)
|
||||
)
|
||||
else:
|
||||
conn.execute(
|
||||
"UPDATE users SET username = ?, email = ? WHERE user_id = ?",
|
||||
(username, email, user_id)
|
||||
)
|
||||
return True, "Settings updated successfully"
|
||||
+63
-16
@@ -1,11 +1,51 @@
|
||||
import os
|
||||
import sqlite3
|
||||
from contextlib import contextmanager
|
||||
|
||||
DATABASE_PATH = "database/database.db"
|
||||
DATABASE_URL = os.getenv("DATABASE_URL")
|
||||
USE_POSTGRES = DATABASE_URL is not None
|
||||
|
||||
db = sqlite3.connect(DATABASE_PATH, check_same_thread=False)
|
||||
db.row_factory = sqlite3.Row
|
||||
db.execute("""
|
||||
if USE_POSTGRES:
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
|
||||
class DBCursor:
|
||||
def __init__(self, cursor):
|
||||
self._cursor = cursor
|
||||
|
||||
def execute(self, sql, params=None):
|
||||
if params is None:
|
||||
params = ()
|
||||
sql = sql.replace("?", "%s")
|
||||
return self._cursor.execute(sql, params)
|
||||
|
||||
def executemany(self, sql, params=None):
|
||||
if params is None:
|
||||
params = ()
|
||||
sql = sql.replace("?", "%s")
|
||||
return self._cursor.executemany(sql, params)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._cursor, name)
|
||||
|
||||
@contextmanager
|
||||
def get_db():
|
||||
conn = psycopg2.connect(DATABASE_URL, cursor_factory=psycopg2.extras.RealDictCursor)
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
yield DBCursor(cursor)
|
||||
conn.commit()
|
||||
except Exception:
|
||||
conn.rollback()
|
||||
raise
|
||||
finally:
|
||||
cursor.close()
|
||||
conn.close()
|
||||
else:
|
||||
db = sqlite3.connect(DATABASE_PATH, check_same_thread=False)
|
||||
db.row_factory = sqlite3.Row
|
||||
db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
@@ -15,22 +55,29 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
db.execute("""
|
||||
db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
token TEXT PRIMARY KEY,
|
||||
user_id INTEGER
|
||||
)
|
||||
""")
|
||||
db.commit()
|
||||
db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS password_resets (
|
||||
token TEXT PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
db.commit()
|
||||
|
||||
@contextmanager
|
||||
def get_db():
|
||||
conn = db.cursor()
|
||||
try:
|
||||
yield conn
|
||||
db.commit()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
@contextmanager
|
||||
def get_db():
|
||||
conn = db.cursor()
|
||||
try:
|
||||
yield conn
|
||||
db.commit()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
+22
-9
@@ -1,4 +1,5 @@
|
||||
from database.db_connection import get_db, get_db
|
||||
from database.db_connection import get_db
|
||||
from database.db_commands import register_user
|
||||
from datetime import datetime
|
||||
|
||||
SEED_SPECIES = [
|
||||
@@ -126,10 +127,9 @@ def init_database():
|
||||
photoid TEXT
|
||||
)
|
||||
""")
|
||||
init_accounts_database()
|
||||
print("Database initialised.")
|
||||
seed_species()
|
||||
init_programs_database()
|
||||
init_articles()
|
||||
|
||||
def init_articles():
|
||||
with get_db() as conn:
|
||||
@@ -211,6 +211,19 @@ def init_accounts_database():
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
token TEXT PRIMARY KEY,
|
||||
user_id INTEGER
|
||||
)
|
||||
""")
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS password_resets (
|
||||
token TEXT PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
print("Accounts database initialised.")
|
||||
|
||||
|
||||
@@ -281,9 +294,9 @@ def init_programs_database():
|
||||
""")
|
||||
print("Programs database tables initialised.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_database()
|
||||
init_accounts_database()
|
||||
init_classes_database()
|
||||
init_programs_database()
|
||||
init_database()
|
||||
init_accounts_database()
|
||||
init_classes_database()
|
||||
init_programs_database()
|
||||
init_articles()
|
||||
register_user("admin", "admin@komodohub.org", "admin", "admin")
|
||||
@@ -35,6 +35,7 @@ def import_pages(route, app):
|
||||
|
||||
app = Flask(__name__, static_folder="static", static_url_path="")
|
||||
app.secret_key = "9279dc6c-d2bc-4f89-a1b3-fc1438a6910c"
|
||||
init_database()
|
||||
|
||||
@app.before_request
|
||||
def load_user():
|
||||
@@ -64,7 +65,6 @@ def handle_bad_request(e):
|
||||
), 500
|
||||
|
||||
|
||||
init_database()
|
||||
import_pages("routes", app)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -6,3 +6,4 @@ itsdangerous==2.2.0
|
||||
Jinja2==3.1.6
|
||||
MarkupSafe==3.0.3
|
||||
Werkzeug==3.1.5
|
||||
psycopg2-binary==2.9.10
|
||||
@@ -1,5 +1,11 @@
|
||||
from flask import render_template, Blueprint, request, redirect, url_for, Response, g
|
||||
from database.db_commands import register_user, login_user
|
||||
from database.db_commands import (
|
||||
register_user,
|
||||
login_user,
|
||||
create_password_reset_request,
|
||||
get_password_reset_request,
|
||||
reset_password,
|
||||
)
|
||||
|
||||
page = Blueprint("login", __name__, url_prefix="")
|
||||
|
||||
@@ -24,6 +30,58 @@ def login():
|
||||
return resp
|
||||
|
||||
|
||||
@page.route("/password-reset", methods=["GET", "POST"])
|
||||
def password_reset_request():
|
||||
if g.user:
|
||||
return redirect(url_for("login.dashboard"))
|
||||
message = None
|
||||
reset_link = None
|
||||
if request.method == "POST":
|
||||
email = request.form.get("email", "").strip().lower()
|
||||
token = create_password_reset_request(email)
|
||||
message = (
|
||||
"If an account with that email exists, a password reset link has been created."
|
||||
)
|
||||
if token:
|
||||
reset_link = url_for("login.password_reset", token=token, _external=True)
|
||||
return render_template(
|
||||
"accounts/password_reset_request.jinja",
|
||||
message=message,
|
||||
reset_link=reset_link,
|
||||
)
|
||||
|
||||
|
||||
@page.route("/password-reset/<token>", methods=["GET", "POST"])
|
||||
def password_reset(token):
|
||||
if g.user:
|
||||
return redirect(url_for("login.dashboard"))
|
||||
error = None
|
||||
success = None
|
||||
reset_request = get_password_reset_request(token)
|
||||
if not reset_request:
|
||||
error = "This reset link is invalid or has expired."
|
||||
if request.method == "POST" and reset_request:
|
||||
password = request.form.get("password", "")
|
||||
password_confirm = request.form.get("password_confirm", "")
|
||||
if password != password_confirm:
|
||||
error = "Passwords do not match."
|
||||
elif len(password) < 8:
|
||||
error = "Password must be at least 8 characters."
|
||||
else:
|
||||
if reset_password(token, password):
|
||||
success = "Your password has been reset. Please log in."
|
||||
reset_request = None
|
||||
else:
|
||||
error = "Unable to reset your password. Please request a new link."
|
||||
return render_template(
|
||||
"accounts/password_reset.jinja",
|
||||
error=error,
|
||||
success=success,
|
||||
token=token,
|
||||
reset_request=reset_request,
|
||||
)
|
||||
|
||||
|
||||
@page.route("/register", methods=["GET", "POST"])
|
||||
def register():
|
||||
if g.user:
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from flask import render_template, Blueprint, g, redirect, url_for, request, flash
|
||||
from database.db_commands import update_user_settings
|
||||
|
||||
|
||||
page = Blueprint('settings', __name__, url_prefix='/settings')
|
||||
|
||||
|
||||
@page.route('/', methods=['GET', 'POST'])
|
||||
def home():
|
||||
if not g.user:
|
||||
return redirect(url_for('login.login'))
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username', '').strip()
|
||||
email = request.form.get('email', '').strip()
|
||||
account_type_display = request.form.get('account_type', '').strip()
|
||||
|
||||
# Map display names to database values
|
||||
account_type_map = {
|
||||
'private account': 'private_user',
|
||||
'teacher': 'teacher',
|
||||
'student': 'student'
|
||||
}
|
||||
account_type = account_type_map.get(account_type_display)
|
||||
|
||||
if not username or not email or not account_type:
|
||||
flash('Username, email, and account type are required.', 'error')
|
||||
return redirect(url_for('settings.home'))
|
||||
|
||||
if '@' not in email or '.' not in email:
|
||||
flash('Please enter a valid email address.', 'error')
|
||||
return redirect(url_for('settings.home'))
|
||||
|
||||
success, message = update_user_settings(g.user['user_id'], username, email, account_type)
|
||||
flash(message, 'success' if success else 'error')
|
||||
if success:
|
||||
return redirect(url_for('settings.home'))
|
||||
|
||||
# Map database values to display names for the template
|
||||
account_type_display_map = {
|
||||
'private_user': 'private account',
|
||||
'teacher': 'teacher',
|
||||
'student': 'student'
|
||||
}
|
||||
current_account_type = account_type_display_map.get(g.user.get('account_type', 'private_user'), 'private account')
|
||||
|
||||
return render_template('settings/settings.jinja', current_account_type=current_account_type)
|
||||
@@ -17,7 +17,7 @@ page = Blueprint("article", __name__)
|
||||
def data(article_id):
|
||||
database_entry = get_article_by_id(article_id)
|
||||
if database_entry is None:
|
||||
raise TypeError(f"No species named '{species_english}' found in the database.")
|
||||
raise TypeError(f"No article named '{article_id}' found in the database.")
|
||||
return render_template(
|
||||
"library/article.jinja",
|
||||
article_id =database_entry["article_id" ],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from flask import render_template, Blueprint, request, redirect, url_for
|
||||
from flask import render_template, Blueprint, request, redirect, url_for, g
|
||||
from database.db_commands import delete_species, get_species_by_id, update_species, get_species_by_name
|
||||
import uuid
|
||||
|
||||
@@ -7,6 +7,8 @@ page = Blueprint("species_editor", __name__, url_prefix="/species/editor")
|
||||
|
||||
@page.route("/<species_id>")
|
||||
def edit_species(species_id):
|
||||
if not g.user or g.user["account_type"] != "admin":
|
||||
return "access denied", 401
|
||||
species_to_edit = get_species_by_id(species_id)
|
||||
print(species_id)
|
||||
return render_template(
|
||||
@@ -17,6 +19,8 @@ def edit_species(species_id):
|
||||
|
||||
@page.route("/<species_id>", methods=["POST"])
|
||||
def species_update(species_id):
|
||||
if not g.user or g.user["account_type"] != "admin":
|
||||
return "access denied", 401
|
||||
try:
|
||||
eng_name = request.form["eng_name"].title()
|
||||
latin_name = request.form["latin_name"].capitalize()
|
||||
@@ -43,6 +47,8 @@ def species_update(species_id):
|
||||
|
||||
@page.route("/success/<eng_name>")
|
||||
def edit_success(eng_name):
|
||||
if not g.user or g.user["account_type"] != "admin":
|
||||
return "access denied", 401
|
||||
return render_template(
|
||||
"species_portal/species_portal_success.jinja",
|
||||
eng_name = eng_name,
|
||||
@@ -50,6 +56,8 @@ def edit_success(eng_name):
|
||||
|
||||
@page.route("/delete/<species_english>", methods=["POST"])
|
||||
def delete_species_from_database(species_english):
|
||||
if not g.user or g.user["account_type"] != "admin":
|
||||
return "access denied", 401
|
||||
try:
|
||||
entry = get_species_by_name(species_english)
|
||||
if entry is None:
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
from flask import render_template, Blueprint, request, redirect, url_for, flash
|
||||
from database.db_commands import add_species, delete_species, get_species_by_name, get_species_by_id
|
||||
from flask import render_template, Blueprint, request, redirect, url_for, flash, g
|
||||
from database.db_commands import (
|
||||
add_species,
|
||||
delete_species,
|
||||
get_species_by_name,
|
||||
get_species_by_id,
|
||||
)
|
||||
import os
|
||||
import uuid
|
||||
|
||||
@@ -8,43 +13,53 @@ page = Blueprint("species_portal", __name__, url_prefix="/species/portal")
|
||||
|
||||
@page.route("/")
|
||||
def portal():
|
||||
if not g.user or g.user["account_type"] != "admin":
|
||||
return "access denied", 401
|
||||
return render_template("species_portal/species_portal.jinja")
|
||||
|
||||
|
||||
@page.route("/", methods=["POST"])
|
||||
def add_species_to_database():
|
||||
if not g.user or g.user["account_type"] != "admin":
|
||||
return "access denied", 401
|
||||
try:
|
||||
eng_name = request.form["eng_name"].title()
|
||||
latin_name = request.form["latin_name"].capitalize()
|
||||
main_text = request.form["main_text"]
|
||||
category = request.form["category"].capitalize()
|
||||
eng_name = request.form["eng_name"].title()
|
||||
latin_name = request.form["latin_name"].capitalize()
|
||||
main_text = request.form["main_text"]
|
||||
category = request.form["category"].capitalize()
|
||||
extinction_risk = request.form["extinction_risk"]
|
||||
species_image = request.files["species_image"]
|
||||
image_id = ""
|
||||
species_image = request.files["species_image"]
|
||||
image_id = ""
|
||||
if species_image:
|
||||
image_id = str(uuid.uuid4())
|
||||
species_image.save(f"database/images/{image_id}")
|
||||
print(category, extinction_risk)
|
||||
add_species(eng_name, latin_name, main_text, category, extinction_risk, image_id)
|
||||
|
||||
add_species(
|
||||
eng_name, latin_name, main_text, category, extinction_risk, image_id
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"species_portal/species_portal_success.jinja",
|
||||
eng_name = eng_name,
|
||||
)
|
||||
eng_name=eng_name,
|
||||
)
|
||||
except ValueError as error_information:
|
||||
species_to_edit = get_species_by_name(request.form["eng_name"].title())
|
||||
species_id = species_to_edit['species_id']
|
||||
flash(f"{species_to_edit['species_english']} already exists. You have been redirected to the editor to update it.", "info")
|
||||
species_id = species_to_edit["species_id"]
|
||||
flash(
|
||||
f"{species_to_edit['species_english']} already exists. You have been redirected to the editor to update it.",
|
||||
"info",
|
||||
)
|
||||
return redirect(url_for("species_editor.edit_species", species_id=species_id))
|
||||
except Exception as error_information:
|
||||
return render_template(
|
||||
"species_portal/species_portal_failed.jinja",
|
||||
error_information=error_information
|
||||
)
|
||||
|
||||
error_information=error_information,
|
||||
)
|
||||
|
||||
|
||||
@page.route("/<eng_name>")
|
||||
def portal_success(eng_name):
|
||||
return render_template(
|
||||
"species_portal/species_portal_success.jinja",
|
||||
eng_name = eng_name,
|
||||
)
|
||||
"species_portal/species_portal_success.jinja",
|
||||
eng_name=eng_name,
|
||||
)
|
||||
@@ -566,7 +566,6 @@ input:focus {
|
||||
color: whitesmoke;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 200px;
|
||||
text-align: center;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<button type="submit" class="submit-button">
|
||||
Login
|
||||
</button>
|
||||
<p class="form-link"><a href="{{ url_for('login.password_reset_request') }}">Forgot your password?</a></p>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,35 @@
|
||||
{% extends "base.jinja" %}
|
||||
{% block title %} Set New Password {% endblock %}
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% from 'button.jinja' import button %}
|
||||
<section id="hero" class="form">
|
||||
<div class="container">
|
||||
{% if error %}
|
||||
<p class="form-error">{{ error }}</p>
|
||||
{% endif %}
|
||||
{% if success %}
|
||||
<p class="form-success">{{ success }}</p>
|
||||
<p><a href="{{ url_for('login.login') }}">Return to login</a></p>
|
||||
{% elif reset_request %}
|
||||
<form action="{{ url_for('login.password_reset', token=token) }}" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="password">New Password:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_confirm">Confirm Password:</label>
|
||||
<input type="password" id="password_confirm" name="password_confirm" required>
|
||||
</div>
|
||||
<button type="submit" class="submit-button">
|
||||
Reset Password
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>Please request a new password reset link.</p>
|
||||
<p><a href="{{ url_for('login.password_reset_request') }}">Request another reset link</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,26 @@
|
||||
{% extends "base.jinja" %}
|
||||
{% block title %} Password Reset {% endblock %}
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% from 'button.jinja' import button %}
|
||||
<section id="hero" class="form">
|
||||
<form action="{{ url_for('login.password_reset_request') }}" class="container" method="POST">
|
||||
{% if message %}
|
||||
<p class="form-success">{{ message }}</p>
|
||||
{% endif %}
|
||||
{% if reset_link %}
|
||||
<p class="form-info">
|
||||
Reset link: <a style="word-break: break-all;" href="{{ reset_link }}">{{ reset_link }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<button type="submit" class="submit-button">
|
||||
Send Reset Link
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
+15
-11
@@ -2,15 +2,16 @@
|
||||
<div class="navbar">
|
||||
<div class="left">
|
||||
<a href="/" class="left">
|
||||
<img src="/images/lizard.png" class="logo-img">
|
||||
<span class="logo-text">Komodo Hub</span>
|
||||
<img src="/images/lizard.png" class="logo-img">
|
||||
<span class="logo-text">Komodo Hub</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="links">
|
||||
{# todo #}
|
||||
{% if user %}
|
||||
{% if user["account_type"] == "admin" %}
|
||||
<div class="dropdown">
|
||||
<button class="dropbtn">Devtools ▾</button>
|
||||
<a class="dropbtn">Devtools ▾</a>
|
||||
<div class="dropdown-content">
|
||||
<a href="/about">About</a>
|
||||
<a href="/species/portal">Add Species</a>
|
||||
@@ -21,14 +22,17 @@
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
{# todo #}
|
||||
{% if user %}
|
||||
{% if user[4] %}
|
||||
<a href="/admin">Admin</a>
|
||||
{% endif %}
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
<a href="/settings">Settings</a>
|
||||
<a href="/logout">Logout</a>
|
||||
<div class="dropdown">
|
||||
<a class="dropbtn">{{user["username"]}} ▾</a>
|
||||
<div class="dropdown-content">
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
<a href="/settings">Settings</a>
|
||||
<a href="/logout">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/about">About</a>
|
||||
{% else %}
|
||||
{{ button(url='/login', button_text='Login', extra_classes='') }}
|
||||
{{ button(url='/register', button_text='Register', extra_classes='') }}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{% extends "base.jinja" %}
|
||||
|
||||
{% block title %}Settings{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h1>Settings</h1>
|
||||
<form method="post" action="{{ url_for('settings.home') }}">
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" value="{{user.username }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email" value="{{user.email }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="account_type">Account Type:</label>
|
||||
<select id="account_type" name="account_type" required>
|
||||
<option value="private account" {% if current_account_type == 'private account' %}selected{% endif %}>Private Account</option>
|
||||
<option value="teacher" {% if current_account_type == 'teacher' %}selected{% endif %}>Teacher</option>
|
||||
<option value="student" {% if current_account_type == 'student' %}selected{% endif %}>Student</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -35,8 +35,9 @@
|
||||
<p>{{ main_text }}</p>
|
||||
</section>
|
||||
</div>
|
||||
{# this button should only be visible to certain account types #}
|
||||
{% if user and user["account_type"] == "admin"%}
|
||||
<div style="text-align: right;">
|
||||
{{ button(url='/species/editor/'+species_id, button_text='Edit Species') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user