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
|
|
|
from secrets import token_urlsafe
|
2023-04-26 18:22:45 +08:00
|
|
|
from typing import Type
|
2023-01-29 22:28:27 +08:00
|
|
|
|
2023-04-10 23:58:08 +08:00
|
|
|
from click.testing import Result
|
2023-04-11 22:05:57 +08:00
|
|
|
from flask import Flask, Blueprint, render_template, redirect, Response, \
|
2023-06-10 10:33:57 +08:00
|
|
|
url_for
|
2023-04-10 23:58:08 +08:00
|
|
|
from flask.testing import FlaskCliRunner
|
2023-01-29 22:28:27 +08:00
|
|
|
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__)
|
2024-07-10 05:49:29 +08:00
|
|
|
db_uri: str = "sqlite://" if is_testing else "sqlite:///local.sqlite"
|
2023-01-29 22:28:27 +08:00
|
|
|
app.config.from_mapping({
|
2023-02-01 15:54:31 +08:00
|
|
|
"SECRET_KEY": os.environ.get("SECRET_KEY", token_urlsafe(32)),
|
2023-05-17 19:57:06 +08:00
|
|
|
"SESSION_COOKIE_SAMESITE": "Lax",
|
|
|
|
"SESSION_COOKIE_SECURE": True,
|
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="/")
|
|
|
|
|
|
|
|
from . import locale
|
|
|
|
locale.init_app(app)
|
|
|
|
|
|
|
|
from . import auth
|
|
|
|
auth.init_app(app)
|
|
|
|
|
2023-04-12 17:43:05 +08:00
|
|
|
from . import reset
|
|
|
|
reset.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:
|
2023-04-11 22:05:57 +08:00
|
|
|
from accounting.utils.next_uri import append_next
|
|
|
|
return redirect(append_next(url_for("auth.login-form")))
|
2023-04-03 19:36:26 +08:00
|
|
|
|
2023-02-01 15:37:56 +08:00
|
|
|
@property
|
2023-04-26 18:22:45 +08:00
|
|
|
def cls(self) -> Type[auth.User]:
|
2023-02-01 15:37:56 +08:00
|
|
|
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
|
|
|
|
2023-04-10 23:50:16 +08:00
|
|
|
with app.app_context():
|
2023-04-10 23:58:08 +08:00
|
|
|
init_db(app)
|
2023-04-10 23:50:16 +08:00
|
|
|
|
2023-01-29 22:28:27 +08:00
|
|
|
return app
|
|
|
|
|
|
|
|
|
2023-04-10 23:58:08 +08:00
|
|
|
def init_db(app: Flask) -> None:
|
2023-04-10 23:50:16 +08:00
|
|
|
"""Initializes the database.
|
|
|
|
|
2023-04-10 23:58:08 +08:00
|
|
|
:param app: The Flask application.
|
2023-04-10 23:50:16 +08:00
|
|
|
:return: None.
|
|
|
|
"""
|
2023-01-29 22:28:27 +08:00
|
|
|
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()
|
2023-04-10 23:58:08 +08:00
|
|
|
runner: FlaskCliRunner = app.test_cli_runner()
|
|
|
|
result: Result = runner.invoke(args=["accounting-init-db", "-u", "editor"])
|
|
|
|
assert result.exit_code == 0, result.output + str(result.exception)
|
2023-01-29 22:28:27 +08:00
|
|
|
|
|
|
|
|
|
|
|
@bp.get("/", endpoint="home")
|
|
|
|
def get_home() -> str:
|
|
|
|
"""Returns the home page.
|
|
|
|
|
|
|
|
:return: The home page.
|
|
|
|
"""
|
|
|
|
return render_template("home.html")
|