Merge pull request #6 from StefBuwalda/development

VERSION 1.0
This commit is contained in:
DaanoGames
2025-04-16 15:52:48 +02:00
committed by GitHub
31 changed files with 847 additions and 1 deletions

8
.gitignore vendored
View File

@@ -1 +1,9 @@
# venv's
venv/
# DB stuff
migrations/
instance/
# Byte-compiled / optimized / DLL files
__pycache__/

View File

@@ -1 +1,24 @@
# WebTech
# How to install
## Setting up your virtual environment
### Creating virtual environment
python -m venv venv
### Activating environment for package installation (windows)
.\venv\Scripts\activate.bat
### Installing required packages
pip install -r requirements.txt
## Setting up the database
### Initialize database
flask --app app.py db init
### Migrate database
flask --app app.py db migrate
### upgrade database
flask --app app.py db upgrade
# Development commands
#### Updating requirements.txt
pip freeze > requirements.txt

Binary file not shown.

15
app.py Normal file
View File

@@ -0,0 +1,15 @@
from application import app
from flask import redirect, url_for
from flask_login import login_required # type: ignore
# home route
@app.route("/")
@login_required
def index():
return redirect(url_for("dash.index"))
# App deployment
if __name__ == "__main__":
app.run(debug=True, port=5000)

43
application/__init__.py Normal file
View File

@@ -0,0 +1,43 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager # type: ignore
import os
# App Config
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///services.sqlite"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # Wat is dit?
app.config["SECRET_KEY"] = "bvjchsygvduycgsyugc" # Andere secret key
app.config["UPLOAD_FOLDER"] = r"application\static\icons"
# Ensure the upload folder exists
os.makedirs(app.config["UPLOAD_FOLDER"], exist_ok=True) # type: ignore
# Object Relational Management
db = SQLAlchemy()
db.init_app(app)
migrate = Migrate(app, db)
# bp import
from application.auth.views import auth_blueprint
from application.dash.views import dash_blueprint
# Login manager
from application.auth.models import User
login_manager = LoginManager()
login_manager.init_app(app) # type: ignore
login_manager.login_view = "auth.login" # type: ignore
@login_manager.user_loader # type: ignore
def load_user(user_id): # type: ignore
return User.query.get(int(user_id)) # type: ignore
# Blueprint magic
app.register_blueprint(dash_blueprint, url_prefix="/dash")
app.register_blueprint(auth_blueprint, url_prefix="/auth")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

29
application/auth/forms.py Normal file
View File

@@ -0,0 +1,29 @@
from flask_wtf import FlaskForm # type: ignore
from wtforms import StringField, SubmitField, PasswordField, BooleanField
from wtforms.validators import DataRequired
class defaultForm(FlaskForm):
username = StringField("Username", validators=[DataRequired()])
password = PasswordField("Password", validators=[DataRequired()])
submit = SubmitField("Submit")
class LoginForm(defaultForm):
pass
class RegisterForm(defaultForm):
confirm_password = PasswordField(
"Confirm Password", validators=[DataRequired()]
)
is_admin = BooleanField("Admin")
class UpdateForm(defaultForm):
confirm_password = PasswordField(
"Confirm Password", validators=[DataRequired()]
)
current_password = PasswordField(
"Current Password", validators=[DataRequired()]
)

View File

@@ -0,0 +1,16 @@
from application import db
from flask_login import UserMixin # type: ignore
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(150), unique=True, nullable=False)
password = db.Column(db.String(150), nullable=False)
is_admin = db.Column(db.Boolean, default=False)
services = db.relationship("Service", backref="user", lazy="joined")
def __init__(self, username: str, password: str, is_admin: bool = False):
self.username = username
self.password = password
self.is_admin = is_admin

View File

