Switch to Flask-SQLAlchemy

Add FLask-Migrate for next step in the Migration 😉
This commit is contained in:
Michał 2023-04-09 19:12:35 +00:00
parent 7d0078ea9a
commit 7c553e99b8
12 changed files with 368 additions and 403 deletions

View file

@ -2,41 +2,31 @@
Onlylegs Gallery Onlylegs Gallery
This is the main app file, it loads all the other files and sets up the app This is the main app file, it loads all the other files and sets up the app
""" """
# Import system modules # Import system modules
import os import os
import logging import logging
# Flask # Flask
from flask_compress import Compress from flask_assets import Bundle
from flask_caching import Cache
from flask_assets import Environment, Bundle
from flask_login import LoginManager
from flask import Flask, render_template, abort from flask import Flask, render_template, abort
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
from gallery.extensions import db, migrate, login_manager, assets, compress, cache
from gallery.models import Users
from gallery.views import index, image, group, settings, profile
from gallery import api
from gallery import auth
# Configuration # Configuration
import platformdirs import platformdirs
from dotenv import load_dotenv from dotenv import load_dotenv
from yaml import safe_load from yaml import safe_load
# Import database
from sqlalchemy.orm import sessionmaker
from gallery import db
USER_DIR = platformdirs.user_config_dir("onlylegs") USER_DIR = platformdirs.user_config_dir("onlylegs")
db_session = sessionmaker(bind=db.engine) def create_app(): # pylint: disable=R0914
db_session = db_session()
login_manager = LoginManager()
assets = Environment()
cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 300})
compress = Compress()
def create_app(test_config=None): # pylint: disable=R0914
""" """
Create and configure the main app Create and configure the main app
""" """
@ -54,7 +44,7 @@ def create_app(test_config=None): # pylint: disable=R0914
# App configuration # App configuration
app.config.from_mapping( app.config.from_mapping(
SECRET_KEY=os.environ.get("FLASK_SECRET"), SECRET_KEY=os.environ.get("FLASK_SECRET"),
DATABASE=os.path.join(app.instance_path, "gallery.sqlite3"), SQLALCHEMY_DATABASE_URI=("sqlite:///gallery.sqlite3"),
UPLOAD_FOLDER=os.path.join(USER_DIR, "uploads"), UPLOAD_FOLDER=os.path.join(USER_DIR, "uploads"),
ALLOWED_EXTENSIONS=conf["upload"]["allowed-extensions"], ALLOWED_EXTENSIONS=conf["upload"]["allowed-extensions"],
MAX_CONTENT_LENGTH=1024 * 1024 * conf["upload"]["max-size"], MAX_CONTENT_LENGTH=1024 * 1024 * conf["upload"]["max-size"],
@ -63,10 +53,8 @@ def create_app(test_config=None): # pylint: disable=R0914
WEBSITE_CONF=conf["website"], WEBSITE_CONF=conf["website"],
) )
if test_config is None: db.init_app(app)
app.config.from_pyfile("config.py", silent=True) migrate.init_app(app, db)
else:
app.config.from_mapping(test_config)
login_manager.init_app(app) login_manager.init_app(app)
login_manager.login_view = "gallery.index" login_manager.login_view = "gallery.index"
@ -74,7 +62,7 @@ def create_app(test_config=None): # pylint: disable=R0914
@login_manager.user_loader @login_manager.user_loader
def load_user(user_id): def load_user(user_id):
return db_session.query(db.Users).filter_by(alt_id=user_id).first() return Users.query.filter_by(alt_id=user_id).first()
@login_manager.unauthorized_handler @login_manager.unauthorized_handler
def unauthorized(): def unauthorized():
@ -82,23 +70,6 @@ def create_app(test_config=None): # pylint: disable=R0914
msg = "You are not authorized to view this page!!!!" msg = "You are not authorized to view this page!!!!"
return render_template("error.html", error=error, msg=msg), error return render_template("error.html", error=error, msg=msg), error
scripts = Bundle(
"js/*.js",
filters="jsmin",
output="gen/js.js",
depends="js/*.js"
)
styles = Bundle(
"sass/*.sass",
filters="libsass, cssmin",
output="gen/styles.css",
depends="sass/**/*.sass",
)
assets.register("scripts", scripts)
assets.register("styles", styles)
# Error handlers, if the error is not a HTTP error, return 500 # Error handlers, if the error is not a HTTP error, return 500
@app.errorhandler(Exception) @app.errorhandler(Exception)
def error_page(err): # noqa def error_page(err): # noqa
@ -109,30 +80,30 @@ def create_app(test_config=None): # pylint: disable=R0914
err.code, err.code,
) )
# Load login, registration and logout manager scripts = Bundle("js/*.js", filters="jsmin", output="gen/js.js", depends="js/*.js")
from gallery import auth
styles = Bundle(
"sass/*.sass",
filters="libsass, cssmin",
output="gen/styles.css",
depends="sass/**/*.sass",
)
assets.register("scripts", scripts)
assets.register("styles", styles)
# Load all the blueprints
app.register_blueprint(auth.blueprint) app.register_blueprint(auth.blueprint)
# Load the API
from gallery import api
app.register_blueprint(api.blueprint) app.register_blueprint(api.blueprint)
# Load the different views
from gallery.views import index, image, group, settings, profile
app.register_blueprint(index.blueprint) app.register_blueprint(index.blueprint)
app.register_blueprint(image.blueprint) app.register_blueprint(image.blueprint)
app.register_blueprint(group.blueprint) app.register_blueprint(group.blueprint)
app.register_blueprint(profile.blueprint) app.register_blueprint(profile.blueprint)
app.register_blueprint(settings.blueprint) app.register_blueprint(settings.blueprint)
# Log to file that the app has started
logging.info("Gallery started successfully!")
# Initialize extensions and return app
assets.init_app(app) assets.init_app(app)
cache.init_app(app) cache.init_app(app)
compress.init_app(app) compress.init_app(app)
logging.info("Gallery started successfully!")
return app return app

View file

