2023-04-04 18:17:44 +08:00
|
|
|
# The Mia! Accounting Demonstration Website.
|
2023-01-29 22:28:27 +08:00
|
|
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/27
|
|
|
|
|
|
|
|
# Copyright (c) 2023 imacat.
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
2023-04-04 18:17:44 +08:00
|
|
|
"""The Mia! Accounting demonstration website.
|
2023-01-29 22:28:27 +08:00
|
|
|
|
|
|
|
"""
|
2023-02-01 15:54:31 +08:00
|
|
|
import os
|
2023-01-29 22:28:27 +08:00
|
|
|
import typing as t
|
|
|
|
from secrets import token_urlsafe
|
|
|
|
|
|
|
|
import click
|
2023-04-03 19:36:26 +08:00
|
|
|
from flask import Flask, Blueprint, render_template, redirect, Response
|
2023-01-29 22:28:27 +08:00
|
|
|
from flask.cli import with_appcontext
|
|
|
|
from flask_babel_js import BabelJS
|
|
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
|
|
from flask_wtf import CSRFProtect
|
2023-02-01 15:37:56 +08:00
|
|
|
from sqlalchemy import Column
|
2023-01-29 22:28:27 +08:00
|
|
|
|
|
|
|
bp: Blueprint = Blueprint("home", __name__)
|
2023-04-03 22:18:58 +08:00
|
|
|
"""The global blueprint."""
|
2023-01-29 22:28:27 +08:00
|
|
|
babel_js: BabelJS = BabelJS()
|
2023-04-03 22:18:58 +08:00
|
|
|
"""The Babel JavaScript instance."""
|
2023-01-29 22:28:27 +08:00
|
|
|
csrf: CSRFProtect = CSRFProtect()
|
2023-04-03 22:18:58 +08:00
|
|
|
"""The CSRF protector."""
|
2023-01-29 22:28:27 +08:00
|
|
|
db: SQLAlchemy = SQLAlchemy()
|
2023-04-03 22:18:58 +08:00
|
|
|
"""The database instance."""
|
2023-01-29 22:28:27 +08:00
|
|
|
|
|
|
|
|
|
|
|
def create_app(is_testing: bool = False) -> Flask:
|
|
|
|
"""Create and configure the application.
|
|
|
|
|
|
|
|
:param is_testing: True if we are running for testing, or False otherwise.
|
|
|
|
:return: The application.
|
|
|
|
"""
|
|
|
|
import accounting
|
|
|
|
|
|
|
|
app: Flask = Flask(__name__)
|
|
|
|
db_uri: str = "sqlite:///" if is_testing else "sqlite:///local.sqlite"
|
|
|
|
app.config.from_mapping({
|
2023-02-01 15:54:31 +08:00
|
|
|
"SECRET_KEY": os.environ.get("SECRET_KEY", token_urlsafe(32)),
|
2023-01-29 22:28:27 +08:00
|
|
|
"SQLALCHEMY_DATABASE_URI": db_uri,
|
|
|
|
"BABEL_DEFAULT_LOCALE": "en",
|
|
|
|
"ALL_LINGUAS": "zh_Hant|正體中文,en|English,zh_Hans|简体中文",
|
|
|
|
})
|
|
|
|
if is_testing:
|
|
|
|
app.config["TESTING"] = True
|
|
|
|
|
|
|
|
babel_js.init_app(app)
|
|
|
|
csrf.init_app(app)
|
|
|
|
db.init_app(app)
|
|
|
|
|
|
|
|
app.register_blueprint(bp, url_prefix="/")
|
|
|
|
app.cli.add_command(init_db_command)
|
|
|
|
|
|
|
|
from . import locale
|
|
|
|
locale.init_app(app)
|
|
|
|
|
|
|
|
from . import auth
|
|
|
|
auth.init_app(app)
|
|
|
|
|
2023-03-14 08:16:32 +08:00
|
|
|
class UserUtilities(accounting.UserUtilityInterface[auth.User]):
|
|
|
|
|
|
|
|
def can_view(self) -> bool:
|
|
|
|
return auth.current_user() is not None \
|
|
|
|
and auth.current_user().username in ["viewer", "editor",
|
2023-03-24 08:32:28 +08:00
|
|
|
"admin"]
|
2023-03-14 08:16:32 +08:00
|
|
|
|
|
|
|
def can_edit(self) -> bool:
|
|
|
|
return auth.current_user() is not None \
|
2023-03-24 08:32:28 +08:00
|
|
|
and auth.current_user().username in ["editor", "admin"]
|
2023-02-01 15:37:56 +08:00
|
|
|
|
2023-03-22 15:34:28 +08:00
|
|
|
def can_admin(self) -> bool:
|
|
|
|
return auth.current_user() is not None \
|
2023-03-24 08:32:28 +08:00
|
|
|
and auth.current_user().username == "admin"
|
2023-03-22 15:34:28 +08:00
|
|
|
|
2023-04-03 19:36:26 +08:00
|
|
|
def unauthorized(self) -> Response:
|
|
|
|
return redirect("/login")
|
|
|
|
|
2023-02-01 15:37:56 +08:00
|
|
|
@property
|
|
|
|
def cls(self) -> t.Type[auth.User]:
|
|
|
|
return auth.User
|
|
|
|
|
|
|
|
@property
|
|
|
|
def pk_column(self) -> Column:
|
|
|
|
return auth.User.id
|
|
|
|
|
|
|
|
@property
|
2023-02-07 14:12:22 +08:00
|
|
|
def current_user(self) -> auth.User | None:
|
2023-02-01 15:37:56 +08:00
|
|
|
return auth.current_user()
|
|
|
|
|
|
|
|
def get_by_username(self, username: str) -> auth.User | None:
|
|
|
|
return auth.User.query\
|
|
|
|
.filter(auth.User.username == username).first()
|
|
|
|
|
|
|
|
def get_pk(self, user: auth.User) -> int:
|
|
|
|
return user.id
|
|
|
|
|
2023-03-14 08:16:32 +08:00
|
|
|
accounting.init_app(app, user_utils=UserUtilities())
|
2023-01-29 22:28:27 +08:00
|
|
|
|
|
|
|
return app
|
|
|
|
|
|
|
|
|
|
|
|
@click.command("init-db")
|
|
|
|
@with_appcontext
|
|
|
|
def init_db_command() -> None:
|
|
|
|
"""Initializes the database."""
|
|
|
|
db.create_all()
|
|
|
|
from .auth import User
|
2023-03-24 08:32:28 +08:00
|
|
|
for username in ["viewer", "editor", "admin", "nobody"]:
|
2023-01-29 22:28:27 +08:00
|
|
|
if User.query.filter(User.username == username).first() is None:
|
|
|
|
db.session.add(User(username=username))
|
|
|
|
db.session.commit()
|
|
|
|
click.echo("Database initialized successfully.")
|
|
|
|
|
|
|
|
|
|
|
|
@bp.get("/", endpoint="home")
|
|
|
|
def get_home() -> str:
|
|
|
|
"""Returns the home page.
|
|
|
|
|
|
|
|
:return: The home page.
|
|
|
|
"""
|
|
|
|
return render_template("home.html")
|