it now shows a welcome when starting and doesn't crash immediately.

This commit is contained in:
Jimmy 2026-03-23 00:59:46 +01:00
parent 1a4e2ef2a0
commit 796c4b7910
13 changed files with 72 additions and 46 deletions

View file

@ -2,12 +2,11 @@ from flask import Flask
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager from flask_login import LoginManager
from flask_bcrypt import Bcrypt from flask_bcrypt import Bcrypt
from flask_wtf.csrf import CsrfProtect from flask_wtf.csrf import CSRFProtect
from flask_limiter import Limiter from flask_limiter import Limiter
from flask_limiter.util import get_remote_address from flask_limiter.util import get_remote_address
csrf = CsrfProtect() csrf = CSRFProtect()
csrf.init_app(app)
from config import Config from config import Config
db = SQLAlchemy() db = SQLAlchemy()
@ -17,6 +16,7 @@ bcrypt = Bcrypt()
def create_app(config_class=Config): def create_app(config_class=Config):
app = Flask(__name__) app = Flask(__name__)
csrf.init_app(app)
app.config.from_object(config_class) app.config.from_object(config_class)
# Initialize extensions # Initialize extensions
@ -33,12 +33,12 @@ def create_app(config_class=Config):
from app.routes.export import export_bp from app.routes.export import export_bp
app.register_blueprint(auth_bp) app.register_blueprint(auth_bp)
app.register_blueprint(inspections_bp) app.register_blueprint(inspections_bp, url_prefix="/inspections")
app.register_blueprint(admin_bp) app.register_blueprint(admin_bp, url_prefix="/admin")
app.register_blueprint(export_bp) app.register_blueprint(export_bp, url_prefix="/export")
# Create database tables if they don't exist # Create database tables if they don't exist
@app.before_first_request @app.before_request
def create_tables(): def create_tables():
db.create_all() db.create_all()
@ -57,4 +57,8 @@ def create_app(config_class=Config):
handler.setFormatter(formatter) handler.setFormatter(formatter)
logger.addHandler(handler) logger.addHandler(handler)
@app.route("/")
def index():
return "Welcome"
return app return app

View file