@ -14,16 +14,14 @@ from flask_login import login_required, current_user
from colorthief import ColorThief from colorthief import ColorThief
from sqlalchemy.orm import sessionmaker from gallery.extensions import db
from gallery.models import Posts, Groups, GroupJunction
from gallery import db
from gallery.utils import metadata as mt from gallery.utils import metadata as mt
from gallery.utils.generate_image import generate_thumbnail from gallery.utils.generate_image import generate_thumbnail
blueprint = Blueprint("api", __name__, url_prefix="/api") blueprint = Blueprint("api", __name__, url_prefix="/api")
db_session = sessionmaker(bind=db.engine)
db_session = db_session()
@blueprint.route("/file/<file_name>", methods=["GET"]) @blueprint.route("/file/<file_name>", methods=["GET"])
@ -87,7 +85,7 @@ def upload():
img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette
# Save to database # Save to database
query = db.Posts( query = Posts(
author_id=current_user.id, author_id=current_user.id,
filename=img_name + "." + img_ext, filename=img_name + "." + img_ext,
mimetype=img_ext, mimetype=img_ext,
@ -97,8 +95,8 @@ def upload():
alt=form["alt"], alt=form["alt"],
) )
db_session.add(query) db.session.add(query)
db_session.commit() db.session.commit()
return "Gwa Gwa" # Return something so the browser doesn't show an error return "Gwa Gwa" # Return something so the browser doesn't show an error
@ -109,7 +107,7 @@ def delete_image(image_id):
""" """
Deletes an image from the server and database Deletes an image from the server and database
""" """
img = db_session.query(db.Posts).filter_by(id=image_id).first() img = Posts.query.filter_by(id=image_id).first()
# Check if image exists and if user is allowed to delete it (author) # Check if image exists and if user is allowed to delete it (author)
if img is None: if img is None:
@ -131,16 +129,15 @@ def delete_image(image_id):
for cache_file in pathlib.Path(cache_path).glob(cache_name + "*"): for cache_file in pathlib.Path(cache_path).glob(cache_name + "*"):
os.remove(cache_file) os.remove(cache_file)
# Delete from database post = Posts.query.filter_by(id=image_id).first()
db_session.query(db.Posts).filter_by(id=image_id).delete() db.session.delete(post)
# Remove all entries in junction table groups = GroupJunction.query.filter_by(post_id=image_id).all()
groups = db_session.query(db.GroupJunction).filter_by(post_id=image_id).all()
for group in groups: for group in groups:
db_session.delete(group) db.session.delete(group)
# Commit all changes # Commit all changes
db_session.commit() db.session.commit()
logging.info("Removed image (%s) %s", image_id, img.filename) logging.info("Removed image (%s) %s", image_id, img.filename)
flash(["Image was all in Le Head!", "1"]) flash(["Image was all in Le Head!", "1"])
@ -153,14 +150,14 @@ def create_group():
""" """
Creates a group Creates a group
""" """
new_group = db.Groups( new_group = Groups(
name=request.form["name"], name=request.form["name"],
description=request.form["description"], description=request.form["description"],
author_id=current_user.id, author_id=current_user.id,
) )
db_session.add(new_group) db.session.add(new_group)
db_session.commit() db.session.commit()
return ":3" return ":3"
@ -175,7 +172,7 @@ def modify_group():
image_id = request.form["image"] image_id = request.form["image"]
action = request.form["action"] action = request.form["action"]
group = db_session.query(db.Groups).filter_by(id=group_id).first() group = Groups.query.filter_by(id=group_id).first()
if group is None: if group is None:
abort(404) abort(404)
@ -183,20 +180,12 @@ def modify_group():
abort(403) abort(403)
if action == "add": if action == "add":
if not ( if not GroupJunction.query.filter_by(group_id=group_id, post_id=image_id).first():
db_session.query(db.GroupJunction) db.session.add(GroupJunction(group_id=group_id, post_id=image_id))
.filter_by(group_id=group_id, post_id=image_id)
.first()
):
db_session.add(db.GroupJunction(group_id=group_id, post_id=image_id))
elif request.form["action"] == "remove": elif request.form["action"] == "remove":
( db.session.delete(GroupJunction.query.filter_by(group_id=group_id, post_id=image_id).first())
db_session.query(db.GroupJunction)
.filter_by(group_id=group_id, post_id=image_id)
.delete()
)
db_session.commit() db.session.commit()
return ":3" return ":3"
@ -208,16 +197,21 @@ def delete_group():
""" """
group_id = request.form["group"] group_id = request.form["group"]
group = db_session.query(db.Groups).filter_by(id=group_id).first() group = Groups.query.filter_by(id=group_id).first()
if group is None: if group is None:
abort(404) abort(404)
elif group.author_id != current_user.id: elif group.author_id != current_user.id:
abort(403) abort(403)
db_session.query(db.Groups).filter_by(id=group_id).delete() group_del = Groups.query.filter_by(id=group_id).first()
db_session.query(db.GroupJunction).filter_by(group_id=group_id).delete() db.session.delete(group_del)
db_session.commit()
junction_del = GroupJunction.query.filter_by(group_id=group_id).all()
for junction in junction_del:
db.session.delete(junction)
db.session.commit()
flash(["Group yeeted!", "1"]) flash(["Group yeeted!", "1"])
return ":3" return ":3"

View file