@@ -0,0 +1,51 @@
{% extends 'base_template.html' %}
{% block title %}
Register
{% endblock %}
{% block content %}
<form class="form bg-body-tertiary" method="POST">
{{ form.hidden_tag() }}
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
</symbol>
<symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
</symbol>
</svg>
{% if feedback %}
{% if feedback=="User succesfully added" %}
<div class="alert alert-success d-flex align-items-center" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Success:"><use xlink:href="#check-circle-fill"/></svg>
<div>
{{feedback}}
</div>
</div>
{% else %}
<div class="alert alert-danger d-flex align-items-center" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#exclamation-triangle-fill"/></svg>
<div>
{{feedback}}
</div>
</div>
{% endif %}
{% endif %}
<div>
{{ form.username.label }} <br> {{ form.username() }}
</div>
<div class="padding">
{{ form.password.label }} <br> {{ form.password() }}
</div>
<div class="padding">
{{ form.confirm_password.label }} <br> {{ form.confirm_password() }}
</div>
<div class="padding">
{{ form.is_admin() }} {{ form.is_admin.label }}
</div>
<div class="padding">
{{ form.submit() }}
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,33 @@
{% extends 'base_template.html' %}
{% block title %}
Login
{% endblock %}
{% block content %}
<form class="form bg-body-tertiary" method="POST">
{{ form.hidden_tag() }}
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
</symbol>
</svg>
{% if feedback %}
<div class="alert alert-danger d-flex align-items-center" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#exclamation-triangle-fill"/></svg>
<div>
{{feedback}}
</div>
</div>
{% endif %}
<div>
{{ form.username.label }} <br> {{ form.username() }}
</div>
<div class="padding">
{{ form.password.label }} <br> {{ form.password() }}
</div>
<div class="padding">
{{ form.submit() }}
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends 'base_template.html' %}
{% block title %}
Update
{% endblock %}
{% block content %}
<form class="form bg-body-tertiary" method="POST">
{{ form.hidden_tag() }}
{% if feedback %}
<p class="feedback">{{feedback}}</p>
{% endif %}
<div>
Current password <br> {{ form.current_password() }}
</div>
<div class="padding">
New password <br> {{ form.password() }}
</div>
<div class="padding">
Confirm new password <br> {{ form.confirm_password() }}
</div>
<div class="padding">
{{ form.submit() }}
</div>
</form>
{% endblock %}

117
application/auth/views.py Normal file
View File

@@ -0,0 +1,117 @@
from flask import Blueprint, render_template, redirect, url_for
from application import db
from application.auth.models import User
from application.auth.forms import LoginForm
from flask_login import ( # type: ignore
login_required, # type: ignore
login_user, # type: ignore
logout_user,
current_user,
)
from werkzeug.security import check_password_hash, generate_password_hash
from application.decorators import admin_required
from application.auth.forms import RegisterForm, UpdateForm
auth_blueprint = Blueprint("auth", __name__, template_folder="templates")
# Routes
@auth_blueprint.route("/register", methods=["GET", "POST"])
@admin_required
def register():
register_form = RegisterForm()
if register_form.validate_on_submit(): # type: ignore
username = register_form.username.data
password = register_form.password.data
confirm_password = register_form.confirm_password.data
is_admin = register_form.is_admin.data
if confirm_password != password:
return render_template(
"admin.html",
form=register_form,
feedback="Passwords don't match, please try again",
active_page="register",
)
if User.query.filter_by(username=username).first():
return render_template(
"admin.html",
form=register_form,
feedback="Username is already taken",
active_page="register",
)
new_user = User(
username=username, # type: ignore
password=generate_password_hash(password), # type: ignore
is_admin=is_admin,
)
db.session.add(new_user)
db.session.commit()
return render_template(
"admin.html",
form=RegisterForm(formdata=None),
feedback="User succesfully added",
active_page="register",
)
return render_template(
"admin.html", form=register_form, active_page="register"
)
@auth_blueprint.route("/update_user", methods=["GET", "POST"])
@login_required
def update():
form = UpdateForm(username=current_user.username)
if form.validate_on_submit(): # type: ignore
if not check_password_hash(
current_user.password, form.current_password.data # type: ignore
):
return render_template(
"update_user.html",
form=form,
feedback="Current password incorrect",
active_page="update",
)
if form.password.data != form.confirm_password.data:
return render_template(
"update_user.html",
form=form,
feedback="New password mismatched",
active_page="update",
)
current_user.password = generate_password_hash(
form.password.data # type: ignore
)
db.session.commit()
logout_user()
return redirect(url_for("auth.login"))
return render_template("update_user.html", form=form, active_page="update")
@auth_blueprint.route("/login", methods=["GET", "POST"])
def login():
login_form = LoginForm()
feedback = None
if login_form.validate_on_submit(): # type: ignore
username = login_form.username.data
password = login_form.password.data
user = User.query.filter_by(username=username).first() # type: ignore
if user and check_password_hash(
user.password, password # type: ignore
):
login_user(user) # type: ignore
return redirect("/")
else:
feedback = "Username or password is incorrect"
return render_template("login.html", form=login_form, feedback=feedback)
@auth_blueprint.route("/logout")
@login_required
def logout():
logout_user()
return redirect(url_for("index"))

