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
|
import json
|
||||||
from flask import render_template, Blueprint, request, jsonify, redirect, url_for
|
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 import database
|
||||||
from QuizTheWord.database import get_question
|
|
||||||
|
|
||||||
|
|
||||||
Admin = Blueprint("admin", __name__, url_prefix="/admin", template_folder="templates", static_folder="static")
|
Admin = Blueprint("admin", __name__, url_prefix="/admin", template_folder="templates", static_folder="static")
|
||||||
|
|
||||||
|
|
||||||
@Admin.route("/")
|
@Admin.route("/")
|
||||||
@roles_required("admin")
|
@permissions_accepted("admin_site_access")
|
||||||
def index():
|
def index():
|
||||||
questions = database.get_unhealthy_questions()
|
questions = database.get_unhealthy_questions()
|
||||||
return render_template("admin/index.html", title="admin", questions=questions)
|
return render_template("admin/index.html", title="admin", questions=questions)
|
||||||
|
|
||||||
|
|
||||||
@Admin.route("/questions/")
|
@Admin.route("/questions/")
|
||||||
@roles_required("admin")
|
@permissions_accepted("question_edit", "question_add")
|
||||||
def questions():
|
def questions():
|
||||||
return render_template("admin/question_list.html")
|
return render_template("admin/question_list.html")
|
||||||
|
|
||||||
|
|
||||||
@Admin.route("/questions/edit/<int:question_id>", methods=["GET", "POST"])
|
@Admin.route("/questions/edit/<int:question_id>", methods=["GET", "POST"])
|
||||||
@roles_required("admin")
|
@permissions_accepted("question_edit")
|
||||||
def edit_question(question_id):
|
def edit_question(question_id):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
data = parse_question_form_data(request.form)
|
data = parse_question_form_data(request.form)
|
||||||
database.update_question(question_id, data)
|
database.update_question(question_id, data)
|
||||||
return redirect(url_for("admin.edit_question", question_id=question_id))
|
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():
|
if "application/json" in request.accept_mimetypes.values():
|
||||||
return jsonify(question.get_dict())
|
return jsonify(question.get_dict())
|
||||||
return render_template("admin/edit_question.html", question=question)
|
return render_template("admin/edit_question.html", question=question)
|
||||||
|
|
||||||
|
|
||||||
@Admin.route("/question_query")
|
@Admin.route("/questions/query")
|
||||||
@roles_required("admin")
|
@permissions_accepted("admin_site_access")
|
||||||
def query_questions():
|
def query_questions():
|
||||||
offset = request.args.get("offset", type=int)
|
offset = request.args.get("offset", type=int)
|
||||||
limit = request.args.get("limit", type=int)
|
limit = request.args.get("limit", type=int)
|
||||||
@ -61,6 +60,7 @@ def query_questions():
|
|||||||
|
|
||||||
|
|
||||||
@Admin.route("/questions/add", methods=["GET", "POST"])
|
@Admin.route("/questions/add", methods=["GET", "POST"])
|
||||||
|
@permissions_accepted("question_add")
|
||||||
def create_question():
|
def create_question():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
data = parse_question_form_data(request.form)
|
data = parse_question_form_data(request.form)
|
||||||
@ -70,6 +70,50 @@ def create_question():
|
|||||||
return render_template("admin/edit_question.html", question=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):
|
def parse_question_query(query):
|
||||||
if query:
|
if query:
|
||||||
query: dict = json.loads(query)
|
query: dict = json.loads(query)
|
||||||
@ -99,3 +143,21 @@ def parse_question_form_data(form):
|
|||||||
if hidden_answer_difficulty is not None:
|
if hidden_answer_difficulty is not None:
|
||||||
data["hidden_answer_difficulty"] = hidden_answer_difficulty
|
data["hidden_answer_difficulty"] = hidden_answer_difficulty
|
||||||
return data
|
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') }}">
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
<script src="https://kit.fontawesome.com/f2d4307e62.js" crossorigin="anonymous"></script>
|
<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 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') }}">
|
<link rel="stylesheet" href="{{ url_for('admin.static', filename='style.css') }}">
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
@ -26,6 +26,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('admin.questions') }}">Questions</a>
|
<a class="nav-link" href="{{ url_for('admin.questions') }}">Questions</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('admin.users') }}">Users</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,15 +4,15 @@
|
|||||||
<form class="container mt-5" method="post">
|
<form class="container mt-5" method="post">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="question_text">Question</label>
|
<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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="answer">Answer</label>
|
<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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="addresses">Bible Verses</label>
|
<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>
|
</div>
|
||||||
<label class="mt-3">Multiple Choice</label>
|
<label class="mt-3">Multiple Choice</label>
|
||||||
<div class="container border mb-1">
|
<div class="container border mb-1">
|
||||||
@ -98,7 +98,7 @@
|
|||||||
return $("<div>")
|
return $("<div>")
|
||||||
.addClass("form-group")
|
.addClass("form-group")
|
||||||
.append($("<select>")
|
.append($("<select>")
|
||||||
.addClass("custom-select")
|
.addClass("form-select")
|
||||||
.attr({"id": category + "_difficulty", "name": category + "_difficulty"})
|
.attr({"id": category + "_difficulty", "name": category + "_difficulty"})
|
||||||
.append(option_none)
|
.append(option_none)
|
||||||
.append(option_easy)
|
.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({
|
$("#question-table").bootstrapTable({
|
||||||
url: "/admin/question_query",
|
url: "{{ url_for('admin.query_questions') }}",
|
||||||
pagination: true,
|
pagination: true,
|
||||||
sidePagination: "server",
|
sidePagination: "server",
|
||||||
filterControl: true,
|
filterControl: true,
|
||||||
@ -37,8 +37,6 @@
|
|||||||
showRefresh: true,
|
showRefresh: true,
|
||||||
searchOnEnterKey: true,
|
searchOnEnterKey: true,
|
||||||
onClickRow: (item, element) => {
|
onClickRow: (item, element) => {
|
||||||
console.log(item);
|
|
||||||
console.log(element);
|
|
||||||
window.location.href = `/admin/questions/edit/${item["question_id"]}`
|
window.location.href = `/admin/questions/edit/${item["question_id"]}`
|
||||||
},
|
},
|
||||||
onLoadSuccess: enable_input,
|
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
|
import os
|
||||||
from flask import request, render_template, jsonify, Flask
|
from flask import request, render_template, jsonify, Flask, url_for, redirect, flash
|
||||||
from flask_security import Security, SQLAlchemySessionUserDatastore
|
from flask_security import Security, SQLAlchemySessionUserDatastore, login_user, logout_user, current_user
|
||||||
from QuizTheWord import database
|
from QuizTheWord import database
|
||||||
from QuizTheWord.admin import admin
|
from QuizTheWord.admin import admin
|
||||||
from QuizTheWord import config
|
# from QuizTheWord import config
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
environment_configuration = os.environ.get('CONFIGURATION_SETUP', "QuizTheWord.config.Development")
|
environment_configuration = os.environ.get('CONFIGURATION_SETUP', "QuizTheWord.config.Development")
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
app.config.from_object(environment_configuration)
|
app.config.from_object(environment_configuration)
|
||||||
user_datastore = SQLAlchemySessionUserDatastore(database.get_session(), database.User, database.Role)
|
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.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("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
return multiple_choice_category()
|
return multiple_choice_category()
|
||||||
@ -58,6 +67,42 @@ def check_answer():
|
|||||||
return jsonify(database.check_answer(question_id, 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)
|
@app.errorhandler(404)
|
||||||
def error_404(e):
|
def error_404(e):
|
||||||
print(e)
|
print(e)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
from flask import current_app, g
|
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
|
import sqlalchemy
|
||||||
from typing import Union, Optional, Literal, Type, List, Tuple
|
from typing import Union, Optional, Literal, Type, List, Tuple
|
||||||
import random
|
import random
|
||||||
@ -14,18 +14,19 @@ from werkzeug.utils import import_string
|
|||||||
|
|
||||||
config = import_string(os.environ.get("CONFIGURATION_SETUP"))
|
config = import_string(os.environ.get("CONFIGURATION_SETUP"))
|
||||||
engine = create_engine(config.DB_URL, pool_size=20)
|
engine = create_engine(config.DB_URL, pool_size=20)
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
def get_scoped_session():
|
def get_scoped_session():
|
||||||
session_factory = sessionmaker(bind=engine)
|
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)
|
return scoped_session(session_factory)
|
||||||
|
|
||||||
|
|
||||||
def get_session() -> sqlalchemy.orm.session.Session:
|
def get_session() -> sqlalchemy.orm.session.Session:
|
||||||
if "session" not in g:
|
if not hasattr(g, "session"):
|
||||||
Session = get_scoped_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()
|
g.session = Session()
|
||||||
|
|
||||||
return g.session
|
return g.session
|
||||||
@ -41,7 +42,8 @@ def destroy_db():
|
|||||||
Base.metadata.drop_all(engine)
|
Base.metadata.drop_all(engine)
|
||||||
|
|
||||||
|
|
||||||
Base = declarative_base()
|
def get_user_datastore() -> SQLAlchemySessionUserDatastore:
|
||||||
|
return SQLAlchemySessionUserDatastore(get_session(), User, Role)
|
||||||
|
|
||||||
|
|
||||||
class User(Base, UserMixin):
|
class User(Base, UserMixin):
|
||||||
@ -59,6 +61,23 @@ class User(Base, UserMixin):
|
|||||||
fs_uniquifier = Column(String, unique=True, nullable=False)
|
fs_uniquifier = Column(String, unique=True, nullable=False)
|
||||||
roles = relationship("Role", secondary="users_roles")
|
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):
|
class Role(Base, RoleMixin):
|
||||||
__tablename__ = "roles"
|
__tablename__ = "roles"
|
||||||
@ -318,3 +337,67 @@ def update_question(question_id, data):
|
|||||||
if column in HiddenAnswer.__table__.columns:
|
if column in HiddenAnswer.__table__.columns:
|
||||||
setattr(question.hidden_answer, column, data[column])
|
setattr(question.hidden_answer, column, data[column])
|
||||||
session.commit()
|
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>
|
<a class="nav-link" href="{{ url_for('hidden_answer_category') }}">Hidden Answer</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
<div class="row justify-content-center tab-content">
|
<div class="row justify-content-center tab-content">
|
||||||
<div class="col-sm col-lg-3">
|
<div class="col-sm col-lg-3">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{{ login_user_form.hidden_tag() }}
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">Email</label>
|
<label for="email">Email</label>
|
||||||
<input id="email" name="email" type="email" class="form-control">
|
<input id="email" name="email" type="email" class="form-control">
|
||||||
@ -20,6 +19,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<button id="submit" name="submit" type="submit" class="btn btn-primary">Submit</button>
|
<button id="submit" name="submit" type="submit" class="btn btn-primary">Submit</button>
|
||||||
</form>
|
</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>
|
</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