@ -10,13 +10,11 @@ from werkzeug.security import check_password_hash, generate_password_hash
from flask_login import login_user, logout_user, login_required from flask_login import login_user, logout_user, login_required
from sqlalchemy.orm import sessionmaker from gallery.extensions import db
from gallery import db from gallery.models import Users
blueprint = Blueprint("auth", __name__, url_prefix="/auth") blueprint = Blueprint("auth", __name__, url_prefix="/auth")
db_session = sessionmaker(bind=db.engine)
db_session = db_session()
@blueprint.route("/login", methods=["POST"]) @blueprint.route("/login", methods=["POST"])
@ -30,7 +28,7 @@ def login():
password = request.form["password"].strip() password = request.form["password"].strip()
remember = bool(request.form["remember-me"]) remember = bool(request.form["remember-me"])
user = db_session.query(db.Users).filter_by(username=username).first() user = Users.query.filter_by(username=username).first()
if not user or not check_password_hash(user.password, password): if not user or not check_password_hash(user.password, password):
logging.error("Login attempt from %s", request.remote_addr) logging.error("Login attempt from %s", request.remote_addr)
@ -79,7 +77,7 @@ def register():
elif password_repeat != password: elif password_repeat != password:
error.append("Passwords do not match!") error.append("Passwords do not match!")
user_exists = db_session.query(db.Users).filter_by(username=username).first() user_exists = Users.query.filter_by(username=username).first()
if user_exists: if user_exists:
error.append("User already exists!") error.append("User already exists!")
@ -93,8 +91,8 @@ def register():
email=email, email=email,
password=generate_password_hash(password, method="sha256"), password=generate_password_hash(password, method="sha256"),
) )
db_session.add(register_user) db.session.add(register_user)
db_session.commit() db.session.commit()
logging.info("User %s registered", username) logging.info("User %s registered", username)
return "ok", 200 return "ok", 200

View file

@ -1,153 +0,0 @@
"""
OnlyLegs - Database models and ions for SQLAlchemy
"""
from uuid import uuid4
import os
import platformdirs
from sqlalchemy import (
create_engine,
Column,
Integer,
String,
DateTime,
ForeignKey,
PickleType,
func,
)
from sqlalchemy.orm import declarative_base, relationship
from flask_login import UserMixin
USER_DIR = platformdirs.user_config_dir("onlylegs")
DB_PATH = os.path.join(USER_DIR, "instance", "gallery.sqlite3")
# In the future, I want to add support for other databases
engine = create_engine(f"sqlite:///{DB_PATH}", echo=False)
base = declarative_base()
class Users(base, UserMixin): # pylint: disable=too-few-public-methods, C0103
"""
User table
Joins with post, groups, session and log
"""
__tablename__ = "users"
# Gallery used information
id = Column(Integer, primary_key=True)
alt_id = Column(String, unique=True, nullable=False, default=str(uuid4()))
profile_picture = Column(String, nullable=True, default=None)
username = Column(String, unique=True, nullable=False)
email = Column(String, unique=True, nullable=False)
password = Column(String, nullable=False)
joined_at = Column(
DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102
)
posts = relationship("Posts", backref="users")
groups = relationship("Groups", backref="users")
log = relationship("Logs", backref="users")
def get_id(self):
return str(self.alt_id)
class Posts(base): # pylint: disable=too-few-public-methods, C0103
"""
Post table
Joins with group_junction
"""
__tablename__ = "posts"
id = Column(Integer, primary_key=True)
author_id = Column(Integer, ForeignKey("users.id"))
created_at = Column(
DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102
)
filename = Column(String, unique=True, nullable=False)
mimetype = Column(String, nullable=False)
exif = Column(PickleType, nullable=False)
colours = Column(PickleType, nullable=False)
description = Column(String, nullable=False)
alt = Column(String, nullable=False)
junction = relationship("GroupJunction", backref="posts")
class Groups(base): # pylint: disable=too-few-public-methods, C0103
"""
Group table
Joins with group_junction
"""
__tablename__ = "groups"
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
description = Column(String, nullable=False)
author_id = Column(Integer, ForeignKey("users.id"))
created_at = Column(
DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102
)
junction = relationship("GroupJunction", backref="groups")
class GroupJunction(base): # pylint: disable=too-few-public-methods, C0103
"""
Junction table for posts and groups
Joins with posts and groups
"""
__tablename__ = "group_junction"
id = Column(Integer, primary_key=True)
date_added = Column(
DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102
)
group_id = Column(Integer, ForeignKey("groups.id"))
post_id = Column(Integer, ForeignKey("posts.id"))
class Logs(base): # pylint: disable=too-few-public-methods, C0103
"""
Log table
Joins with user
"""
__tablename__ = "logs"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"))
ip_address = Column(String, nullable=False)
code = Column(Integer, nullable=False)
note = Column(String, nullable=False)
created_at = Column(
DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102
)
class Bans(base): # pylint: disable=too-few-public-methods, C0103
"""
Bans table
"""
__tablename__ = "bans"
id = Column(Integer, primary_key=True)
ip_address = Column(String, nullable=False)
code = Column(Integer, nullable=False)
note = Column(String, nullable=False)
banned_at = Column(
DateTime, nullable=False, server_default=func.now() # pylint: disable=E1102
)
# check if database file exists, if not create it
if not os.path.isfile(DB_PATH):
base.metadata.create_all(engine)
print("Database created")

13
gallery/extensions.py Normal file
View file

@ -0,0 +1,13 @@
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_assets import Environment
from flask_compress import Compress
from flask_caching import Cache
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
assets = Environment()
compress = Compress()
cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 300})

138
gallery/models.py Normal file
View file