16
application/dash/forms.py Normal file
View File

@@ -0,0 +1,16 @@
from flask_wtf import FlaskForm # type: ignore
from wtforms import StringField, SubmitField, URLField
from wtforms.validators import DataRequired
from flask_wtf.file import FileField, FileAllowed # type: ignore
class ServiceForm(FlaskForm):
name = StringField("Service name:", validators=[DataRequired()])
url = URLField("Service URL:", validators=[DataRequired()])
image = FileField(
"Icon:",
validators=[
FileAllowed(["jpg", "jpeg", "png"], "Unsupported file format"),
],
)
submit = SubmitField("Submit")

View File

@@ -0,0 +1,18 @@
from application import db
class Service(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
url = db.Column(db.String, nullable=False)
icon = db.Column(db.String, default="google.png")
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
def __init__(
self, name: str, url: str, user_id: int, icon: str = "google.png"
):
self.name = name
self.url = url
self.user_id = user_id
self.icon = icon

View File

@@ -0,0 +1,57 @@
{% extends 'base_template.html' %}
{% block title %}
Add service
{% endblock %}
{% block content %}
<form class="form bg-body-tertiary" method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
<path
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" />
</symbol>
<symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
<path
d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
</symbol>
</svg>
{% if feedback %}
{% if feedback=="Service succesfully added" %}
<div class="alert alert-success d-flex align-items-center" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Success:">
<use xlink:href="#check-circle-fill" />
</svg>
<div>
{{feedback}}
</div>
</div>
{% else %}
<div class="alert alert-danger d-flex align-items-center" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:">
<use xlink:href="#exclamation-triangle-fill" />
</svg>
<div>
{{feedback}}
</div>
</div>
{% endif %}
{% endif %}
<div>
{{ form.name.label }} <br> {{ form.name() }}
</div>
<div class="padding">
{{ form.url.label }} <br> {{ form.url() }}
</div>
<div class="padding">
Upload an icon:
</div>
<div class="padding">
{{ form.image() }}
</div>
<div class="padding">
{{ form.submit() }}
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,48 @@
{% extends "base_template.html" %}
{%block title%}Dashboard{%endblock%}
{%block content%}
<div class="grid-container">
{% for service in services%}
<div class="bg-light container-xxl">
<div class="row row-cols-3">
<div class="col-sm-2" onclick="location.href='{{service.url}}';" style="cursor: pointer;">
<img class="fit-picture" src="{{ url_for('static', filename='icons/'+service['icon'])}}">
</div>
<div class="col-sm-10">
<div class="row">
<div class="col-sm-10" onclick="location.href='{{service.url}}';" style="cursor: pointer;">
{{service["name"]}}
</div>
<div class="col-sm-2 dots dropdown">
<button class="btn btn-light py-0" type="button" id="threeDotDropdown" data-bs-toggle="dropdown"
aria-expanded="false">
&#x22EE;
</button>
<ul class="dropdown-menu" aria-labelledby="threeDotDropdown">
<li>
<a class="dropdown-item" href="{{ url_for('dash.edit_service', service_id=service.id) }}">Edit</a>
</li>
<li>
<hr class="dropdown-divider">
</li>
<li>
<form action="{{ url_for('dash.delete_item', service_id=service.id) }}" method="POST" style="display:inline;">
<button style="color: red;" type="submit" class="dropdown-item">Delete</button>
</form>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col" onclick="location.href='{{service.url}}';" style="cursor: pointer;">
{{service["url"]}}
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{%endblock%}

View File

@@ -0,0 +1,26 @@
{% extends 'base_template.html' %}
{% block title %}
Edit service
{% endblock %}
{% block content %}
<form class="form bg-body-tertiary" method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div>
{{ form.name.label }} <br> {{ form.name() }}
</div>
<div>
{{ form.url.label }} <br> {{ form.url() }}
</div>
<div class="padding">
Upload an icon:
</div>
<div class="padding">
{{ form.image() }}
</div>
<div class="padding">
{{ form.submit(value="Edit") }}
</div>
</form>
{% endblock %}

109
application/dash/views.py Normal file
View File

@@ -0,0 +1,109 @@
from application import db
from flask import Blueprint, render_template, redirect, url_for
from application.dash.forms import ServiceForm
from flask_login import login_required, current_user # type: ignore
from application.dash.models import Service
from application.utils import saveImage
dash_blueprint = Blueprint("dash", __name__, template_folder="templates")
@dash_blueprint.route("/", methods=["GET", "POST"])
@login_required
def index():
services = current_user.services # type: ignore
return render_template(
"dashboard.html", services=services, active_page="dashboard"
)
@dash_blueprint.route("/delete_item/<int:service_id>", methods=["POST"])
@login_required
def delete_item(service_id: int):
service = Service.query.get_or_404(service_id)
# Check ownership
if service.user_id != current_user.id:
return redirect(url_for("dash.index"))
db.session.delete(service)
db.session.commit()
return redirect(url_for("dash.index"))
@dash_blueprint.route("/service", methods=["GET", "POST"])
@login_required
def service():
service_form = ServiceForm()
if service_form.validate_on_submit(): # type: ignore
image = service_form.image.data
name = service_form.name.data
url = service_form.url.data
filename2 = "google.png"
if image:
filename2 = saveImage(image)
new_service = Service(
name=name, # type: ignore
url=url, # type: ignore
user_id=current_user.id,
icon=filename2,
) # type: ignore
db.session.add(new_service)
db.session.commit()
return render_template(
"add_service.html",
form=ServiceForm(formdata=None),
feedback="Service succesfully added",
active_page="service",
)
return render_template(
"add_service.html", form=service_form, active_page="service"
)
@dash_blueprint.route(
"/edit_service/<int:service_id>", methods=["GET", "POST"]
)
@login_required
def edit_service(service_id: int):
service = Service.query.get_or_404(service_id)
if current_user.id != service.user_id:
redirect(url_for("dash.index"))
# Correcte gebruiker
form = ServiceForm()
if form.validate_on_submit(): # type: ignore
commit = False
if service.name != form.name.data:
service.name = form.name.data
commit = True
if service.url != form.url.data:
service.url = form.url.data
commit = True
if form.image.data:
service.icon = saveImage(form.image.data)
commit = True
if commit:
db.session.commit()
return redirect(url_for("dash.index"))
# Fill in correct data
form = ServiceForm(name=service.name, url=service.url)
return render_template("edit_service.html", form=form)
"""
def saveImage(image: ...):
filename = secure_filename(image.filename)
save_path = os.path.join(
app.config["UPLOAD_FOLDER"], # type: ignore
str(current_user.id),
filename,
)
os.makedirs(os.path.dirname(save_path), exist_ok=True)
image.save(save_path) # type: ignore
filename2 = str(current_user.id) + "/" + filename
return filename2
"""

16
application/decorators.py Normal file
View File

@@ -0,0 +1,16 @@
from typing import Callable, Any
from flask_login import current_user # type: ignore
from functools import wraps
from flask import redirect, url_for
def admin_required(f: Callable[..., Any]) -> Callable[..., Any]:
@wraps(f)
def decorated_function(*args: ..., **kwargs: ...):
if not current_user.is_authenticated:
return redirect(url_for("login"))
if not current_user.is_admin:
return redirect(url_for("index"))
return f(*args, **kwargs)
return decorated_function

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,54 @@
/* Base template */
body {
background-color: lightslategray;
}
/* Dashboard page */
.grid-container {
display: grid;
grid-template-columns: auto auto auto;
gap: 20px;
background-color: lightslategray;
padding-top: 20px;
margin-left: 10%;
margin-right: 10%;
}
.grid-container > div {
height: fit-content;
width: 400px;
padding-top: 5px;
padding-bottom: 5px;
border: 2px solid black;
border-radius: 10px;
}
.fit-picture {
width: 45px;
padding-top: 5px;
padding-bottom: 5px;
}
/* Login page */
.form {
display: block;
margin-left: auto;
margin-right: auto;
height: fit-content;
width: fit-content;
border: 2px solid black;
border-radius: 10px;
text-align: center;
margin-top: 10px;
padding: 20px;
font-size: 20px;
}
.padding {
padding-top: 10px;
}
.feedback {
font-size: 16px;
color: red;
}

View File

@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link href="{{url_for('static', filename='style.css')}}" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<nav class="navbar sticky-top navbar-expand-lg bg-body-tertiary" data-bs-theme="dark">
<div class="container-fluid">
<a class="navbar-brand" href="{{url_for('index')}}">ServiceHUB</a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link {% if active_page == 'dashboard' %}active{% endif %}" aria-current=" page"
href="{{url_for('dash.index')}}">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_page == 'service' %}active{% endif %}"
href="{{url_for('dash.service')}}">Add service</a>
</li>
{% endif %}
{% if current_user.is_admin %}
<li class="nav-item">
<a class="nav-link {% if active_page == 'register' %}active{% endif %}"
href="{{url_for('auth.register')}}">Add user</a>
</li>
{% endif %}
</ul>
{% if current_user.is_authenticated %}
<div class="dropstart">
<button class="btn btn-outline-info" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
Profile
</button>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-lg-start" aria-labelledby="dropdownMenuButton1">
<li><a class="dropdown-item">Username: {{current_user.username}}</a></li>
<li><a class="dropdown-item {% if active_page == 'update' %}active{% endif %}" href="{{url_for('auth.update')}}">Change password</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" style="color: tomato;" data-bs-theme="dark" href="{{url_for('auth.logout')}}">Logout</a></li>
</ul>
</div>
{% endif %}
</div>
</div>
</nav>
{% block content %}
{% endblock %}
</body>
</html>