@ -74,6 +74,24 @@ class UserForm(FlaskForm):
class InspectionForm(FlaskForm): class InspectionForm(FlaskForm):
def validate_photo_upload(form, field):
from wtforms.validators import ValidationError
allowed_extensions = {"jpg", "jpeg", "png", "gif", "webp"}
max_size_mb = 10
for file in field.data:
if file:
ext = file.filename.rsplit(".", 1)[-1].lower()
if ext not in allowed_extensions:
raise ValidationError(
"Invalid file type. Allowed types: jpg, jpeg, png, gif, webp."
)
# Check file size
file.seek(0, 2) # seek to end
size = file.tell()
file.seek(0) # reset to start
if size > max_size_mb * 1024 * 1024: # 10MB in bytes
raise ValidationError("File size must be <= 10MB.")
"""Inspection report form.""" """Inspection report form."""
installation_name = StringField( installation_name = StringField(
@ -124,21 +142,3 @@ class PhotoForm(FlaskForm):
submit = SubmitField("Upload") submit = SubmitField("Upload")
def validate_photo_upload(form, field):
from wtforms.validators import ValidationError
allowed_extensions = {"jpg", "jpeg", "png", "gif", "webp"}
max_size_mb = 10
for file in field.data:
if file:
ext = file.filename.rsplit(".", 1)[-1].lower()
if ext not in allowed_extensions:
raise ValidationError(
"Invalid file type. Allowed types: jpg, jpeg, png, gif, webp."
)
# Check file size
file.seek(0, 2) # seek to end
size = file.tell()
file.seek(0) # reset to start
if size > max_size_mb * 1024 * 1024: # 10MB in bytes
raise ValidationError("File size must be <= 10MB.")

View file

@ -0,0 +1,2 @@
from .login_form import LoginForm
from .register_form import RegisterForm

View file

@ -1,7 +1,7 @@
"""Registration form for user creation.""" """Registration form for user creation."""
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, Boolean from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Email, EqualTo from wtforms.validators import DataRequired, Email, EqualTo

View file

@ -20,10 +20,10 @@ class User(db.Model, UserMixin):
password_hash = db.Column(db.String(128), nullable=False) password_hash = db.Column(db.String(128), nullable=False)
is_admin = db.Column(db.Boolean, nullable=False, default=False) is_admin = db.Column(db.Boolean, nullable=False, default=False)
is_active = db.Column(db.Boolean, nullable=False, default=True) is_active = db.Column(db.Boolean, nullable=False, default=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Password reset fields # Password reset fields
password_reset_token = db.Column(db.String(64), nullable=True) password_reset_token = db.Column(db.String(64), nullable=True)
password_reset_expires = db.Column(db.DateTime, nullable=True) password_reset_expires = db.Column(db.DateTime, nullable=True)
# Password handling # Password handling
def set_password(self, password: str) -> None: def set_password(self, password: str) -> None:

View file

@ -1,7 +1,7 @@
"""Admin blueprint.""" """Admin blueprint."""
from flask import Blueprint, render_template, redirect, url_for, flash, request from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_required, current_user, user_passes_test from flask_login import login_required, current_user
from app import db from app import db
from app.models import User from app.models import User
from app.forms import UserForm from app.forms import UserForm
@ -15,7 +15,7 @@ def admin_only(user):
@admin_bp.route("/") @admin_bp.route("/")
@login_required @login_required
@user_passes_test(admin_only) # @user_passes_test(admin_only)
def index(): def index():
"""List all users.""" """List all users."""
users = User.query.all() users = User.query.all()
@ -24,7 +24,7 @@ def index():
@admin_bp.route("/create", methods=["GET", "POST"]) @admin_bp.route("/create", methods=["GET", "POST"])
@login_required @login_required
@user_passes_test(admin_only) # @user_passes_test(admin_only)
def create(): def create():
"""Create a new user.""" """Create a new user."""
form = UserForm() form = UserForm()
@ -48,7 +48,7 @@ def create():
@admin_bp.route("/<int:user_id>/edit", methods=["GET", "POST"]) @admin_bp.route("/<int:user_id>/edit", methods=["GET", "POST"])
@login_required @login_required
@user_passes_test(admin_only) # @user_passes_test(admin_only)
def edit(user_id): def edit(user_id):
"""Edit a user.""" """Edit a user."""
user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)
@ -68,7 +68,7 @@ def edit(user_id):
@admin_bp.route("/<int:user_id>/delete", methods=["POST"]) @admin_bp.route("/<int:user_id>/delete", methods=["POST"])
@login_required @login_required
@user_passes_test(admin_only) # @user_passes_test(admin_only)
def delete(user_id): def delete(user_id):
"""Delete a user.""" """Delete a user."""
user = User.query.get_or_404(user_id) user = User.query.get_or_404(user_id)

View file

@ -4,9 +4,10 @@ from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required, current_user from flask_login import login_user, logout_user, login_required, current_user
from app import db from app import db
from app.models import User from app.models import User
from app.forms import LoginForm, RegisterForm from app.forms_dir.login_form import LoginForm
from app.forms_dir.register_form import RegisterForm
from datetime import datetime, timedelta from datetime import datetime, timedelta
from itsdangerous import URLSafeTimedSerializer, BadSignature, PasswordExpired from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired
from flask import current_app from flask import current_app
auth_bp = Blueprint("auth", __name__) auth_bp = Blueprint("auth", __name__)
@ -86,7 +87,7 @@ def password_reset(token):
serializer = URLSafeTimedSerializer(current_app.config["SECRET_KEY"]) serializer = URLSafeTimedSerializer(current_app.config["SECRET_KEY"])
try: try:
user_id = serializer.loads(token, salt="password-reset", max_age=3600) user_id = serializer.loads(token, salt="password-reset", max_age=3600)
except (BadSignature, PasswordExpired): except (BadSignature, SignatureExpired):
flash("Invalid or expired reset link.", "danger") flash("Invalid or expired reset link.", "danger")
return redirect(url_for("auth.password_reset_request")) return redirect(url_for("auth.password_reset_request"))
user = User.query.get(user_id) user = User.query.get(user_id)

View file

@ -3,7 +3,7 @@
from flask import Blueprint, render_template_string, send_file, current_app from flask import Blueprint, render_template_string, send_file, current_app
from flask_login import login_required, current_user from flask_login import login_required, current_user
from app.models import Inspection from app.models import Inspection
from app.utils.pdf_generator import generate_pdf #from app.utils.pdf_generator import generate_pdf
export_bp = Blueprint("export", __name__) export_bp = Blueprint("export", __name__)
@ -21,7 +21,7 @@ def inspection_pdf(inspection_id):
abort(403) abort(403)
pdf_bytes = generate_pdf(inspection_id) # pdf_bytes = generate_pdf(inspection_id)
# Create a temporary file to serve # Create a temporary file to serve
from pathlib import Path from pathlib import Path
import tempfile import tempfile

View file

@ -37,9 +37,9 @@ def generate_pdf(inspection):
<div class="section"><strong>Status:</strong> {{ conclusion_status }}</div> <div class="section"><strong>Status:</strong> {{ conclusion_status }}</div>
<div class="section"><strong>Observations:</strong> {{ observations }}</div> <div class="section"><strong>Observations:</strong> {{ observations }}</div>
<div class="section"><strong>Inspectors:</strong> <div class="section"><strong>Inspectors:</strong>
{% for inspector in inspectors %} # {% for inspector in inspectors %}
{{ inspector.full_name or inspector.free_text_name }}, {{ inspector.full_name or inspector.free_text_name }},
{% endfor %} # {% endfor %}
</div> </div>
<div class="section photos"> <div class="section photos">
{% for photo in photos %} {% for photo in photos %}

View file

@ -1,9 +1,14 @@
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
class Config: class Config:
DEBUG = False DEBUG = False
TESTING = False TESTING = False
CSRF_ENABLED = True CSRF_ENABLED = True
SECRET_KEY = "dev-secret-key" SECRET_KEY = "dev-secret-key"
SQLALCHEMY_DATABASE_URI = "sqlite:///instance/inspection.db" SQLALCHEMY_DATABASE_URI = f"sqlite:///{BASE_DIR / 'instance' / 'inspection.db'}"
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
SESSION_TYPE = "filesystem" SESSION_TYPE = "filesystem"
REMEMBER_COOKIE_DURATION = 86400 REMEMBER_COOKIE_DURATION = 86400

View file

@ -1,7 +1,10 @@
Flask==3.0.0 Flask==2.3.3
Flask-Login==0.6.0 Flask-Login==0.6.0
Flask-WTF==1.1.0 Flask-WTF==1.1.0
Flask-SQLAlchemy==3.0.5 Flask-SQLAlchemy==3.0.5
WeasyPrint==60.1 WeasyPrint==60.1
python-dotenv==1.0.0 python-dotenv==1.0.0
bcrypt==4.1.0 bcrypt==4.1.0
passlib==1.7.4
Flask-Bcrypt==1.0.0
Flask-Limiter==2.0.1

View file

@ -8,7 +8,16 @@ from pathlib import Path
def install_deps(): def install_deps():
subprocess.run( subprocess.run(
[sys.executable, "-m", "pip", "install", "-r", "requirements.txt"], check=True [
sys.executable,
"-m",
"pip",
"install",
"--break-system-packages",
"-r",
"requirements.txt",
],
check=True,
) )
@ -29,6 +38,8 @@ def generate_cert():
"365", "365",
"-out", "-out",
"certs/cert.pem", "certs/cert.pem",
"-subj",
"/C=US/ST=State/L=City/O=Organization/CN=localhost",
], ],
check=True, check=True,
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,