@ -0,0 +1,138 @@
"""
OnlyLegs - Database models and ions for SQLAlchemy
"""
from uuid import uuid4
from flask_login import UserMixin
from .extensions import db
class Users(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C0103
"""
User table
Joins with post, groups, session and log
"""
__tablename__ = "users"
# Gallery used information
id = db.Column(db.Integer, primary_key=True)
alt_id = db.Column(db.String, unique=True, nullable=False, default=str(uuid4()))
profile_picture = db.Column(db.String, nullable=True, default=None)
username = db.Column(db.String, unique=True, nullable=False)
email = db.Column(db.String, unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
joined_at = db.Column(
db.DateTime,
nullable=False,
server_default=db.func.now(), # pylint: disable=E1102
)
posts = db.relationship("Posts", backref="users")
groups = db.relationship("Groups", backref="users")
log = db.relationship("Logs", backref="users")
def get_id(self):
return str(self.alt_id)
class Posts(db.Model): # pylint: disable=too-few-public-methods, C0103
"""
Post table
Joins with group_junction
"""
__tablename__ = "posts"
id = db.Column(db.Integer, primary_key=True)
author_id = db.Column(db.Integer, db.ForeignKey("users.id"))
created_at = db.Column(
db.DateTime,
nullable=False,
server_default=db.func.now(), # pylint: disable=E1102
)
filename = db.Column(db.String, unique=True, nullable=False)
mimetype = db.Column(db.String, nullable=False)
exif = db.Column(db.PickleType, nullable=False)
colours = db.Column(db.PickleType, nullable=False)
description = db.Column(db.String, nullable=False)
alt = db.Column(db.String, nullable=False)
junction = db.relationship("GroupJunction", backref="posts")
class Groups(db.Model): # pylint: disable=too-few-public-methods, C0103
"""
Group table
Joins with group_junction
"""
__tablename__ = "groups"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
description = db.Column(db.String, nullable=False)
author_id = db.Column(db.Integer, db.ForeignKey("users.id"))
created_at = db.Column(
db.DateTime,
nullable=False,
server_default=db.func.now(), # pylint: disable=E1102
)
junction = db.relationship("GroupJunction", backref="groups")
class GroupJunction(db.Model): # pylint: disable=too-few-public-methods, C0103
"""
Junction table for posts and groups
Joins with posts and groups
"""
__tablename__ = "group_junction"
id = db.Column(db.Integer, primary_key=True)
date_added = db.Column(
db.DateTime,
nullable=False,
server_default=db.func.now(), # pylint: disable=E1102
)
group_id = db.Column(db.Integer, db.ForeignKey("groups.id"))
post_id = db.Column(db.Integer, db.ForeignKey("posts.id"))
class Logs(db.Model): # pylint: disable=too-few-public-methods, C0103
"""
Log table
Joins with user
"""
__tablename__ = "logs"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
ip_address = db.Column(db.String, nullable=False)
code = db.Column(db.Integer, nullable=False)
note = db.Column(db.String, nullable=False)
created_at = db.Column(
db.DateTime,
nullable=False,
server_default=db.func.now(), # pylint: disable=E1102
)
class Bans(db.Model): # pylint: disable=too-few-public-methods, C0103
"""
Bans table
"""
__tablename__ = "bans"
id = db.Column(db.Integer, primary_key=True)
ip_address = db.Column(db.String, nullable=False)
code = db.Column(db.Integer, nullable=False)
note = db.Column(db.String, nullable=False)
banned_at = db.Column(
db.DateTime,
nullable=False,
server_default=db.func.now(), # pylint: disable=E1102
)

View file

@ -5,14 +5,11 @@ sounds more limiting that it actually is in this gallery
""" """
from flask import Blueprint, abort, render_template, url_for from flask import Blueprint, abort, render_template, url_for
from sqlalchemy.orm import sessionmaker from gallery.models import Posts, Users, GroupJunction, Groups
from gallery import db
from gallery.utils import contrast from gallery.utils import contrast
blueprint = Blueprint("group", __name__, url_prefix="/group") blueprint = Blueprint("group", __name__, url_prefix="/group")
db_session = sessionmaker(bind=db.engine)
db_session = db_session()
@blueprint.route("/", methods=["GET"]) @blueprint.route("/", methods=["GET"])
@ -20,21 +17,21 @@ def groups():
""" """
Group overview, shows all image groups Group overview, shows all image groups
""" """
groups = db_session.query(db.Groups).all() groups = Groups.query.all()
# For each group, get the 3 most recent images # For each group, get the 3 most recent images
for group in groups: for group in groups:
group.author_username = ( group.author_username = (
db_session.query(db.Users.username) Users.query.with_entities(Users.username)
.filter(db.Users.id == group.author_id) .filter(Users.id == group.author_id)
.first()[0] .first()[0]
) )
# Get the 3 most recent images # Get the 3 most recent images
images = ( images = (
db_session.query(db.GroupJunction.post_id) GroupJunction.query.with_entities(GroupJunction.post_id)
.filter(db.GroupJunction.group_id == group.id) .filter(GroupJunction.group_id == group.id)
.order_by(db.GroupJunction.date_added.desc()) .order_by(GroupJunction.date_added.desc())
.limit(3) .limit(3)
) )
@ -42,10 +39,10 @@ def groups():
group.images = [] group.images = []
for image in images: for image in images:
group.images.append( group.images.append(
db_session.query( Posts.query.with_entities(
db.Posts.filename, db.Posts.alt, db.Posts.colours, db.Posts.id Posts.filename, Posts.alt, Posts.colours, Posts.id
) )
.filter(db.Posts.id == image[0]) .filter(Posts.id == image[0])
.first() .first()
) )
@ -58,32 +55,30 @@ def group(group_id):
Group view, shows all images in a group Group view, shows all images in a group
""" """
# Get the group, if it doesn't exist, 404 # Get the group, if it doesn't exist, 404
group = db_session.query(db.Groups).filter(db.Groups.id == group_id).first() group = Groups.query.filter(Groups.id == group_id).first()
if group is None: if group is None:
abort(404, "Group not found! D:") abort(404, "Group not found! D:")
# Get the group's author username # Get the group's author username
group.author_username = ( group.author_username = (
db_session.query(db.Users.username) Users.query.with_entities(Users.username)
.filter(db.Users.id == group.author_id) .filter(Users.id == group.author_id)
.first()[0] .first()[0]
) )
# Get all images in the group from the junction table # Get all images in the group from the junction table
junction = ( junction = (
db_session.query(db.GroupJunction.post_id) GroupJunction.query.with_entities(GroupJunction.post_id)
.filter(db.GroupJunction.group_id == group_id) .filter(GroupJunction.group_id == group_id)
.order_by(db.GroupJunction.date_added.desc()) .order_by(GroupJunction.date_added.desc())
.all() .all()
) )
# Get the image data for each image in the group # Get the image data for each image in the group
images = [] images = []
for image in junction: for image in junction:
images.append( images.append(Posts.query.filter(Posts.id == image[0]).first())
db_session.query(db.Posts).filter(db.Posts.id == image[0]).first()
)
# Check contrast for the first image in the group for the banner # Check contrast for the first image in the group for the banner
text_colour = "rgb(var(--fg-black))" text_colour = "rgb(var(--fg-black))"
@ -103,21 +98,21 @@ def group_post(group_id, image_id):
Image view, shows the image and its metadata from a specific group Image view, shows the image and its metadata from a specific group
""" """
# Get the image, if it doesn't exist, 404 # Get the image, if it doesn't exist, 404
image = db_session.query(db.Posts).filter(db.Posts.id == image_id).first() image = Posts.query.filter(Posts.id == image_id).first()
if image is None: if image is None:
abort(404, "Image not found") abort(404, "Image not found")
# Get the image's author username # Get the image's author username
image.author_username = ( image.author_username = (
db_session.query(db.Users.username) Users.query.with_entities(Users.username)
.filter(db.Users.id == image.author_id) .filter(Users.id == image.author_id)
.first()[0] .first()[0]
) )
# Get all groups the image is in # Get all groups the image is in
groups = ( groups = (
db_session.query(db.GroupJunction.group_id) GroupJunction.query.with_entities(GroupJunction.group_id)
.filter(db.GroupJunction.post_id == image_id) .filter(GroupJunction.post_id == image_id)
.all() .all()
) )
@ -125,24 +120,24 @@ def group_post(group_id, image_id):
image.groups = [] image.groups = []
for group in groups: for group in groups:
image.groups.append( image.groups.append(
db_session.query(db.Groups.id, db.Groups.name) Groups.query.with_entities(Groups.id, Groups.name)
.filter(db.Groups.id == group[0]) .filter(Groups.id == group[0])
.first() .first()
) )
# Get the next and previous images in the group # Get the next and previous images in the group
next_url = ( next_url = (
db_session.query(db.GroupJunction.post_id) GroupJunction.query.with_entities(GroupJunction.post_id)
.filter(db.GroupJunction.group_id == group_id) .filter(GroupJunction.group_id == group_id)
.filter(db.GroupJunction.post_id > image_id) .filter(GroupJunction.post_id > image_id)
.order_by(db.GroupJunction.date_added.asc()) .order_by(GroupJunction.date_added.asc())
.first() .first()
) )
prev_url = ( prev_url = (
db_session.query(db.GroupJunction.post_id) GroupJunction.query.with_entities(GroupJunction.post_id)
.filter(db.GroupJunction.group_id == group_id) .filter(GroupJunction.group_id == group_id)
.filter(db.GroupJunction.post_id < image_id) .filter(GroupJunction.post_id < image_id)
.order_by(db.GroupJunction.date_added.desc()) .order_by(GroupJunction.date_added.desc())
.first() .first()
) )

View file

@ -5,13 +5,10 @@ from math import ceil
from flask import Blueprint, abort, render_template, url_for, current_app from flask import Blueprint, abort, render_template, url_for, current_app
from sqlalchemy.orm import sessionmaker from gallery.models import Posts, Users, GroupJunction, Groups
from gallery import db
blueprint = Blueprint("image", __name__, url_prefix="/image") blueprint = Blueprint("image", __name__, url_prefix="/image")
db_session = sessionmaker(bind=db.engine)
db_session = db_session()
@blueprint.route("/<int:image_id>") @blueprint.route("/<int:image_id>")
@ -20,21 +17,21 @@ def image(image_id):
Image view, shows the image and its metadata Image view, shows the image and its metadata
""" """
# Get the image, if it doesn't exist, 404 # Get the image, if it doesn't exist, 404
image = db_session.query(db.Posts).filter(db.Posts.id == image_id).first() image = Posts.query.filter(Posts.id == image_id).first()
if not image: if not image:
abort(404, "Image not found :<") abort(404, "Image not found :<")
# Get the image's author username # Get the image's author username
image.author_username = ( image.author_username = (
db_session.query(db.Users.username) Users.query.with_entities(Users.username)
.filter(db.Users.id == image.author_id) .filter(Users.id == image.author_id)
.first()[0] .first()[0]
) )
# Get the image's groups # Get the image's groups
groups = ( groups = (
db_session.query(db.GroupJunction.group_id) GroupJunction.query.with_entities(GroupJunction.group_id)
.filter(db.GroupJunction.post_id == image_id) .filter(GroupJunction.post_id == image_id)
.all() .all()
) )
@ -42,23 +39,23 @@ def image(image_id):
image.groups = [] image.groups = []
for group in groups: for group in groups:
image.groups.append( image.groups.append(
db_session.query(db.Groups.id, db.Groups.name) Groups.query.with_entities(Groups.name, Groups.id)
.filter(db.Groups.id == group[0]) .filter(Groups.id == group[0])
.first() .first()
) )
# Get the next and previous images # Get the next and previous images
# Check if there is a group ID set # Check if there is a group ID set
next_url = ( next_url = (
db_session.query(db.Posts.id) Posts.query.with_entities(Posts.id)
.filter(db.Posts.id > image_id) .filter(Posts.id > image_id)
.order_by(db.Posts.id.asc()) .order_by(Posts.id.asc())
.first() .first()
) )
prev_url = ( prev_url = (
db_session.query(db.Posts.id) Posts.query.with_entities(Posts.id)
.filter(db.Posts.id < image_id) .filter(Posts.id < image_id)
.order_by(db.Posts.id.desc()) .order_by(Posts.id.desc())
.first() .first()
) )
@ -69,7 +66,7 @@ def image(image_id):
prev_url = url_for("image.image", image_id=prev_url[0]) prev_url = url_for("image.image", image_id=prev_url[0])
# Yoink all the images in the database # Yoink all the images in the database
total_images = db_session.query(db.Posts.id).order_by(db.Posts.id.desc()).all() total_images = Posts.query.with_entities(Posts.id).order_by(Posts.id.desc()).all()
limit = current_app.config["UPLOAD_CONF"]["max-load"] limit = current_app.config["UPLOAD_CONF"]["max-load"]
# If the number of items is less than the limit, no point of calculating the page # If the number of items is less than the limit, no point of calculating the page

View file

@ -6,13 +6,10 @@ from math import ceil
from flask import Blueprint, render_template, request, current_app from flask import Blueprint, render_template, request, current_app
from werkzeug.exceptions import abort from werkzeug.exceptions import abort
from sqlalchemy.orm import sessionmaker from gallery.models import Posts
from gallery import db
blueprint = Blueprint("gallery", __name__) blueprint = Blueprint("gallery", __name__)
db_session = sessionmaker(bind=db.engine)
db_session = db_session()
@blueprint.route("/") @blueprint.route("/")
@ -30,7 +27,7 @@ def index():
# get the total number of images in the database # get the total number of images in the database
# calculate the total number of pages, and make sure the page number is valid # calculate the total number of pages, and make sure the page number is valid
total_images = db_session.query(db.Posts.id).count() total_images = Posts.query.with_entities(Posts.id).count()
pages = ceil(max(total_images, limit) / limit) pages = ceil(max(total_images, limit) / limit)
if page > pages: if page > pages:
abort( abort(
@ -41,14 +38,10 @@ def index():
# get the images for the current page # get the images for the current page
images = ( images = (
db_session.query( Posts.query.with_entities(
db.Posts.filename, Posts.filename, Posts.alt, Posts.colours, Posts.created_at, Posts.id
db.Posts.alt,
db.Posts.colours,
db.Posts.created_at,
db.Posts.id,
) )
.order_by(db.Posts.id.desc()) .order_by(Posts.id.desc())
.offset((page - 1) * limit) .offset((page - 1) * limit)
.limit(limit) .limit(limit)
.all() .all()

View file

@ -5,13 +5,10 @@ from flask import Blueprint, render_template, request
from werkzeug.exceptions import abort from werkzeug.exceptions import abort
from flask_login import current_user from flask_login import current_user
from sqlalchemy.orm import sessionmaker from gallery.models import Posts, Users
from gallery import db
blueprint = Blueprint("profile", __name__, url_prefix="/profile") blueprint = Blueprint("profile", __name__, url_prefix="/profile")
db_session = sessionmaker(bind=db.engine)
db_session = db_session()
@blueprint.route("/profile") @blueprint.route("/profile")
@ -29,11 +26,11 @@ def profile():
abort(404, "You must be logged in to view your own profile!") abort(404, "You must be logged in to view your own profile!")
# Get the user's data # Get the user's data
user = db_session.query(db.Users).filter(db.Users.id == user_id).first() user = Users.query.filter(Users.id == user_id).first()
if not user: if not user:
abort(404, "User not found :c") abort(404, "User not found :c")
images = db_session.query(db.Posts).filter(db.Posts.author_id == user_id).all() images = Posts.query.filter(Posts.author_id == user_id).all()
return render_template("profile.html", user=user, images=images) return render_template("profile.html", user=user, images=images)

175
poetry.lock generated
View file

@ -1,5 +1,27 @@
# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. # This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand.
[[package]]
name = "alembic"
version = "1.10.3"
description = "A database migration tool for SQLAlchemy."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "alembic-1.10.3-py3-none-any.whl", hash = "sha256:b2e0a6cfd3a8ce936a1168320bcbe94aefa3f4463cd773a968a55071beb3cd37"},
{file = "alembic-1.10.3.tar.gz", hash = "sha256:32a69b13a613aeb7e8093f242da60eff9daed13c0df02fff279c1b06c32965d2"},
]
[package.dependencies]
importlib-metadata = {version = "*", markers = "python_version < \"3.9\""}
importlib-resources = {version = "*", markers = "python_version < \"3.9\""}
Mako = "*"
SQLAlchemy = ">=1.3.0"
typing-extensions = ">=4"
[package.extras]
tz = ["python-dateutil"]
[[package]] [[package]]
name = "astroid" name = "astroid"
version = "2.15.2" version = "2.15.2"
@ -192,79 +214,6 @@ files = [
[package.extras] [package.extras]
graph = ["objgraph (>=1.7.2)"] graph = ["objgraph (>=1.7.2)"]
[[package]]
name = "dukpy"
version = "0.3.0"
description = "Simple JavaScript interpreter for Python"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "dukpy-0.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:47ed8813baf52ad3e3a7d4c7416173af0693bbfab1f3b685cbf0165e0e376769"},
{file = "dukpy-0.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4b3a69977d89c83d74e64a5feb7264acb007c251e2eb83bc4e79c818b73b4fc"},
{file = "dukpy-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:782e60979db86f7ae9d5e84185cf6c252954cbcfda982353dd30ff6a17fef0be"},
{file = "dukpy-0.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3698f35c184b3319257d4d7bfa796ef109e8f78fc3cef8e22a3bf0f2d0eef774"},
{file = "dukpy-0.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bd7f6ded168548d808e3a3ac97ccf98ee1a97c327e7e67c13229932f3c923f85"},
{file = "dukpy-0.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9e044f3e78881f3c1fc0b939349551a9be2e2519d4e670038ce497d7cc780c69"},
{file = "dukpy-0.3.0-cp310-cp310-win32.whl", hash = "sha256:c1827f1f7282bb0cc329c7f687c0f58d87f5736777e553f483c26636e9bd1960"},
{file = "dukpy-0.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:d9697701eb1a01c0044479b3fa501685adc1a699ffe1acbb39b0b724bc1a7bac"},
{file = "dukpy-0.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c189ae4b5c5deb2b576cd0b0ae0193dbd7e15a1499491b3798f3ed7aae8274b1"},
{file = "dukpy-0.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:531db11c50326c1baa00711a8221995ec0935418c690d02a84ef9ce537968686"},
{file = "dukpy-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c072d28ff58db698eb5bfa4556f59e5ce4d4f219b176c93375bfda87c117253f"},
{file = "dukpy-0.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8171990e640625ad5876a0548072220ebd34c9f0705510144082ce34a2e777b"},
{file = "dukpy-0.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:74e0a194e8908bfa64ea2e2e353cf28184d498ed675174a96d948ac2dd6db24e"},
{file = "dukpy-0.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0aebd4be1109e58126ff4e959415f3198390b92dc48cf6144b97caeb786cf0df"},
{file = "dukpy-0.3.0-cp311-cp311-win32.whl", hash = "sha256:ff24928cf9c14af226cf575640e2166611a79d8fd14ea498183ca7cd7ab349e5"},
{file = "dukpy-0.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:a96a600ce653c5fb9c7190af8c1e82b7d212709dfdd31ce65a2e328cbd923dd6"},
{file = "dukpy-0.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:581bbd180a7d69149a1b3171d987a8d1eedf988ce3d138ca2e1730888012e41a"},
{file = "dukpy-0.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0636f4658024033427907b3a67b9bbc9c405fd7ee1f924ec1b1eca070d7a6efb"},
{file = "dukpy-0.3.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1628e9171e900d2b97e45a22709e68f91bb88ef8dbabfc0c1f4f92524eeb900e"},
{file = "dukpy-0.3.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:2c1576a480656ee4bce9bb2f471623b894c8ab809617bdf08b8f547a990df063"},
{file = "dukpy-0.3.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:87202891b5dd85053321b561173ebbe84ceab58f9cd4e6c028686e5793bfc976"},
{file = "dukpy-0.3.0-cp36-cp36m-win32.whl", hash = "sha256:6b2ef5b42a666d4cd73618dce1b9b182c02f15cf52598aef4047e0ecbed2f4ed"},
{file = "dukpy-0.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6e16d07a506e79af132a7d1b4d28b7846d1e980a8a965130bfe755f56922f35e"},
{file = "dukpy-0.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7708973d15bc01c91e68195338f9db0a6d4b1e663e2a778da2db00b8c27e7488"},
{file = "dukpy-0.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:218c26430b424cd2fa4a8a0e252acf835719ee2107937d01c7bbc15615b07e0d"},
{file = "dukpy-0.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d1f25e485a77e1318b95db43717454001e412adec0ba268dfc8eecf3b893d45"},
{file = "dukpy-0.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5a1614b73884c14a00b496384d2e793bfd07dfcac425eb1fe768e5b870118111"},
{file = "dukpy-0.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005579d0e3fd7ab35e18138da3172baa59e3305f22a55fbe9961c67204b1ddd"},
{file = "dukpy-0.3.0-cp37-cp37m-win32.whl", hash = "sha256:e59a93c819cb818251e7d8ad0b548163227fec3b8485c4cdcecfac59abd9db87"},
{file = "dukpy-0.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d10c0cd5035e3e2dc27d193734537546f1910d2dc0ccd468bb510924313bbaa2"},
{file = "dukpy-0.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3bee97f928e0477a197fcc66f25a8d46d1ebc7068ddda2f657445cced303111b"},
{file = "dukpy-0.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bd7e8b90590122b92e8057052e485afdcc4a6145e50036cc55deac045dd6568f"},
{file = "dukpy-0.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2feb5c2d05b3c9b8fafc9088c5c025a14c9e239f96abb1aa75ebc022f1777e9c"},
{file = "dukpy-0.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09ae9309490fc578da96611fc50e46f02c32616e53f55f2bc9c864f67e6c759e"},
{file = "dukpy-0.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf7412d2d6883fe0ff498cbdb0e67e16804972cf216c169d83aa8d5bad50d109"},
{file = "dukpy-0.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a8e06c2402031e030924088b37bbde27cf43936bf8ff0ff65c9bdfd9bf4ae89c"},
{file = "dukpy-0.3.0-cp38-cp38-win32.whl", hash = "sha256:d87b932a387d4015d9acdb99b94c788453b19b5aa5fd10584098e042d8c7118a"},
{file = "dukpy-0.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e31213f8cbbf85d0386f0ea0e478cd0e4dd918a8747d568a6936044dbd21330c"},
{file = "dukpy-0.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49f6390bbc47b618fdb19d7af89e73f643f308a2ab9f5d5e0eb161d4508f23c6"},
{file = "dukpy-0.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a95ff658b7400e71acaab453359ea74d1a1625cebb937d0294a053b6aac3e507"},
{file = "dukpy-0.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9087a3321649beb17f91afa6ffde991d477aa0029c3be5ce908369517ac85251"},
{file = "dukpy-0.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6acc3a3ce997aef380f79f1985636d87701c1841707c0748ee5eff65e396f0b2"},
{file = "dukpy-0.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1329be71ce19fdda899a0b59cd531b711adc0d30867488f7401b38b518415a9"},
{file = "dukpy-0.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0c6ebf8e495f9750f2820cdedfe384621369ebef562ed52770d7a9f070e5991e"},
{file = "dukpy-0.3.0-cp39-cp39-win32.whl", hash = "sha256:6ae877b9d439941e2afcaaaa410ef168c51e885f99665bf591b97a71eafaeb0f"},
{file = "dukpy-0.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:1a8df866eb0af6b55f1a27786f5217334a4e904fd04b7c285c4ee5b684072abe"},
{file = "dukpy-0.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b895adaab9feaec6e33ba221bcfc16bd50710b18346077b8cec06e843355fb6"},
{file = "dukpy-0.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2edf126a5c8da0b1ffc39381323d3129cf922d041c74c78402652c9efdf74c99"},
{file = "dukpy-0.3.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f0f517d245b69781ad91dcb6d9d1a9550b2dbb0d8b636b9e8899838780ad211"},
{file = "dukpy-0.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:99f76adf6f9c40b0501d7fffc1570a7b7dc4eaf8b2d3cb38ac738068ba2731e6"},
{file = "dukpy-0.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:eca56334b67370427c503b65f21424d317b7560620e28809b4852828a9fcea54"},
{file = "dukpy-0.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3da5a1bc3ce7788ed05cc16fc67f9be5e187ed4f6fedcf1fd6574633a5230be"},
{file = "dukpy-0.3.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1cb574f3b71545adbccbb7688059b1a63eca057c59ac00004a6de196eb95844a"},
{file = "dukpy-0.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b4b068796a4d81c37673e9d949a6307dbadc2cd6c2062b6010bd6561a24895fb"},
{file = "dukpy-0.3.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:87a9ea4cb2593220e0c6abc6a0b5849e940de78c1e464fe6a4339efe655fd3af"},
{file = "dukpy-0.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1185f27c920889a41e189ba8a2c76211ee84be1ea1bb4c1f7cc4343f9a1a3d2c"},
{file = "dukpy-0.3.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a60d5b3537800944cb6e8bedbf68a724dea92a6f9a8ce9a48530838e68478716"},
{file = "dukpy-0.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:157280c79833f223f3dc6effe8981525e68cd262e26947b2cddd57addac9a3d8"},
{file = "dukpy-0.3.0.tar.gz", hash = "sha256:ca5772e9373f3cf7772a711e65db765c4361dcf6d4e65c5d88cb879e9ee3f5a6"},
]
[package.extras]
testing = ["mock", "pytest", "pytest-cov"]
webassets = ["webassets"]
[[package]] [[package]]
name = "flask" name = "flask"
version = "2.2.3" version = "2.2.3"
@ -352,6 +301,39 @@ files = [
Flask = ">=1.0.4" Flask = ">=1.0.4"
Werkzeug = ">=1.0.1" Werkzeug = ">=1.0.1"
[[package]]
name = "flask-migrate"
version = "4.0.4"
description = "SQLAlchemy database migrations for Flask applications using Alembic."
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "Flask-Migrate-4.0.4.tar.gz", hash = "sha256:73293d40b10ac17736e715b377e7b7bde474cb8105165d77474df4c3619b10b3"},
{file = "Flask_Migrate-4.0.4-py3-none-any.whl", hash = "sha256:77580f27ab39bc68be4906a43c56d7674b45075bc4f883b1d0b985db5164d58f"},
]
[package.dependencies]
alembic = ">=1.9.0"
Flask = ">=0.9"
Flask-SQLAlchemy = ">=1.0"
[[package]]
name = "flask-sqlalchemy"
version = "3.0.3"
description = "Add SQLAlchemy support to your Flask application."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "Flask-SQLAlchemy-3.0.3.tar.gz", hash = "sha256:2764335f3c9d7ebdc9ed6044afaf98aae9fa50d7a074cef55dde307ec95903ec"},
{file = "Flask_SQLAlchemy-3.0.3-py3-none-any.whl", hash = "sha256:add5750b2f9cd10512995261ee2aa23fab85bd5626061aa3c564b33bb4aa780a"},
]
[package.dependencies]
Flask = ">=2.2"
SQLAlchemy = ">=1.4.18"
[[package]] [[package]]
name = "greenlet" name = "greenlet"
version = "2.0.2" version = "2.0.2"
@ -449,14 +431,14 @@ tornado = ["tornado (>=0.2)"]
[[package]] [[package]]
name = "importlib-metadata" name = "importlib-metadata"
version = "6.2.0" version = "6.2.1"
description = "Read metadata from Python packages" description = "Read metadata from Python packages"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "importlib_metadata-6.2.0-py3-none-any.whl", hash = "sha256:8388b74023a138c605fddd0d47cb81dd706232569f56c9aca7d9c7fdb54caeba"}, {file = "importlib_metadata-6.2.1-py3-none-any.whl", hash = "sha256:f65e478a7c2177bd19517a3a15dac094d253446d8690c5f3e71e735a04312374"},
{file = "importlib_metadata-6.2.0.tar.gz", hash = "sha256:9127aad2f49d7203e7112098c12b92e4fd1061ccd18548cdfdc49171a8c073cc"}, {file = "importlib_metadata-6.2.1.tar.gz", hash = "sha256:5a66966b39ff1c14ef5b2d60c1d842b0141fefff0f4cc6365b4bc9446c652807"},
] ]
[package.dependencies] [package.dependencies]
@ -467,6 +449,25 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker
perf = ["ipython"] perf = ["ipython"]
testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
[[package]]
name = "importlib-resources"
version = "5.12.0"
description = "Read resources from Python packages"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"},
{file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"},
]
[package.dependencies]
zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
[[package]] [[package]]
name = "isort" name = "isort"
version = "5.12.0" version = "5.12.0"
@ -587,6 +588,26 @@ files = [
{file = "libsass-0.22.0.tar.gz", hash = "sha256:3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425"}, {file = "libsass-0.22.0.tar.gz", hash = "sha256:3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425"},
] ]
[[package]]
name = "mako"
version = "1.2.4"
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"},
{file = "Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"},
]
[package.dependencies]
MarkupSafe = ">=0.9.2"
[package.extras]
babel = ["Babel"]
lingua = ["lingua"]
testing = ["pytest"]
[[package]] [[package]]
name = "markupsafe" name = "markupsafe"
version = "2.1.2" version = "2.1.2"
@ -1115,4 +1136,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "8ff06b3873e6466deca84a8258f87f8aa250072d02e30564fbd554e826754e61" content-hash = "88387c49c901feebd4685ee75f6b79c0bbc8cc9c1a64798cd0394b140e165603"