17
application/utils.py Normal file
View File

@@ -0,0 +1,17 @@
from werkzeug.utils import secure_filename
import os
from application import app
from flask_login import current_user # type: ignore
def saveImage(image: ...):
filename = secure_filename(image.filename)
save_path = os.path.join(
app.config["UPLOAD_FOLDER"], # type: ignore
str(current_user.id),
filename,
)
os.makedirs(os.path.dirname(save_path), exist_ok=True)
image.save(save_path) # type: ignore
filename2 = str(current_user.id) + "/" + filename
return filename2

View File

@@ -1,8 +1,19 @@
alembic==1.15.2
blinker==1.9.0
click==8.1.8
colorama==0.4.6
Flask==3.1.0
Flask-Login==0.6.3
Flask-Migrate==4.1.0
Flask-SQLAlchemy==3.1.1
Flask-WTF==1.2.2
greenlet==3.1.1
itsdangerous==2.2.0
Jinja2==3.1.6
Mako==1.3.9
MarkupSafe==3.0.2
pillow==11.1.0
SQLAlchemy==2.0.40
typing_extensions==4.13.0
Werkzeug==3.1.3
WTForms==3.2.1

54
seed.py Normal file
View File

@@ -0,0 +1,54 @@
from application import db, app
from application.dash.models import Service
from application.auth.models import User
from werkzeug.security import generate_password_hash
"""
new_strikers = [
se(name="Erik", strike="y", age=44),
Striker(name="Henk", strike="n", age=88),
]
"""
new_users = [
User(
username="admin",
password=generate_password_hash("admin"),
is_admin=True,
),
User(
username="Daan",
password=generate_password_hash("pass"),
is_admin=True,
),
User(
username="stef",
password=generate_password_hash("stef123"),
is_admin=False,
),
]
new_services = [
Service(name="test123", url="http://google.com", user_id=1),
# Daan services
Service(name="Google", url="https://google.com", user_id=2),
Service(name="Netflix", url="https://www.netflix.com", user_id=2),
# Stef services
Service(name="Plex", url="https://plex.local", user_id=3),
Service(name="TrueNAS", url="https://truenas.local", user_id=3),
Service(name="Transmission", url="https://transmission.local", user_id=3),
Service(name="Tautulli", url="https://tautulli.local", user_id=3),
Service(name="Overseerr", url="https://overseerr.local", user_id=3),
Service(name="Plex", url="https://plex.local", user_id=3),
]
with app.app_context():
# Remove all existing
Service.query.delete()
User.query.delete()
db.session.commit()
# Then add new
db.session.add_all(new_services)
db.session.add_all(new_users)
db.session.commit()

BIN
test/123123123.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB