Add a visible login and register page
Add users page to admin site Change admin site to use permissions instead of roles Fix issue with flask-security-too user_datastore giving error
This commit is contained in:
parent
0738cf84be
commit
105706680b
@ -1,42 +1,41 @@
|
||||
import json
|
||||
from flask import render_template, Blueprint, request, jsonify, redirect, url_for
|
||||
from flask_security import roles_required
|
||||
from flask_security import roles_required, permissions_accepted
|
||||
from QuizTheWord import database
|
||||
from QuizTheWord.database import get_question
|
||||
|
||||
|
||||
Admin = Blueprint("admin", __name__, url_prefix="/admin", template_folder="templates", static_folder="static")
|
||||
|
||||
|
||||
@Admin.route("/")
|
||||
@roles_required("admin")
|
||||
@permissions_accepted("admin_site_access")
|
||||
def index():
|
||||
questions = database.get_unhealthy_questions()
|
||||
return render_template("admin/index.html", title="admin", questions=questions)
|
||||
|
||||
|
||||
@Admin.route("/questions/")
|
||||
@roles_required("admin")
|
||||
@permissions_accepted("question_edit", "question_add")
|
||||
def questions():
|
||||
return render_template("admin/question_list.html")
|
||||
|
||||
|
||||
@Admin.route("/questions/edit/<int:question_id>", methods=["GET", "POST"])
|
||||
@roles_required("admin")
|
||||
@permissions_accepted("question_edit")
|
||||
def edit_question(question_id):
|
||||
if request.method == "POST":
|
||||
data = parse_question_form_data(request.form)
|
||||
database.update_question(question_id, data)
|
||||
return redirect(url_for("admin.edit_question", question_id=question_id))
|
||||
|
||||
question: database.AllQuestions = get_question(database.AllQuestions, question_id)
|
||||
question: database.AllQuestions = database.get_question(database.AllQuestions, question_id)
|
||||
if "application/json" in request.accept_mimetypes.values():
|
||||
return jsonify(question.get_dict())
|
||||
return render_template("admin/edit_question.html", question=question)
|
||||
|
||||
|
||||
@Admin.route("/question_query")
|
||||
@roles_required("admin")
|
||||
@Admin.route("/questions/query")
|
||||
@permissions_accepted("admin_site_access")
|
||||
def query_questions():
|
||||
offset = request.args.get("offset", type=int)
|
||||
limit = request.args.get("limit", type=int)
|
||||
@ -61,6 +60,7 @@ def query_questions():
|
||||
|
||||
|
||||
@Admin.route("/questions/add", methods=["GET", "POST"])
|
||||
@permissions_accepted("question_add")
|
||||
def create_question():
|
||||
if request.method == "POST":
|
||||
data = parse_question_form_data(request.form)
|
||||
@ -70,6 +70,50 @@ def create_question():
|
||||
return render_template("admin/edit_question.html", question=question)
|
||||
|
||||
|
||||
@Admin.route("/users/")
|
||||
@permissions_accepted("user_edit")
|
||||
def users():
|
||||
return render_template("admin/user_list.html")
|
||||
|
||||
|
||||
@Admin.route("/users/edit/<int:user_id>", methods=["GET", "POST"])
|
||||
@permissions_accepted("user_edit")
|
||||
def edit_user(user_id):
|
||||
if request.method == "POST":
|
||||
data = parse_user_form_data(request.form)
|
||||
database.update_user(user_id, data)
|
||||
return redirect(url_for("admin.edit_user", user_id=user_id))
|
||||
|
||||
user = database.get_user(user_id)
|
||||
roles = database.get_all_roles()
|
||||
if "application/json" in request.accept_mimetypes.values():
|
||||
return jsonify(user.get_dict())
|
||||
return render_template("admin/edit_user.html", user=user, roles=roles)
|
||||
|
||||
|
||||
@Admin.route("/users/query")
|
||||
@permissions_accepted("user_edit")
|
||||
def query_users():
|
||||
offset = request.args.get("offset", type=int)
|
||||
limit = request.args.get("limit", type=int)
|
||||
sort = request.args.get("sort")
|
||||
order = request.args.get("order")
|
||||
query = parse_user_query(request.args.get("filter"))
|
||||
result = database.query_users(offset, limit, query, sort, order)
|
||||
response_dict = {
|
||||
"total": result[1],
|
||||
"rows": [],
|
||||
}
|
||||
for user in result[0]:
|
||||
response_dict["rows"].append({
|
||||
"id": user.user_id,
|
||||
"user_id": user.user_id,
|
||||
"username": user.username,
|
||||
"email": user.email,
|
||||
})
|
||||
return jsonify(response_dict)
|
||||
|
||||
|
||||
def parse_question_query(query):
|
||||
if query:
|
||||
query: dict = json.loads(query)
|
||||
@ -99,3 +143,21 @@ def parse_question_form_data(form):
|
||||
if hidden_answer_difficulty is not None:
|
||||
data["hidden_answer_difficulty"] = hidden_answer_difficulty
|
||||
return data
|
||||
|
||||
|
||||
def parse_user_form_data(form):
|
||||
data = {
|
||||
"username": form.get("username"),
|
||||
"email": form.get("email"),
|
||||
"roles": form.getlist("roles")
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
def parse_user_query(query):
|
||||
if query:
|
||||
query: dict = json.loads(query)
|
||||
# if "admin" in query.keys():
|
||||
# query["admin"] = True if query["admin"] == "true" else False
|
||||
return query
|
||||
return None
|
||||
|
@ -7,7 +7,7 @@
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
<script src="https://kit.fontawesome.com/f2d4307e62.js" crossorigin="anonymous"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cosmo/bootstrap.min.css">
|
||||
{# <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cosmo/bootstrap.min.css">#}
|
||||
<link rel="stylesheet" href="{{ url_for('admin.static', filename='style.css') }}">
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
@ -26,6 +26,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin.questions') }}">Questions</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin.users') }}">Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,15 +4,15 @@
|
||||
<form class="container mt-5" method="post">
|
||||
<div class="form-group">
|
||||
<label for="question_text">Question</label>
|
||||
<input id="question_text" name="question_text" type="text" class="form-control" value="{{ question.question_text }}">
|
||||
<input id="question_text" name="question_text" type="text" class="form-control" value="{{ question.question_text if question.question_text is not none else "" }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="answer">Answer</label>
|
||||
<input id="answer" name="answer" type="text" class="form-control" value="{{ question.answer }}">
|
||||
<input id="answer" name="answer" type="text" class="form-control" value="{{ question.answer if question.answer is not none else "" }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="addresses">Bible Verses</label>
|
||||
<input id="addresses" name="addresses" type="text" class="form-control" value="{{ question.addresses }}">
|
||||
<input id="addresses" name="addresses" type="text" class="form-control" value="{{ question.addresses if question.addresses is not none else "" }}">
|
||||
</div>
|
||||
<label class="mt-3">Multiple Choice</label>
|
||||
<div class="container border mb-1">
|
||||
@ -98,7 +98,7 @@
|
||||
return $("<div>")
|
||||
.addClass("form-group")
|
||||
.append($("<select>")
|
||||
.addClass("custom-select")
|
||||
.addClass("form-select")
|
||||
.attr({"id": category + "_difficulty", "name": category + "_difficulty"})
|
||||
.append(option_none)
|
||||
.append(option_easy)
|
||||
|
25
QuizTheWord/admin/templates/admin/edit_user.html
Normal file
25
QuizTheWord/admin/templates/admin/edit_user.html
Normal file
@ -0,0 +1,25 @@
|
||||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<form class="container mt-5" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input id="email" type="email" class="form-control" name="email" value="{{ user.email if user.email is not none else "" }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input id="username" type="text" class="form-control" name="username" value="{{ user.username if user.username is not none else "" }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="list-group">
|
||||
{% for role in roles %}
|
||||
<label class="list-group-item">
|
||||
<input class="form-check-input me-1" type="checkbox" name="roles" value="{{ role.name }}" {{ "checked" if user.has_role(role) else "" }}>
|
||||
{{ role.name }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
{% endblock %}
|
@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
$("#question-table").bootstrapTable({
|
||||
url: "/admin/question_query",
|
||||
url: "{{ url_for('admin.query_questions') }}",
|
||||
pagination: true,
|
||||
sidePagination: "server",
|
||||
filterControl: true,
|
||||
@ -37,8 +37,6 @@
|
||||
showRefresh: true,
|
||||
searchOnEnterKey: true,
|
||||
onClickRow: (item, element) => {
|
||||
console.log(item);
|
||||
console.log(element);
|
||||
window.location.href = `/admin/questions/edit/${item["question_id"]}`
|
||||
},
|
||||
onLoadSuccess: enable_input,
|
||||
|
67
QuizTheWord/admin/templates/admin/user_list.html
Normal file
67
QuizTheWord/admin/templates/admin/user_list.html
Normal file
@ -0,0 +1,67 @@
|
||||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="{{ url_for('admin.static', filename='bootstrap-table.min.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('admin.static', filename='bootstrap-table-filter-control.min.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<table id="user-table" class="table"></table>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('admin.static', filename='bootstrap-table.min.js') }}"></script>
|
||||
<script src="{{ url_for('admin.static', filename='bootstrap-table-filter-control.min.js') }}"></script>
|
||||
<script>
|
||||
function disable_input() {
|
||||
$("input, select").attr("disabled", true);
|
||||
}
|
||||
|
||||
function enable_input() {
|
||||
$("input, select").removeAttr("disabled");
|
||||
}
|
||||
|
||||
$("#user-table").bootstrapTable({
|
||||
url: "{{ url_for('admin.query_users') }}",
|
||||
pagination: true,
|
||||
sidePagination: "server",
|
||||
filterControl: true,
|
||||
showRefresh: true,
|
||||
searchOnEnterKey: true,
|
||||
onClickRow: (item, element) => {
|
||||
window.location.href = `/admin/users/edit/${item["user_id"]}`
|
||||
},
|
||||
onLoadSuccess: enable_input,
|
||||
onPageChange: disable_input,
|
||||
onSearch: disable_input,
|
||||
{#buttons: [#}
|
||||
{# {#}
|
||||
{# html: "<a class='btn btn-secondary' href='{{ url_for('admin.create_user') }}'>Add User</a>",#}
|
||||
{# attributes: {#}
|
||||
{# title: "Add a new user to the database."#}
|
||||
{# }#}
|
||||
{# }#}
|
||||
{#],#}
|
||||
columns: [
|
||||
{
|
||||
field: "user_id",
|
||||
title: "User ID",
|
||||
sortable: true,
|
||||
filterControl: "input",
|
||||
},
|
||||
{
|
||||
field: "username",
|
||||
title: "Username",
|
||||
sortable: true,
|
||||
filterControl: "input",
|
||||
},
|
||||
{
|
||||
field: "email",
|
||||
title: "Email",
|
||||
sortable: true,
|
||||
filterControl: "input",
|
||||
},
|
||||
]
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,19 +1,28 @@
|
||||
import os
|
||||
from flask import request, render_template, jsonify, Flask
|
||||
from flask_security import Security, SQLAlchemySessionUserDatastore
|
||||
from flask import request, render_template, jsonify, Flask, url_for, redirect, flash
|
||||
from flask_security import Security, SQLAlchemySessionUserDatastore, login_user, logout_user, current_user
|
||||
from QuizTheWord import database
|
||||
from QuizTheWord.admin import admin
|
||||
from QuizTheWord import config
|
||||
# from QuizTheWord import config
|
||||
|
||||
app = Flask(__name__)
|
||||
environment_configuration = os.environ.get('CONFIGURATION_SETUP', "QuizTheWord.config.Development")
|
||||
with app.app_context():
|
||||
app.config.from_object(environment_configuration)
|
||||
user_datastore = SQLAlchemySessionUserDatastore(database.get_session(), database.User, database.Role)
|
||||
security = Security(app, user_datastore)
|
||||
security = Security(app, user_datastore, False)
|
||||
app.register_blueprint(admin.Admin)
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def func():
|
||||
return {
|
||||
"user_authenticated": current_user.is_authenticated,
|
||||
"has_admin_access": current_user.has_permission("admin_site_access"),
|
||||
"is_admin": current_user.has_role("admin"),
|
||||
}
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return multiple_choice_category()
|
||||
@ -58,6 +67,42 @@ def check_answer():
|
||||
return jsonify(database.check_answer(question_id, answer))
|
||||
|
||||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
next_page = request.args.get("next", default=url_for("index"))
|
||||
if request.method == "POST":
|
||||
email = request.form.get("email")
|
||||
password = request.form.get("password")
|
||||
remember = request.args.get("remember")
|
||||
user = user_datastore.find_user(email=email)
|
||||
if user is None or not user.check_password(password):
|
||||
flash("invalid email or password")
|
||||
return redirect(url_for("login"))
|
||||
login_user(user, remember=remember)
|
||||
return redirect(next_page)
|
||||
return render_template("login.html", title="login")
|
||||
|
||||
|
||||
@app.route("/register", methods=["GET", "POST"])
|
||||
def register():
|
||||
if request.method == "POST":
|
||||
email = request.form.get("email")
|
||||
password = request.form.get("password")
|
||||
user = database.add_user(email, password)
|
||||
if user is None:
|
||||
flash("email already in use")
|
||||
return redirect(url_for("register"))
|
||||
login_user(user)
|
||||
return redirect(url_for("index"))
|
||||
return render_template("register.html")
|
||||
|
||||
|
||||
@app.route("/logout")
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def error_404(e):
|
||||
print(e)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import os
|
||||
from flask import current_app, g
|
||||
from flask_security import UserMixin, RoleMixin
|
||||
from flask_security import UserMixin, RoleMixin, SQLAlchemySessionUserDatastore, verify_password, hash_password
|
||||
import sqlalchemy
|
||||
from typing import Union, Optional, Literal, Type, List, Tuple
|
||||
import random
|
||||
@ -14,18 +14,19 @@ from werkzeug.utils import import_string
|
||||
|
||||
config = import_string(os.environ.get("CONFIGURATION_SETUP"))
|
||||
engine = create_engine(config.DB_URL, pool_size=20)
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def get_scoped_session():
|
||||
session_factory = sessionmaker(bind=engine)
|
||||
Base.query = scoped_session(session_factory).query_property() # This is for compatibility with Flask-Security-Too which assumes usage of Flask-Sqlalchemy
|
||||
return scoped_session(session_factory)
|
||||
|
||||
|
||||
def get_session() -> sqlalchemy.orm.session.Session:
|
||||
if "session" not in g:
|
||||
if not hasattr(g, "session"):
|
||||
Session = get_scoped_session()
|
||||
Session.query_property()
|
||||
Base.query = Session.query_property() # This is for compatibility with Flask-Security-Too which assumes usage of Flask-Sqlalchemy
|
||||
# Session.query_property()
|
||||
g.session = Session()
|
||||
|
||||
return g.session
|
||||
@ -41,7 +42,8 @@ def destroy_db():
|
||||
Base.metadata.drop_all(engine)
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
def get_user_datastore() -> SQLAlchemySessionUserDatastore:
|
||||
return SQLAlchemySessionUserDatastore(get_session(), User, Role)
|
||||
|
||||
|
||||
class User(Base, UserMixin):
|
||||
@ -59,6 +61,23 @@ class User(Base, UserMixin):
|
||||
fs_uniquifier = Column(String, unique=True, nullable=False)
|
||||
roles = relationship("Role", secondary="users_roles")
|
||||
|
||||
def check_password(self, password):
|
||||
return verify_password(password, self.password)
|
||||
|
||||
def add_role(self, role):
|
||||
self.roles.append(role)
|
||||
|
||||
def remove_role(self, role):
|
||||
self.roles.remove(role)
|
||||
|
||||
def get_dict(self):
|
||||
result = {
|
||||
"user_id": self.user_id,
|
||||
"email": self.email,
|
||||
"username": self.username,
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
class Role(Base, RoleMixin):
|
||||
__tablename__ = "roles"
|
||||
@ -318,3 +337,67 @@ def update_question(question_id, data):
|
||||
if column in HiddenAnswer.__table__.columns:
|
||||
setattr(question.hidden_answer, column, data[column])
|
||||
session.commit()
|
||||
|
||||
|
||||
def add_user(email, password):
|
||||
session = get_session()
|
||||
user_datastore = get_user_datastore()
|
||||
if user_datastore.find_user(email=email) is None:
|
||||
user = user_datastore.create_user(email=email, password=hash_password(password), username=email)
|
||||
session.add(user)
|
||||
role = session.query(Role).filter(Role.name == "basic").one_or_none()
|
||||
if role is not None:
|
||||
user_datastore.add_role_to_user(user, role)
|
||||
# user.add_role(role)
|
||||
# user_datastore.commit()
|
||||
# update_user(user.user_id, {"roles": ["basic"]})
|
||||
# session.add(user)
|
||||
# user_datastore.add_role_to_user(user, "basic")
|
||||
session.commit()
|
||||
return user
|
||||
return None
|
||||
|
||||
|
||||
def get_user(user_id) -> User:
|
||||
session = get_session()
|
||||
return session.query(User).filter(User.user_id == user_id).one_or_none()
|
||||
|
||||
|
||||
def update_user(user_id, data):
|
||||
session = get_session()
|
||||
user_datastore = get_user_datastore()
|
||||
user = session.query(User).filter(User.user_id == user_id).one_or_none()
|
||||
for role in user.roles:
|
||||
if role.name not in data["roles"]:
|
||||
# user.remove_role(role)
|
||||
user_datastore.remove_role_from_user(user, role)
|
||||
for role_name in data["roles"]:
|
||||
role = session.query(Role).filter(Role.name == role_name).one_or_none()
|
||||
# if role is not None:
|
||||
# user.add_role(role)
|
||||
user_datastore.add_role_to_user(user, role)
|
||||
for column in data.keys():
|
||||
if column in User.__table__.columns:
|
||||
setattr(user, column, data[column])
|
||||
session.commit()
|
||||
|
||||
|
||||
def query_users(offset, limit, query: dict = None, sort=None, order=None) -> Tuple[List[User], int]:
|
||||
session = get_session()
|
||||
query_params = []
|
||||
if query is not None:
|
||||
for key in query.keys():
|
||||
if hasattr(User, key):
|
||||
query_params.append(getattr(User, key).ilike("%" + query[key] + "%"))
|
||||
order_by = None
|
||||
if sort and order:
|
||||
order_by = getattr(getattr(User, sort), order)()
|
||||
q = session.query(User).filter(*query_params).order_by(order_by)
|
||||
questions = q.offset(offset).limit(limit).all()
|
||||
count = q.count()
|
||||
return questions, count
|
||||
|
||||
|
||||
def get_all_roles() -> List[Role]:
|
||||
session = get_session()
|
||||
return session.query(Role).all()
|
||||
|
@ -28,6 +28,17 @@
|
||||
<a class="nav-link" href="{{ url_for('hidden_answer_category') }}">Hidden Answer</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ms-auto">
|
||||
{% if not user_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a class="btn btn-primary" href="{{ url_for('login') }}">Login</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="btn btn-secondary" href="{{ url_for('logout') }}">Logout</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -5,7 +5,6 @@
|
||||
<div class="row justify-content-center tab-content">
|
||||
<div class="col-sm col-lg-3">
|
||||
<form method="post">
|
||||
{{ login_user_form.hidden_tag() }}
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input id="email" name="email" type="email" class="form-control">
|
||||
@ -20,6 +19,16 @@
|
||||
</div>
|
||||
<button id="submit" name="submit" type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
Don't have an account? <a href="{{ url_for('register') }}">Register</a>
|
||||
</div>
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div class="alert alert-danger mt-2" role="alert">
|
||||
{{ messages[0] }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
80
QuizTheWord/templates/register.html
Normal file
80
QuizTheWord/templates/register.html
Normal file
@ -0,0 +1,80 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container-fluid mt-5">
|
||||
<div class="row justify-content-center tab-content">
|
||||
<div class="col-sm col-lg-3">
|
||||
<form id="form" class="needs-validation" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input id="email" name="email" type="email" class="form-control" required>
|
||||
{# <div class="invalid-feedback">You must enter an email.</div>#}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input id="password" name="password" type="password" class="form-control" required>
|
||||
{# <div class="invalid-feedback">You must enter a password.</div>#}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password_confirm" class="form-label">Retype Password</label>
|
||||
<input id="password_confirm" name="password_confirm" type="password" class="form-control" required>
|
||||
{# <div class="invalid-feedback">You must confirm your password.</div>#}
|
||||
</div>
|
||||
<button id="submit" name="submit" type="submit" class="btn btn-primary">Register</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
Already have an account? <a href="{{ url_for('login') }}">Login</a>
|
||||
</div>
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div class="alert alert-danger mt-2" role="alert">
|
||||
{{ messages[0] }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
let form = $("#form");
|
||||
let email = $("#email");
|
||||
let password = $("#password");
|
||||
let password_confirm = $("#password_confirm");
|
||||
|
||||
{#$("input[type='password']").attr("minlength", 4)#}
|
||||
|
||||
function password_validate() {
|
||||
let password_val = password.val();
|
||||
if (password_val.length < 4) {
|
||||
return "Password must be at least 4 characters long!";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function password_confirm_validate() {
|
||||
let password_val = password.val();
|
||||
let password_confirm_val = password_confirm.val();
|
||||
if (password_confirm_val !== password_val) {
|
||||
return "passwords do not match!";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function form_valid() {
|
||||
let password_result = password[0].setCustomValidity(password_validate());
|
||||
let password_confirm_result = password_confirm[0].setCustomValidity(password_confirm_validate());
|
||||
return form[0].checkValidity();
|
||||
}
|
||||
|
||||
{#form.on("submit", (event) => {#}
|
||||
{# if (!form_valid()) {#}
|
||||
{# event.preventDefault();#}
|
||||
{# event.stopPropagation()#}
|
||||
{# }#}
|
||||
{# form.addClass("was-validated");#}
|
||||
{#})#}
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,26 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container-fluid mt-5">
|
||||
<div class="row justify-content-center tab-content">
|
||||
<div class="col-sm col-lg-3">
|
||||
<form method="post">
|
||||
{{ register_user_form.hidden_tag() }}
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input id="email" name="email" type="email" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input id="password" name="password" type="password" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password_confirm">Retype Password</label>
|
||||
<input id="password_confirm" name="password_confirm" type="password" class="form-control">
|
||||
</div>
|
||||
<button id="submit" name="submit" type="submit" class="btn btn-primary">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user