View file

@ -9,19 +9,20 @@ readme = ".github/README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.8" python = "^3.8"
Flask = "^2.2.2" Flask = "^2.2.2"
Flask-Sqlalchemy = "^3.0.3"
Flask-Migrate = "^4.0.4"
Flask-Compress = "^1.13" Flask-Compress = "^1.13"
Flask-Caching = "^2.0.2" Flask-Caching = "^2.0.2"
Flask-Assets = "^2.0" Flask-Assets = "^2.0"
Flask-Login = "^0.6.2" Flask-Login = "^0.6.2"
SQLAlchemy = "^2.0.3"
python-dotenv = "^0.21.0" python-dotenv = "^0.21.0"
gunicorn = "^20.1.0" gunicorn = "^20.1.0"
pyyaml = "^6.0" pyyaml = "^6.0"
libsass = "^0.22.0"
colorthief = "^0.2.1" colorthief = "^0.2.1"
Pillow = "^9.4.0" Pillow = "^9.4.0"
platformdirs = "^3.0.0" platformdirs = "^3.0.0"
pylint = "^2.16.3" pylint = "^2.16.3"
libsass = "^0.22.0"
jsmin = "^3.0.1" jsmin = "^3.0.1"
cssmin = "^0.2.0" cssmin = "^0.2.0"