Added user authentication for the admin site
Added error pages for 404 and 500 codes Added site icon
This commit is contained in:
parent
197164d691
commit
b4efbc8f3f
@ -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)
|
||||
|
@ -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 %}
|
||||
|
@ -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):
|
||||
|
39
database.py
39
database.py
@ -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
25
main.py
@ -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
BIN
static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 161 KiB |
@ -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
8
templates/error.html
Normal 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 %}
|
22
templates/security/login_user.html
Normal file
22
templates/security/login_user.html
Normal 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 %}
|
22
templates/security/register_user.html
Normal file
22
templates/security/register_user.html
Normal 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 %}
|
Loading…
Reference in New Issue
Block a user