Added user authentication for the admin site

Added error pages for 404 and 500 codes
Added site icon
This commit is contained in:
Matthew Welch 2021-03-22 16:23:52 -07:00
parent 197164d691
commit b4efbc8f3f
10 changed files with 119 additions and 7 deletions

View File

@ -1,5 +1,6 @@
import json
from flask import render_template, Blueprint, request, jsonify
from flask import render_template, Blueprint, request, jsonify, redirect, url_for
from flask_security import roles_required, auth_required
import database
from database import get_all_questions, get_question
@ -8,22 +9,26 @@ Admin = Blueprint("admin", __name__, template_folder="templates")
@Admin.route("/admin/")
@roles_required("admin")
def index():
return render_template("admin/index.html", title="admin")
@Admin.route("/admin/questions/")
@roles_required("admin")
def questions():
return render_template("admin/question_list.html")
@Admin.route("/admin/questions/edit/<int:question_id>")
@roles_required("admin")
def edit_question(question_id):
question: database.AllQuestions = get_question(database.AllQuestions, question_id)
return render_template("admin/edit_question.html", question=question)
@Admin.route("/admin/question_query")
@roles_required("admin")
def query_questions():
offset = request.args.get("offset", type=int)
limit = request.args.get("limit", type=int)

View File

@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<title>Admin</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cosmo/bootstrap.min.css">
{% block head %}{% endblock %}

View File

@ -13,6 +13,7 @@ class Config:
DB_HOST = environ.get("DB_HOST")
DB_PORT = environ.get("DB_PORT")
DB_NAME = environ.get("DB_NAME")
SECURITY_REGISTERABLE = True
class Production(Config):

View File

@ -1,9 +1,10 @@
from flask import current_app, g
from flask_security import UserMixin, RoleMixin
import sqlalchemy
from typing import Union, Optional, Literal, Type, List, Tuple
import random
import os
from sqlalchemy import Column, JSON, String, Integer, create_engine, ForeignKey, func, ARRAY
from sqlalchemy import Column, JSON, String, Integer, create_engine, ForeignKey, func, ARRAY, Boolean, UnicodeText, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import sessionmaker, relationship, scoped_session
@ -16,6 +17,38 @@ engine = create_engine(config.DB_URL)
session_factory = sessionmaker(bind=engine)
Session = scoped_session(session_factory)
Base = declarative_base()
Base.query = Session.query_property()
class User(Base, UserMixin):
__tablename__ = "users"
user_id = Column(Integer, primary_key=True)
email = Column(String, unique=True, nullable=False)
username = Column(String, unique=True)
password = Column(String, nullable=False)
active = Column(Boolean, nullable=False)
last_login_at = Column(DateTime())
current_login_at = Column(DateTime())
last_login_ip = Column(String(100))
current_login_ip = Column(String(100))
login_count = Column(Integer)
fs_uniquifier = Column(String, unique=True, nullable=False)
roles = relationship("Role", secondary="users_roles")
class Role(Base, RoleMixin):
__tablename__ = "roles"
role_id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
description = Column(String)
permissions = Column(UnicodeText)
class UsersRoles(Base):
__tablename__ = "users_roles"
user_role_id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.user_id"))
role_id = Column(Integer, ForeignKey("roles.role_id"))
class AllQuestions(Base):
@ -91,6 +124,9 @@ class MultipleChoice(Base):
return f"<Question Multiple Choice: {self.question_id}>"
Base.metadata.create_all(engine)
def add_multiple_choice_question(question, answer, addresses, difficulty, hint, wrong_answers):
session: sqlalchemy.orm.session.Session = Session()
question_id = session.query(AllQuestions).count()
@ -140,7 +176,6 @@ def get_random_hidden_answer(difficulty: Optional[Literal[1, 2, 3]] = None) -> H
def get_random_multiple_choice(difficulty: Optional[Literal[1, 2, 3]] = None) -> MultipleChoice:
session: sqlalchemy.orm.session.Session = Session()
print(type(session))
if difficulty is not None:
return session.query(MultipleChoice).filter(MultipleChoice.difficulty == difficulty).order_by(func.random()).first()
return session.query(MultipleChoice).order_by(func.random()).first()

25
main.py
View File

@ -1,13 +1,15 @@
import os
from flask import Flask, render_template, request, jsonify
from flask_security import Security
from flask import request, render_template, jsonify, Flask
from flask_security import Security, SQLAlchemySessionUserDatastore, RegisterForm
import database
from admin import admin
app = Flask(__name__)
app.register_blueprint(admin.Admin)
environment_configuration = os.environ['CONFIGURATION_SETUP']
app.config.from_object(environment_configuration)
user_datastore = SQLAlchemySessionUserDatastore(database.Session, database.User, database.Role)
security = Security(app, user_datastore)
app.register_blueprint(admin.Admin)
@app.route("/")
@ -31,7 +33,7 @@ def hidden_answer_category():
@app.route("/category/multiple_choice")
def multiple_choice_category():
easy = database.get_random_multiple_choice(3)
easy = database.get_random_multiple_choice(1)
easy.randomize_answer_list()
medium = database.get_random_multiple_choice(2)
medium.randomize_answer_list()
@ -54,5 +56,20 @@ def check_answer():
return jsonify(database.check_answer(question_id, answer))
@app.errorhandler(404)
def error_404(e):
print(e)
return render_template("error.html", error_msg="The requested page can not be found.", error_code=404), 404
@app.errorhandler(500)
def error_404(e):
print(e)
msg = "There was an error with the server."
if app.config["DEBUG"]:
msg = e
return render_template("error.html", error_msg=msg, error_code=500), 500
if __name__ == "__main__":
app.run()

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

View File

@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<title>Quiz The Word - {{ title }}</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/darkly/bootstrap.min.css">
<link rel="stylesheet" href="/static/style.css">

8
templates/error.html Normal file
View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block body %}
<div class="mt-5 container">
<h1>{{ error_code }}</h1>
<p>{{ error_msg }}</p>
</div>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block body %}
<div class="container mt-5">
<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">
</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 custom-control custom-checkbox">
<input id="remember" name="remember" type="checkbox" class="custom-control-input">
<label class="custom-control-label" for="remember">Remember me</label>
</div>
<button id="submit" name="submit" type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block body %}
<div class="container mt-5">
<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>
{% endblock %}