mirror of
https://github.com/Derpy-Leggies/OnlyLegs.git
synced 2025-01-27 08:41:41 +00:00
commit
a33fa2f690
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
github: Fluffy-Bean
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,7 +1,9 @@
|
|||
gallery/static/theme
|
||||
gallery/static/.webassets-cache
|
||||
gallery/static/gen
|
||||
|
||||
instance/
|
||||
migrations/
|
||||
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
|
@ -11,7 +13,6 @@ venv/
|
|||
*.pyc
|
||||
__pycache__/
|
||||
|
||||
instance/
|
||||
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
|
|
|
@ -2,79 +2,87 @@
|
|||
Onlylegs Gallery
|
||||
This is the main app file, it loads all the other files and sets up the app
|
||||
"""
|
||||
|
||||
# Import system modules
|
||||
import os
|
||||
import logging
|
||||
import platformdirs
|
||||
|
||||
from flask_assets import Bundle
|
||||
|
||||
from flask_migrate import init as migrate_init
|
||||
from flask_migrate import upgrade as migrate_upgrade
|
||||
from flask_migrate import migrate as migrate_migrate
|
||||
|
||||
# Flask
|
||||
from flask_compress import Compress
|
||||
from flask_caching import Cache
|
||||
from flask_assets import Environment, Bundle
|
||||
from flask_login import LoginManager
|
||||
from flask import Flask, render_template, abort
|
||||
from werkzeug.exceptions import HTTPException
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
# Configuration
|
||||
import platformdirs
|
||||
from dotenv import load_dotenv
|
||||
from yaml import safe_load
|
||||
|
||||
# Import database
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from gallery import db
|
||||
from gallery.extensions import db, migrate, login_manager, assets, compress, cache
|
||||
from gallery.views import index, image, group, settings, profile
|
||||
from gallery.models import User
|
||||
from gallery import api
|
||||
from gallery import auth
|
||||
|
||||
|
||||
USER_DIR = platformdirs.user_config_dir("onlylegs")
|
||||
INSTACE_DIR = os.path.join(platformdirs.user_config_dir("onlylegs"), "instance")
|
||||
MIGRATIONS_DIR = os.path.join(INSTACE_DIR, "migrations")
|
||||
|
||||
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
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
|
||||
def create_app(): # pylint: disable=R0914
|
||||
"""
|
||||
Create and configure the main app
|
||||
"""
|
||||
app = Flask(__name__, instance_path=os.path.join(USER_DIR, "instance"))
|
||||
app = Flask(__name__, instance_path=INSTACE_DIR)
|
||||
app.config.from_pyfile("config.py")
|
||||
|
||||
# Get environment variables
|
||||
load_dotenv(os.path.join(USER_DIR, ".env"))
|
||||
print("Loaded environment variables")
|
||||
# DATABASE
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
|
||||
# Get config file
|
||||
with open(os.path.join(USER_DIR, "conf.yml"), encoding="utf-8", mode="r") as file:
|
||||
conf = safe_load(file)
|
||||
print("Loaded config")
|
||||
# If database file doesn't exist, create it
|
||||
if not os.path.exists(os.path.join(INSTACE_DIR, "gallery.sqlite3")):
|
||||
print("Creating database")
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
# App configuration
|
||||
app.config.from_mapping(
|
||||
SECRET_KEY=os.environ.get("FLASK_SECRET"),
|
||||
DATABASE=os.path.join(app.instance_path, "gallery.sqlite3"),
|
||||
UPLOAD_FOLDER=os.path.join(USER_DIR, "uploads"),
|
||||
ALLOWED_EXTENSIONS=conf["upload"]["allowed-extensions"],
|
||||
MAX_CONTENT_LENGTH=1024 * 1024 * conf["upload"]["max-size"],
|
||||
ADMIN_CONF=conf["admin"],
|
||||
UPLOAD_CONF=conf["upload"],
|
||||
WEBSITE_CONF=conf["website"],
|
||||
)
|
||||
register_user = User(
|
||||
username=app.config["ADMIN_CONF"]["username"],
|
||||
email=app.config["ADMIN_CONF"]["email"],
|
||||
password=generate_password_hash("changeme!", method="sha256"),
|
||||
)
|
||||
db.session.add(register_user)
|
||||
db.session.commit()
|
||||
|
||||
if test_config is None:
|
||||
app.config.from_pyfile("config.py", silent=True)
|
||||
else:
|
||||
app.config.from_mapping(test_config)
|
||||
print(
|
||||
"""
|
||||
####################################################
|
||||
# DEFAULY ADMIN USER GENERATED WITH GIVEN USERNAME #
|
||||
# THE DEFAULT PASSWORD "changeme!" HAS BEEN USED, #
|
||||
# PLEASE UPDATE IT IN THE SETTINGS! #
|
||||
####################################################
|
||||
"""
|
||||
)
|
||||
|
||||
# Check if migrations directory exists, if not create it
|
||||
with app.app_context():
|
||||
if not os.path.exists(MIGRATIONS_DIR):
|
||||
print("Creating migrations directory")
|
||||
migrate_init(directory=MIGRATIONS_DIR)
|
||||
|
||||
# Check if migrations are up to date
|
||||
with app.app_context():
|
||||
print("Checking for schema changes...")
|
||||
migrate_migrate(directory=MIGRATIONS_DIR)
|
||||
migrate_upgrade(directory=MIGRATIONS_DIR)
|
||||
|
||||
# LOGIN MANAGER
|
||||
# can also set session_protection to "strong"
|
||||
# this would protect against session hijacking
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = "gallery.index"
|
||||
login_manager.session_protection = "strong"
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return db_session.query(db.Users).filter_by(alt_id=user_id).first()
|
||||
return User.query.filter_by(alt_id=user_id).first()
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def unauthorized():
|
||||
|
@ -82,23 +90,12 @@ def create_app(test_config=None): # pylint: disable=R0914
|
|||
msg = "You are not authorized to view this page!!!!"
|
||||
return render_template("error.html", error=error, msg=msg), error
|
||||
|
||||
lib = Bundle(
|
||||
"lib/*.js", filters="jsmin", output="gen/lib.js", depends="lib/*.js"
|
||||
)
|
||||
scripts = Bundle(
|
||||
"js/*.js", filters="jsmin", output="gen/index.js", depends="js/*.js"
|
||||
)
|
||||
styles = Bundle(
|
||||
"sass/*.sass", filters="libsass, cssmin", output="gen/styles.css", depends='sass/**/*.sass'
|
||||
)
|
||||
|
||||
assets.register("lib", lib)
|
||||
assets.register("js", scripts)
|
||||
assets.register("styles", styles)
|
||||
|
||||
# Error handlers, if the error is not a HTTP error, return 500
|
||||
# ERROR HANDLERS
|
||||
@app.errorhandler(Exception)
|
||||
def error_page(err): # noqa
|
||||
"""
|
||||
Error handlers, if the error is not a HTTP error, return 500
|
||||
"""
|
||||
if not isinstance(err, HTTPException):
|
||||
abort(500)
|
||||
return (
|
||||
|
@ -106,30 +103,34 @@ def create_app(test_config=None): # pylint: disable=R0914
|
|||
err.code,
|
||||
)
|
||||
|
||||
# Load login, registration and logout manager
|
||||
from gallery import auth
|
||||
# ASSETS
|
||||
assets.init_app(app)
|
||||
|
||||
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)
|
||||
|
||||
# BLUEPRINTS
|
||||
app.register_blueprint(auth.blueprint)
|
||||
|
||||
# Load the API
|
||||
from gallery import api
|
||||
|
||||
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(image.blueprint)
|
||||
app.register_blueprint(group.blueprint)
|
||||
app.register_blueprint(profile.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)
|
||||
# CACHE AND COMPRESS
|
||||
cache.init_app(app)
|
||||
compress.init_app(app)
|
||||
|
||||
# Yupee! We got there :3
|
||||
print("Done!")
|
||||
logging.info("Gallery started successfully!")
|
||||
return app
|
||||
|
|
|
@ -9,21 +9,17 @@ import platformdirs
|
|||
|
||||
from flask import Blueprint, send_from_directory, abort, flash, request, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
from colorthief import ColorThief
|
||||
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from gallery import db
|
||||
from gallery.extensions import db
|
||||
from gallery.models import Post, Group, GroupJunction
|
||||
from gallery.utils import metadata as mt
|
||||
from gallery.utils.generate_image import generate_thumbnail
|
||||
|
||||
|
||||
blueprint = Blueprint("api", __name__, url_prefix="/api")
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route("/file/<file_name>", methods=["GET"])
|
||||
|
@ -87,7 +83,7 @@ def upload():
|
|||
img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette
|
||||
|
||||
# Save to database
|
||||
query = db.Posts(
|
||||
query = Post(
|
||||
author_id=current_user.id,
|
||||
filename=img_name + "." + img_ext,
|
||||
mimetype=img_ext,
|
||||
|
@ -97,8 +93,8 @@ def upload():
|
|||
alt=form["alt"],
|
||||
)
|
||||
|
||||
db_session.add(query)
|
||||
db_session.commit()
|
||||
db.session.add(query)
|
||||
db.session.commit()
|
||||
|
||||
return "Gwa Gwa" # Return something so the browser doesn't show an error
|
||||
|
||||
|
@ -109,40 +105,33 @@ def delete_image(image_id):
|
|||
"""
|
||||
Deletes an image from the server and database
|
||||
"""
|
||||
img = db_session.query(db.Posts).filter_by(id=image_id).first()
|
||||
post = Post.query.filter_by(id=image_id).first()
|
||||
|
||||
# Check if image exists and if user is allowed to delete it (author)
|
||||
if img is None:
|
||||
if post is None:
|
||||
abort(404)
|
||||
if img.author_id != current_user.id:
|
||||
if post.author_id != current_user.id:
|
||||
abort(403)
|
||||
|
||||
# Delete file
|
||||
try:
|
||||
os.remove(os.path.join(current_app.config["UPLOAD_FOLDER"], img.filename))
|
||||
os.remove(os.path.join(current_app.config["UPLOAD_FOLDER"], post.filename))
|
||||
except FileNotFoundError:
|
||||
logging.warning(
|
||||
"File not found: %s, already deleted or never existed", img.filename
|
||||
"File not found: %s, already deleted or never existed", post.filename
|
||||
)
|
||||
|
||||
# Delete cached files
|
||||
cache_path = os.path.join(platformdirs.user_config_dir("onlylegs"), "cache")
|
||||
cache_name = img.filename.rsplit(".")[0]
|
||||
cache_name = post.filename.rsplit(".")[0]
|
||||
for cache_file in pathlib.Path(cache_path).glob(cache_name + "*"):
|
||||
os.remove(cache_file)
|
||||
|
||||
# Delete from database
|
||||
db_session.query(db.Posts).filter_by(id=image_id).delete()
|
||||
GroupJunction.query.filter_by(post_id=image_id).delete()
|
||||
db.session.delete(post)
|
||||
db.session.commit()
|
||||
|
||||
# Remove all entries in junction table
|
||||
groups = db_session.query(db.GroupJunction).filter_by(post_id=image_id).all()
|
||||
for group in groups:
|
||||
db_session.delete(group)
|
||||
|
||||
# Commit all changes
|
||||
db_session.commit()
|
||||
|
||||
logging.info("Removed image (%s) %s", image_id, img.filename)
|
||||
logging.info("Removed image (%s) %s", image_id, post.filename)
|
||||
flash(["Image was all in Le Head!", "1"])
|
||||
return "Gwa Gwa"
|
||||
|
||||
|
@ -153,14 +142,14 @@ def create_group():
|
|||
"""
|
||||
Creates a group
|
||||
"""
|
||||
new_group = db.Groups(
|
||||
new_group = Group(
|
||||
name=request.form["name"],
|
||||
description=request.form["description"],
|
||||
author_id=current_user.id,
|
||||
)
|
||||
|
||||
db_session.add(new_group)
|
||||
db_session.commit()
|
||||
db.session.add(new_group)
|
||||
db.session.commit()
|
||||
|
||||
return ":3"
|
||||
|
||||
|
@ -175,29 +164,23 @@ def modify_group():
|
|||
image_id = request.form["image"]
|
||||
action = request.form["action"]
|
||||
|
||||
group = db_session.query(db.Groups).filter_by(id=group_id).first()
|
||||
group = db.get_or_404(Group, group_id)
|
||||
db.get_or_404(Post, image_id) # Check if image exists
|
||||
|
||||
if group is None:
|
||||
abort(404)
|
||||
elif group.author_id != current_user.id:
|
||||
if group.author_id != current_user.id:
|
||||
abort(403)
|
||||
|
||||
if action == "add":
|
||||
if not (
|
||||
db_session.query(db.GroupJunction)
|
||||
.filter_by(group_id=group_id, post_id=image_id)
|
||||
.first()
|
||||
):
|
||||
db_session.add(db.GroupJunction(group_id=group_id, post_id=image_id))
|
||||
if (
|
||||
action == "add"
|
||||
and not GroupJunction.query.filter_by(
|
||||
group_id=group_id, post_id=image_id
|
||||
).first()
|
||||
):
|
||||
db.session.add(GroupJunction(group_id=group_id, post_id=image_id))
|
||||
elif request.form["action"] == "remove":
|
||||
(
|
||||
db_session.query(db.GroupJunction)
|
||||
.filter_by(group_id=group_id, post_id=image_id)
|
||||
.delete()
|
||||
)
|
||||
|
||||
db_session.commit()
|
||||
GroupJunction.query.filter_by(group_id=group_id, post_id=image_id).delete()
|
||||
|
||||
db.session.commit()
|
||||
return ":3"
|
||||
|
||||
|
||||
|
@ -207,17 +190,16 @@ def delete_group():
|
|||
Deletes a group
|
||||
"""
|
||||
group_id = request.form["group"]
|
||||
|
||||
group = db_session.query(db.Groups).filter_by(id=group_id).first()
|
||||
group = Group.query.filter_by(id=group_id).first()
|
||||
|
||||
if group is None:
|
||||
abort(404)
|
||||
elif group.author_id != current_user.id:
|
||||
abort(403)
|
||||
|
||||
db_session.query(db.Groups).filter_by(id=group_id).delete()
|
||||
db_session.query(db.GroupJunction).filter_by(group_id=group_id).delete()
|
||||
db_session.commit()
|
||||
GroupJunction.query.filter_by(group_id=group_id).delete()
|
||||
db.session.delete(group)
|
||||
db.session.commit()
|
||||
|
||||
flash(["Group yeeted!", "1"])
|
||||
return ":3"
|
||||
|
|
|
@ -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 sqlalchemy.orm import sessionmaker
|
||||
from gallery import db
|
||||
from gallery.extensions import db
|
||||
from gallery.models import User
|
||||
|
||||
|
||||
blueprint = Blueprint("auth", __name__, url_prefix="/auth")
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route("/login", methods=["POST"])
|
||||
|
@ -30,7 +28,7 @@ def login():
|
|||
password = request.form["password"].strip()
|
||||
remember = bool(request.form["remember-me"])
|
||||
|
||||
user = db_session.query(db.Users).filter_by(username=username).first()
|
||||
user = User.query.filter_by(username=username).first()
|
||||
|
||||
if not user or not check_password_hash(user.password, password):
|
||||
logging.error("Login attempt from %s", request.remote_addr)
|
||||
|
@ -79,7 +77,7 @@ def register():
|
|||
elif password_repeat != password:
|
||||
error.append("Passwords do not match!")
|
||||
|
||||
user_exists = db_session.query(db.Users).filter_by(username=username).first()
|
||||
user_exists = User.query.filter_by(username=username).first()
|
||||
if user_exists:
|
||||
error.append("User already exists!")
|
||||
|
||||
|
@ -88,13 +86,13 @@ def register():
|
|||
print(error)
|
||||
return jsonify(error), 400
|
||||
|
||||
register_user = db.Users(
|
||||
register_user = User(
|
||||
username=username,
|
||||
email=email,
|
||||
password=generate_password_hash(password, method="sha256"),
|
||||
)
|
||||
db_session.add(register_user)
|
||||
db_session.commit()
|
||||
db.session.add(register_user)
|
||||
db.session.commit()
|
||||
|
||||
logging.info("User %s registered", username)
|
||||
return "ok", 200
|
||||
|
|
36
gallery/config.py
Normal file
36
gallery/config.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
Gallery configuration file
|
||||
"""
|
||||
import os
|
||||
import platformdirs
|
||||
from dotenv import load_dotenv
|
||||
from yaml import safe_load
|
||||
|
||||
|
||||
# Set dirs
|
||||
user_dir = platformdirs.user_config_dir("onlylegs")
|
||||
instance_dir = os.path.join(user_dir, "instance")
|
||||
|
||||
# Load environment variables
|
||||
print("Loading environment variables...")
|
||||
load_dotenv(os.path.join(user_dir, ".env"))
|
||||
|
||||
# Load config from user dir
|
||||
print("Loading config...")
|
||||
with open(os.path.join(user_dir, "conf.yml"), encoding="utf-8", mode="r") as file:
|
||||
conf = safe_load(file)
|
||||
|
||||
|
||||
# Flask config
|
||||
SECRET_KEY = os.environ.get("FLASK_SECRET")
|
||||
SQLALCHEMY_DATABASE_URI = "sqlite:///gallery.sqlite3"
|
||||
|
||||
# Upload config
|
||||
MAX_CONTENT_LENGTH = 1024 * 1024 * conf["upload"]["max-size"]
|
||||
UPLOAD_FOLDER = os.path.join(user_dir, "uploads")
|
||||
ALLOWED_EXTENSIONS = conf["upload"]["allowed-extensions"]
|
||||
|
||||
# Pass YAML config to app
|
||||
ADMIN_CONF = conf["admin"]
|
||||
UPLOAD_CONF = conf["upload"]
|
||||
WEBSITE_CONF = conf["website"]
|
153
gallery/db.py
153
gallery/db.py
|
@ -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")
|
16
gallery/extensions.py
Normal file
16
gallery/extensions.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Extensions used by the application
|
||||
"""
|
||||
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})
|
105
gallery/models.py
Normal file
105
gallery/models.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
"""
|
||||
OnlyLegs - Database models and ions for SQLAlchemy
|
||||
"""
|
||||
from uuid import uuid4
|
||||
from flask_login import UserMixin
|
||||
from .extensions import db
|
||||
|
||||
|
||||
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)
|
||||
|
||||
group_id = db.Column(db.Integer, db.ForeignKey("group.id"))
|
||||
post_id = db.Column(db.Integer, db.ForeignKey("post.id"))
|
||||
|
||||
date_added = db.Column(
|
||||
db.DateTime,
|
||||
nullable=False,
|
||||
server_default=db.func.now(), # pylint: disable=E1102
|
||||
)
|
||||
|
||||
|
||||
class Post(db.Model): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
Post table
|
||||
"""
|
||||
|
||||
__tablename__ = "post"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||
|
||||
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)
|
||||
|
||||
created_at = db.Column(
|
||||
db.DateTime,
|
||||
nullable=False,
|
||||
server_default=db.func.now(), # pylint: disable=E1102
|
||||
)
|
||||
|
||||
junction = db.relationship("GroupJunction", backref="posts")
|
||||
|
||||
|
||||
class Group(db.Model): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
Group table
|
||||
"""
|
||||
|
||||
__tablename__ = "group"
|
||||
|
||||
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("user.id"))
|
||||
created_at = db.Column(
|
||||
db.DateTime,
|
||||
nullable=False,
|
||||
server_default=db.func.now(), # pylint: disable=E1102
|
||||
)
|
||||
|
||||
junction = db.relationship("GroupJunction", backref="groups")
|
||||
|
||||
|
||||
class User(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
User table
|
||||
"""
|
||||
|
||||
__tablename__ = "user"
|
||||
|
||||
# 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("Post", backref="author")
|
||||
groups = db.relationship("Group", backref="author")
|
||||
|
||||
def get_id(self):
|
||||
return str(self.alt_id)
|
7
gallery/static/fonts/font.css
Normal file
7
gallery/static/fonts/font.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
@font-face {
|
||||
font-family: 'Rubik';
|
||||
src: url('./Rubik.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-weight: 300 900;
|
||||
}
|
|
@ -65,7 +65,7 @@ window.onload = function () {
|
|||
}
|
||||
infoButton.onclick = function () {
|
||||
popUpShow('OnlyLegs',
|
||||
'<a href="https://github.com/Fluffy-Bean/onlylegs">V23.04.08</a> ' +
|
||||
'<a href="https://github.com/Fluffy-Bean/onlylegs">V23.04.10</a> ' +
|
||||
'using <a href="https://phosphoricons.com/">Phosphoricons</a> and Flask.' +
|
||||
'<br>Made by Fluffy and others with ❤️');
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
function addNotification(notificationText, notificationLevel) {
|
||||
let notificationContainer = document.querySelector('.notifications');
|
||||
const notificationContainer = document.querySelector('.notifications');
|
||||
|
||||
// Set the different icons for the different notification levels
|
||||
let successIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"></path></svg>';
|
||||
let criticalIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z"></path></svg>';
|
||||
let warningIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path></svg>';
|
||||
let infoIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path></svg>';
|
||||
const successIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"></path></svg>';
|
||||
const criticalIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z"></path></svg>';
|
||||
const warningIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path></svg>';
|
||||
const infoIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path></svg>';
|
||||
|
||||
// Create notification element
|
||||
let notification = document.createElement('div');
|
||||
const notification = document.createElement('div');
|
||||
notification.classList.add('sniffle__notification');
|
||||
notification.onclick = function() {
|
||||
if (notification) {
|
||||
|
@ -21,7 +21,7 @@ function addNotification(notificationText, notificationLevel) {
|
|||
};
|
||||
|
||||
// Create icon element and append to notification
|
||||
let iconElement = document.createElement('span');
|
||||
const iconElement = document.createElement('span');
|
||||
iconElement.classList.add('sniffle__notification-icon');
|
||||
notification.appendChild(iconElement);
|
||||
|
||||
|
@ -41,7 +41,7 @@ function addNotification(notificationText, notificationLevel) {
|
|||
}
|
||||
|
||||
// Create text element and append to notification
|
||||
let description = document.createElement('span');
|
||||
const description = document.createElement('span');
|
||||
description.classList.add('sniffle__notification-text');
|
||||
description.innerHTML = notificationText;
|
||||
notification.appendChild(description);
|
|
@ -1,19 +1,19 @@
|
|||
function popUpShow(titleText, subtitleText, bodyContent=null, userActions=null) {
|
||||
// Get popup elements
|
||||
let popupSelector = document.querySelector('.pop-up');
|
||||
let headerSelector = document.querySelector('.pop-up-header');
|
||||
let actionsSelector = document.querySelector('.pop-up-controlls');
|
||||
const popupSelector = document.querySelector('.pop-up');
|
||||
const headerSelector = document.querySelector('.pop-up-header');
|
||||
const actionsSelector = document.querySelector('.pop-up-controlls');
|
||||
|
||||
// Clear popup elements
|
||||
headerSelector.innerHTML = '';
|
||||
actionsSelector.innerHTML = '';
|
||||
|
||||
// Set popup header and subtitle
|
||||
let titleElement = document.createElement('h2');
|
||||
const titleElement = document.createElement('h2');
|
||||
titleElement.innerHTML = titleText;
|
||||
headerSelector.appendChild(titleElement);
|
||||
|
||||
let subtitleElement = document.createElement('p');
|
||||
const subtitleElement = document.createElement('p');
|
||||
subtitleElement.innerHTML = subtitleText;
|
||||
headerSelector.appendChild(subtitleElement);
|
||||
|
||||
|
@ -25,8 +25,7 @@ function popUpShow(titleText, subtitleText, bodyContent=null, userActions=null)
|
|||
if (userActions) {
|
||||
// for each user action, add the element
|
||||
for (let i = 0; i < userActions.length; i++) {
|
||||
let action = userActions[i];
|
||||
actionsSelector.appendChild(action);
|
||||
actionsSelector.appendChild(userActions[i]);
|
||||
}
|
||||
} else {
|
||||
actionsSelector.innerHTML = '<button class="btn-block" onclick="popupDissmiss()">Close</button>';
|
||||
|
@ -39,7 +38,7 @@ function popUpShow(titleText, subtitleText, bodyContent=null, userActions=null)
|
|||
}
|
||||
|
||||
function popupDissmiss() {
|
||||
let popupSelector = document.querySelector('.pop-up');
|
||||
const popupSelector = document.querySelector('.pop-up');
|
||||
|
||||
document.querySelector("html").style.overflow = "auto";
|
||||
popupSelector.classList.remove('active');
|
|
@ -1,5 +1,5 @@
|
|||
function checkWebpSupport() {
|
||||
var webpSupport = false;
|
||||
let webpSupport = false;
|
||||
try {
|
||||
webpSupport = document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0;
|
||||
} catch (e) {
|
|
@ -75,7 +75,6 @@
|
|||
grid-template-columns: 1fr auto
|
||||
grid-template-rows: 1fr auto auto
|
||||
grid-template-areas: 'info info' 'header header' 'subtitle options'
|
||||
gap: 0.5rem
|
||||
|
||||
z-index: +2
|
||||
|
||||
|
@ -89,6 +88,8 @@
|
|||
.banner-header
|
||||
grid-area: header
|
||||
|
||||
margin: 0.5rem 0
|
||||
|
||||
text-align: left
|
||||
font-size: 6.9rem
|
||||
font-weight: 700
|
||||
|
@ -113,7 +114,7 @@
|
|||
background-color: RGB($bg-100)
|
||||
|
||||
.banner-content
|
||||
padding: 0.5rem
|
||||
padding: 0 0.5rem
|
||||
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
@ -124,7 +125,6 @@
|
|||
display: flex
|
||||
flex-direction: row
|
||||
justify-content: flex-start
|
||||
gap: 1rem
|
||||
|
||||
z-index: +2
|
||||
|
||||
|
@ -137,7 +137,7 @@
|
|||
justify-self: flex-start
|
||||
|
||||
.banner-header
|
||||
padding-bottom: 0.25rem
|
||||
margin-right: 0.6rem
|
||||
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
|
@ -149,6 +149,8 @@
|
|||
color: RGB($primary)
|
||||
|
||||
.banner-info
|
||||
margin-right: 0.6rem
|
||||
|
||||
font-size: 0.9rem
|
||||
font-weight: 400
|
||||
|
||||
|
@ -174,9 +176,9 @@
|
|||
flex-direction: column
|
||||
justify-content: center
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
|
||||
.banner-header
|
||||
margin: 1rem 0
|
||||
text-align: center
|
||||
font-size: 2.5rem
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
@mixin btn-block($color)
|
||||
color: RGB($color)
|
||||
box-shadow: 0 1px 0 RGBA($black, 0.2), 0 -1px 0 RGBA($white, 0.2)
|
||||
|
||||
&:hover
|
||||
&:hover, &:focus-visible
|
||||
background-color: RGBA($color, 0.1)
|
||||
color: RGB($color)
|
||||
box-shadow: 0 1px 0 RGBA($black, 0.2), 0 -1px 0 RGBA($color, 0.2)
|
||||
|
||||
&:focus-visible
|
||||
outline: 2px solid RGBA($color, 0.3)
|
||||
|
||||
.btn-block
|
||||
padding: 0.4rem 0.7rem
|
||||
|
@ -25,19 +25,19 @@
|
|||
font-weight: 400
|
||||
text-align: center
|
||||
|
||||
background-color: transparent
|
||||
background-color: RGBA($white, 0.1)
|
||||
color: RGB($white)
|
||||
border: none
|
||||
border-radius: $rad-inner
|
||||
box-shadow: 0 1px 0 RGBA($black, 0.2), 0 -1px 0 RGBA($white, 0.2)
|
||||
outline: none
|
||||
|
||||
cursor: pointer
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out
|
||||
transition: background-color 0.15s ease-in-out, color 0.15s ease-in-out, box-shadow 0.15s ease-in-out
|
||||
|
||||
&:hover
|
||||
background-color: RGBA($white, 0.1)
|
||||
|
||||
&:focus-visible
|
||||
outline: 2px solid RGBA($white, 0.3)
|
||||
&:hover, &:focus-visible
|
||||
background-color: RGBA($white, 0.2)
|
||||
box-shadow: 0 1px 0 RGBA($black, 0.3), 0 -1px 0 RGBA($white, 0.3)
|
||||
|
||||
&.primary
|
||||
@include btn-block($primary)
|
||||
|
@ -91,10 +91,11 @@
|
|||
|
||||
background-color: RGBA($white, 0.1)
|
||||
color: RGB($white)
|
||||
|
||||
border: none
|
||||
border-bottom: 3px solid RGBA($white, 0.1)
|
||||
border-radius: $rad-inner
|
||||
box-shadow: 0 1px 0 RGBA($black, 0.2), 0 -1px 0 RGBA($white, 0.2)
|
||||
outline: none
|
||||
|
||||
cursor: pointer
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out
|
||||
|
@ -107,7 +108,6 @@
|
|||
|
||||
&:focus
|
||||
border-color: RGB($primary)
|
||||
outline: none
|
||||
|
||||
&.black
|
||||
@include btn-block($black)
|
||||
|
@ -132,13 +132,14 @@
|
|||
|
||||
background-color: RGBA($white, 0.1)
|
||||
color: RGB($white)
|
||||
|
||||
border: none
|
||||
border-radius: $rad-inner
|
||||
box-shadow: 0 1px 0 RGBA($black, 0.2), 0 -1px 0 RGBA($white, 0.2)
|
||||
outline: none
|
||||
|
||||
cursor: pointer
|
||||
overflow: hidden
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out, box-shadow 0.15s ease-in-out
|
||||
|
||||
input
|
||||
position: absolute
|
||||
|
@ -153,20 +154,20 @@
|
|||
text-align: center
|
||||
overflow: hidden
|
||||
|
||||
&:hover
|
||||
&:hover, &:focus-visible
|
||||
background-color: RGBA($white, 0.2)
|
||||
color: RGB($white)
|
||||
|
||||
&:focus-visible
|
||||
outline: 2px solid RGBA($white, 0.3)
|
||||
box-shadow: 0 1px 0 RGBA($black, 0.3), 0 -1px 0 RGBA($white, 0.3)
|
||||
|
||||
&.active
|
||||
background-color: RGBA($primary, 0.2)
|
||||
color: RGB($primary)
|
||||
box-shadow: 0 1px 0 RGBA($black, 0.3), 0 -1px 0 RGBA($primary, 0.3)
|
||||
|
||||
&.edging
|
||||
background-color: RGBA($white, 0.2)
|
||||
color: RGB($white)
|
||||
box-shadow: 0 1px 0 RGBA($black, 0.3), 0 -1px 0 RGBA($white, 0.3)
|
||||
|
||||
input
|
||||
display: none // So it doesnt get in the way of the drop as that breaks things
|
||||
|
@ -174,3 +175,4 @@
|
|||
&.error
|
||||
background-color: RGBA($critical, 0.2)
|
||||
color: RGB($critical)
|
||||
box-shadow: 0 1px 0 RGBA($black, 0.3), 0 -1px 0 RGBA($critical, 0.3)
|
||||
|
|
|
@ -18,10 +18,11 @@
|
|||
|
||||
background-color: RGB($bg-200)
|
||||
border-radius: $rad
|
||||
box-shadow: 0 1px 0 RGB($bg-100), 0 -1px 0 RGB($bg-300)
|
||||
|
||||
.pill-text
|
||||
margin: 0
|
||||
padding: 0.5rem
|
||||
padding: 0.5rem 1rem
|
||||
|
||||
width: auto
|
||||
height: 2.5rem
|
||||
|
@ -36,7 +37,9 @@
|
|||
font-size: 1rem
|
||||
font-weight: 400
|
||||
|
||||
background-color: RGB($bg-200)
|
||||
color: RGB($fg-white)
|
||||
border-radius: $rad
|
||||
|
||||
.pill-item
|
||||
margin: 0
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
.gallery-grid
|
||||
margin: 0
|
||||
padding: 0.65rem
|
||||
padding: 0.35rem
|
||||
|
||||
width: 100%
|
||||
|
||||
display: grid
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
|
||||
gap: 0.65rem
|
||||
|
||||
.gallery-item
|
||||
margin: 0
|
||||
margin: 0.35rem
|
||||
padding: 0
|
||||
|
||||
height: auto
|
||||
|
@ -97,7 +96,7 @@
|
|||
opacity: 1
|
||||
|
||||
.group-item
|
||||
margin: 0
|
||||
margin: 0.35rem
|
||||
padding: 0
|
||||
|
||||
height: auto
|
||||
|
@ -161,8 +160,6 @@
|
|||
|
||||
display: block
|
||||
|
||||
background-color: RGB($bg-bright)
|
||||
|
||||
img
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
background-color: RGB($fg-white)
|
||||
|
||||
filter: blur(1rem)
|
||||
filter: blur(1rem) saturate(1.2)
|
||||
transform: scale(1.1)
|
||||
|
||||
object-fit: cover
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
.image-fullscreen
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
width: 100%
|
||||
height: 100%
|
||||
height: 100dvh
|
||||
|
||||
position: fixed
|
||||
top: 0
|
||||
left: 0
|
||||
|
||||
display: none
|
||||
opacity: 0 // hide
|
||||
|
||||
background-color: $bg-transparent
|
||||
z-index: 100
|
||||
|
||||
box-sizing: border-box
|
||||
|
||||
transition: opacity 0.2s cubic-bezier(.79, .14, .15, .86)
|
||||
|
||||
img
|
||||
margin: auto
|
||||
padding: 0
|
||||
|
||||
width: auto
|
||||
height: auto
|
||||
max-width: 100%
|
||||
max-height: 100%
|
||||
|
||||
object-fit: contain
|
||||
object-position: center
|
||||
|
||||
transform: scale(0.8)
|
||||
transition: transform 0.2s cubic-bezier(.68,-0.55,.27,1.55)
|
||||
|
||||
&.active
|
||||
opacity: 1 // show
|
||||
|
||||
img
|
||||
transform: scale(1)
|
|
@ -1,5 +1,5 @@
|
|||
.info-container
|
||||
width: 25rem
|
||||
width: 27rem
|
||||
height: 100vh
|
||||
|
||||
position: absolute
|
||||
|
@ -17,7 +17,7 @@
|
|||
transition: left 0.3s cubic-bezier(0.76, 0, 0.17, 1)
|
||||
|
||||
&.collapsed
|
||||
left: -25rem
|
||||
left: -27rem
|
||||
|
||||
.info-tab
|
||||
width: 100%
|
||||
|
@ -39,8 +39,8 @@
|
|||
transform: rotate(90deg)
|
||||
|
||||
.info-table
|
||||
height: 0
|
||||
padding: 0
|
||||
opacity: 0
|
||||
|
||||
.collapse-indicator
|
||||
margin: 0
|
||||
|
@ -126,11 +126,6 @@
|
|||
.link
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
font-size: 1rem
|
||||
font-weight: 500
|
||||
text-align: center
|
||||
line-height: 1
|
||||
|
||||
color: RGB($primary)
|
||||
|
||||
|
@ -150,43 +145,31 @@
|
|||
border-collapse: collapse
|
||||
|
||||
tr
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
width: 100%
|
||||
|
||||
white-space: nowrap
|
||||
|
||||
td
|
||||
padding-bottom: 0.5rem
|
||||
|
||||
max-width: 0
|
||||
|
||||
font-size: 1rem
|
||||
font-weight: 400
|
||||
|
||||
vertical-align: top
|
||||
|
||||
td:first-child
|
||||
padding-right: 0.5rem
|
||||
|
||||
width: 50%
|
||||
max-width: 0
|
||||
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
font-size: 1rem
|
||||
font-weight: 400
|
||||
td:last-child
|
||||
padding: 0
|
||||
|
||||
width: 50%
|
||||
max-width: 0
|
||||
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
font-size: 1rem
|
||||
font-weight: 400
|
||||
|
||||
td.empty-table
|
||||
opacity: 0.3
|
||||
white-space: normal
|
||||
word-break: break-word
|
||||
|
||||
tr:last-of-type td
|
||||
padding-bottom: 0
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
@import 'background'
|
||||
@import 'fullscreen'
|
||||
@import 'info-tab'
|
||||
@import 'image'
|
||||
|
||||
|
@ -18,10 +17,10 @@
|
|||
z-index: 3
|
||||
|
||||
.image-block
|
||||
margin: 0 0 0 25rem
|
||||
margin: 0 0 0 27rem
|
||||
padding: 0
|
||||
|
||||
width: calc(100% - 25rem)
|
||||
width: calc(100% - 27rem)
|
||||
height: 100vh
|
||||
|
||||
position: relative
|
||||
|
@ -79,4 +78,4 @@
|
|||
.info-tab.collapsed .info-header
|
||||
border-radius: $rad
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
&::after
|
||||
content: ""
|
||||
|
||||
width: 450px
|
||||
width: 100%
|
||||
height: 3px
|
||||
|
||||
position: absolute
|
|
@ -94,10 +94,6 @@
|
|||
vertical-align: middle
|
||||
|
||||
a, .link
|
||||
font-size: 1rem
|
||||
font-weight: 500
|
||||
line-height: 1
|
||||
|
||||
color: RGB($primary)
|
||||
|
||||
cursor: pointer
|
|
@ -26,7 +26,7 @@
|
|||
padding: 0
|
||||
|
||||
font-size: 1rem
|
||||
font-weight: 500
|
||||
font-weight: 400
|
||||
|
||||
form
|
||||
margin: 0
|
||||
|
@ -57,9 +57,9 @@
|
|||
|
||||
position: absolute
|
||||
bottom: 0
|
||||
left: -25rem
|
||||
left: -27rem
|
||||
|
||||
width: 25rem
|
||||
width: 27rem
|
||||
height: 100%
|
||||
|
||||
display: flex
|
||||
|
@ -67,11 +67,10 @@
|
|||
gap: 1rem
|
||||
|
||||
background-color: RGB($bg-200)
|
||||
opacity: 0
|
||||
|
||||
z-index: +2
|
||||
|
||||
transition: left 0.25s cubic-bezier(0.76, 0, 0.17, 1), bottom 0.25s cubic-bezier(0.76, 0, 0.17, 1), opacity 0.25s cubic-bezier(0.76, 0, 0.17, 1)
|
||||
transition: left 0.25s cubic-bezier(0.76, 0, 0.17, 1), bottom 0.25s cubic-bezier(0.76, 0, 0.17, 1)
|
||||
|
||||
#dragIndicator
|
||||
display: none
|
||||
|
@ -197,7 +196,6 @@
|
|||
|
||||
.container
|
||||
left: 0
|
||||
opacity: 1
|
||||
|
||||
@media (max-width: $breakpoint)
|
||||
.upload-panel
|
|
@ -4,10 +4,10 @@
|
|||
@import "variables"
|
||||
@import "animations"
|
||||
|
||||
@import "components/elements/notification"
|
||||
@import "components/elements/pop-up"
|
||||
@import "components/elements/upload-panel"
|
||||
@import "components/elements/tags"
|
||||
@import "components/notification"
|
||||
@import "components/pop-up"
|
||||
@import "components/upload-panel"
|
||||
@import "components/tags"
|
||||
|
||||
@import "components/navigation"
|
||||
@import "components/banner"
|
||||
|
|
|
@ -74,13 +74,3 @@ $breakpoint: 800px
|
|||
|
||||
--breakpoint: 800px
|
||||
|
||||
// I have no clue if its webassets or libsass thats doing this shit
|
||||
// But one of them is trying to "correct" the path, and 404-ing the
|
||||
// font, so enjoy this path fuckery
|
||||
|
||||
@font-face
|
||||
font-family: 'Rubik'
|
||||
src: url('../../../../static/fonts/Rubik.ttf') format('truetype')
|
||||
font-style: normal
|
||||
font-display: swap
|
||||
font-weight: 300 900
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
{% if current_user.id == group.author_id %}
|
||||
{% if current_user.id == group.author.id %}
|
||||
function groupDelete() {
|
||||
cancelBtn = document.createElement('button');
|
||||
cancelBtn.classList.add('btn-block');
|
||||
|
@ -223,7 +223,7 @@
|
|||
<img src="{{ url_for('api.file', file_name=images.0.filename ) }}?r=prev" onload="imgFade(this)" style="opacity:0;" alt="{% if images.0.alt %}{{ images.0.alt }}{% else %}Group Banner{% endif %}"/>
|
||||
<span class="banner-filter"></span>
|
||||
<div class="banner-content">
|
||||
<p class="banner-info"><a href="{{ url_for('profile.profile', id=group.author_id) }}" class="link">By {{ group.author_username }}</a></p>
|
||||
<p class="banner-info"><a href="{{ url_for('profile.profile', id=group.author.id) }}" class="link">By {{ group.author.username }}</a></p>
|
||||
<h1 class="banner-header">{{ group.name }}</h1>
|
||||
<p class="banner-subtitle">{{ images|length }} Images · {{ group.description }}</p>
|
||||
<div class="pill-row">
|
||||
|
@ -232,7 +232,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,112v96a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V112A16,16,0,0,1,56,96H80a8,8,0,0,1,0,16H56v96H200V112H176a8,8,0,0,1,0-16h24A16,16,0,0,1,216,112ZM93.66,69.66,120,43.31V136a8,8,0,0,0,16,0V43.31l26.34,26.35a8,8,0,0,0,11.32-11.32l-40-40a8,8,0,0,0-11.32,0l-40,40A8,8,0,0,0,93.66,69.66Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
{% if current_user.id == group.author_id %}
|
||||
{% if current_user.id == group.author.id %}
|
||||
<div>
|
||||
<button class="pill-item pill__critical" onclick="groupDelete()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
||||
|
@ -249,14 +249,14 @@
|
|||
<div class="banner-small">
|
||||
<div class="banner-content">
|
||||
<h1 class="banner-header">{{ group.name }}</h1>
|
||||
<p class="banner-info">By {{ group.author_username }}</p>
|
||||
<p class="banner-info">By {{ group.author.username }}</p>
|
||||
<div class="pill-row">
|
||||
<div>
|
||||
<button class="pill-item" onclick="groupShare()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,112v96a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V112A16,16,0,0,1,56,96H80a8,8,0,0,1,0,16H56v96H200V112H176a8,8,0,0,1,0-16h24A16,16,0,0,1,216,112ZM93.66,69.66,120,43.31V136a8,8,0,0,0,16,0V43.31l26.34,26.35a8,8,0,0,0,11.32-11.32l-40-40a8,8,0,0,0-11.32,0l-40,40A8,8,0,0,0,93.66,69.66Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
{% if current_user.id == group.author_id %}
|
||||
{% if current_user.id == group.author.id %}
|
||||
<div>
|
||||
<button class="pill-item pill__critical" onclick="groupDelete()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
{% if current_user.id == image.author_id %}
|
||||
{% if current_user.id == image.author.id %}
|
||||
function imageDelete() {
|
||||
cancelBtn = document.createElement('button');
|
||||
cancelBtn.classList.add('btn-block');
|
||||
|
@ -85,10 +85,6 @@
|
|||
<span></span>
|
||||
</div>
|
||||
|
||||
<div class="image-fullscreen" onclick="imageFullscreenOff()">
|
||||
<img src="" alt="{{ image.alt }}"/>
|
||||
</div>
|
||||
|
||||
<div class="image-grid">
|
||||
<div class="image-block">
|
||||
<div class="image-container">
|
||||
|
@ -124,7 +120,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Zm-42.34-61.66a8,8,0,0,1,0,11.32l-24,24a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L120,164.69V120a8,8,0,0,1,16,0v44.69l10.34-10.35A8,8,0,0,1,157.66,154.34Z"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
{% if current_user.id == image.author_id %}
|
||||
{% if current_user.id == image.author.id %}
|
||||
<div>
|
||||
<button class="pill-item pill__critical" onclick="imageDelete()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
||||
|
@ -145,22 +141,6 @@
|
|||
</div>
|
||||
|
||||
<div class="info-container">
|
||||
{% if image.post_description %}
|
||||
<div class="info-tab">
|
||||
<div class="info-header">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,160H40V56H216V200ZM184,96a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,96Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,128Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,160Z"></path></svg>
|
||||
<h2>Description</h2>
|
||||
<button class="collapse-indicator">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -8 24 24" fill="currentColor">
|
||||
<path d="M7.071 5.314l4.95-4.95a1 1 0 1 1 1.414 1.414L7.778 7.435a1 1 0 0 1-1.414 0L.707 1.778A1 1 0 1 1 2.121.364l4.95 4.95z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="info-table">
|
||||
<p>{{ image.post_description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="info-tab">
|
||||
<div class="info-header">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"></path></svg>
|
||||
|
@ -175,12 +155,18 @@
|
|||
<table>
|
||||
<tr>
|
||||
<td>Author</td>
|
||||
<td><a href="{{ url_for('profile.profile', id=image.author_id) }}" class="link">{{ image.author_username }}</a></td>
|
||||
<td><a href="{{ url_for('profile.profile', id=image.author.id) }}" class="link">{{ image.author.username }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload date</td>
|
||||
<td><span class="time">{{ image.created_at }}</span></td>
|
||||
</tr>
|
||||
{% if image.description %}
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>{{ image.description }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
<div class="img-colours">
|
||||
{% for col in image.colours %}
|
||||
|
|
|
@ -30,11 +30,12 @@
|
|||
type="image/svg+xml"
|
||||
media="(prefers-color-scheme: dark)"/>
|
||||
|
||||
{% assets "lib" %}
|
||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}
|
||||
<link
|
||||
rel="prefetch"
|
||||
href="{{url_for('static', filename='fonts/font.css')}}"
|
||||
type="stylesheet"/>
|
||||
|
||||
{% assets "js" %}
|
||||
{% assets "scripts" %}
|
||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}
|
||||
|
||||
|
@ -69,7 +70,7 @@
|
|||
<div class="navigation">
|
||||
<!--<img src="{{url_for('static', filename='icon.png')}}" alt="Logo" class="logo" onload="this.style.opacity=1;" style="opacity:0">-->
|
||||
|
||||
<a href="{{url_for('gallery.index')}}{% block page_index %}{% endblock %}" class="navigation-item {% block nav_home %}{% endblock %}">
|
||||
<a href="{{ url_for('gallery.index') }}{% block page_index %}{% endblock %}" class="navigation-item {% block nav_home %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M208,32H80A16,16,0,0,0,64,48V64H48A16,16,0,0,0,32,80V208a16,16,0,0,0,16,16H176a16,16,0,0,0,16-16V192h16a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM80,48H208v69.38l-16.7-16.7a16,16,0,0,0-22.62,0L93.37,176H80Zm96,160H48V80H64v96a16,16,0,0,0,16,16h96ZM104,88a16,16,0,1,1,16,16A16,16,0,0,1,104,88Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Home
|
||||
|
@ -77,7 +78,7 @@
|
|||
</span>
|
||||
</a>
|
||||
|
||||
<a href="{{url_for('group.groups')}}" class="navigation-item {% block nav_groups %}{% endblock %}">
|
||||
<a href="{{ url_for('group.groups') }}" class="navigation-item {% block nav_groups %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M245,110.64A16,16,0,0,0,232,104H216V88a16,16,0,0,0-16-16H130.67L102.94,51.2a16.14,16.14,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V208h0a8,8,0,0,0,8,8H211.1a8,8,0,0,0,7.59-5.47l28.49-85.47A16.05,16.05,0,0,0,245,110.64ZM93.34,64l27.73,20.8a16.12,16.12,0,0,0,9.6,3.2H200v16H146.43a16,16,0,0,0-8.88,2.69l-20,13.31H69.42a15.94,15.94,0,0,0-14.86,10.06L40,166.46V64Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Groups
|
||||
|
@ -86,66 +87,66 @@
|
|||
</a>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<button class="navigation-item {% block nav_upload %}{% endblock %}" onclick="toggleUploadTab()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M74.34,77.66a8,8,0,0,1,0-11.32l48-48a8,8,0,0,1,11.32,0l48,48a8,8,0,0,1-11.32,11.32L136,43.31V128a8,8,0,0,1-16,0V43.31L85.66,77.66A8,8,0,0,1,74.34,77.66ZM240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16h68a4,4,0,0,1,4,4v3.46c0,13.45,11,24.79,24.46,24.54A24,24,0,0,0,152,128v-4a4,4,0,0,1,4-4h68A16,16,0,0,1,240,136Zm-40,32a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Upload
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
<button class="navigation-item {% block nav_upload %}{% endblock %}" onclick="toggleUploadTab()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M74.34,77.66a8,8,0,0,1,0-11.32l48-48a8,8,0,0,1,11.32,0l48,48a8,8,0,0,1-11.32,11.32L136,43.31V128a8,8,0,0,1-16,0V43.31L85.66,77.66A8,8,0,0,1,74.34,77.66ZM240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16h68a4,4,0,0,1,4,4v3.46c0,13.45,11,24.79,24.46,24.54A24,24,0,0,0,152,128v-4a4,4,0,0,1,4-4h68A16,16,0,0,1,240,136Zm-40,32a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Upload
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<span class="navigation-spacer"></span>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{url_for('profile.profile')}}" class="navigation-item {% block nav_profile %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M231.73,221.94A8,8,0,0,1,224,232H160A8,8,0,0,1,152.27,222a40,40,0,0,1,17.11-23.33,32,32,0,1,1,45.24,0A40,40,0,0,1,231.73,221.94ZM216,72H130.67L102.93,51.2a16.12,16.12,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V200a16,16,0,0,0,16,16h80a8,8,0,0,0,0-16H40V64H93.33l27.74,20.8a16.12,16.12,0,0,0,9.6,3.2H216v32a8,8,0,0,0,16,0V88A16,16,0,0,0,216,72Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Profile
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a href="{{url_for('settings.general')}}" class="navigation-item {% block nav_settings %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,130.16q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.6,107.6,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.29,107.29,0,0,0-26.25-10.86,8,8,0,0,0-7.06,1.48L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.6,107.6,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06ZM128,168a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Settings
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</a>
|
||||
<a href="{{ url_for('profile.profile') }}" class="navigation-item {% block nav_profile %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M231.73,221.94A8,8,0,0,1,224,232H160A8,8,0,0,1,152.27,222a40,40,0,0,1,17.11-23.33,32,32,0,1,1,45.24,0A40,40,0,0,1,231.73,221.94ZM216,72H130.67L102.93,51.2a16.12,16.12,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V200a16,16,0,0,0,16,16h80a8,8,0,0,0,0-16H40V64H93.33l27.74,20.8a16.12,16.12,0,0,0,9.6,3.2H216v32a8,8,0,0,0,16,0V88A16,16,0,0,0,216,72Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Profile
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('settings.general') }}" class="navigation-item {% block nav_settings %}{% endblock %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M216,130.16q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.6,107.6,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.29,107.29,0,0,0-26.25-10.86,8,8,0,0,0-7.06,1.48L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.6,107.6,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06ZM128,168a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Settings
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="navigation-item {% block nav_login %}{% endblock %}" onclick="showLogin()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M141.66,133.66l-40,40A8,8,0,0,1,88,168V136H24a8,8,0,0,1,0-16H88V88a8,8,0,0,1,13.66-5.66l40,40A8,8,0,0,1,141.66,133.66ZM192,32H136a8,8,0,0,0,0,16h56V208H136a8,8,0,0,0,0,16h56a16,16,0,0,0,16-16V48A16,16,0,0,0,192,32Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Login
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
<button class="navigation-item {% block nav_login %}{% endblock %}" onclick="showLogin()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M141.66,133.66l-40,40A8,8,0,0,1,88,168V136H24a8,8,0,0,1,0-16H88V88a8,8,0,0,1,13.66-5.66l40,40A8,8,0,0,1,141.66,133.66ZM192,32H136a8,8,0,0,0,0,16h56V208H136a8,8,0,0,0,0,16h56a16,16,0,0,0,16-16V48A16,16,0,0,0,192,32Z"></path></svg>
|
||||
<span class="tool-tip">
|
||||
Login
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M168,48V208a8,8,0,0,1-13.66,5.66l-80-80a8,8,0,0,1,0-11.32l80-80A8,8,0,0,1,168,48Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="upload-panel">
|
||||
<span class="click-off" onclick="closeUploadTab()"></span>
|
||||
<div class="container">
|
||||
<span id="dragIndicator"></span>
|
||||
<h3>Upload stuffs</h3>
|
||||
<p>May the world see your stuff 👀</p>
|
||||
<form id="uploadForm">
|
||||
<button class="fileDrop-block" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H80a8,8,0,0,1,0,16H32v64H224V136H176a8,8,0,0,1,0-16h48A16,16,0,0,1,240,136ZM85.66,77.66,120,43.31V128a8,8,0,0,0,16,0V43.31l34.34,34.35a8,8,0,0,0,11.32-11.32l-48-48a8,8,0,0,0-11.32,0l-48,48A8,8,0,0,0,85.66,77.66ZM200,168a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
||||
<span class="status">Choose or Drop file</span>
|
||||
<input type="file" id="file" tab-index="-1"/>
|
||||
</button>
|
||||
<div class="upload-panel">
|
||||
<span class="click-off" onclick="closeUploadTab()"></span>
|
||||
<div class="container">
|
||||
<span id="dragIndicator"></span>
|
||||
<h3>Upload stuffs</h3>
|
||||
<p>May the world see your stuff 👀</p>
|
||||
<form id="uploadForm">
|
||||
<button class="fileDrop-block" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M240,136v64a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V136a16,16,0,0,1,16-16H80a8,8,0,0,1,0,16H32v64H224V136H176a8,8,0,0,1,0-16h48A16,16,0,0,1,240,136ZM85.66,77.66,120,43.31V128a8,8,0,0,0,16,0V43.31l34.34,34.35a8,8,0,0,0,11.32-11.32l-48-48a8,8,0,0,0-11.32,0l-48,48A8,8,0,0,0,85.66,77.66ZM200,168a12,12,0,1,0-12,12A12,12,0,0,0,200,168Z"></path></svg>
|
||||
<span class="status">Choose or Drop file</span>
|
||||
<input type="file" id="file" tab-index="-1"/>
|
||||
</button>
|
||||
|
||||
<input class="input-block" type="text" placeholder="alt" id="alt"/>
|
||||
<input class="input-block" type="text" placeholder="description" id="description"/>
|
||||
<input class="input-block" type="text" placeholder="tags" id="tags"/>
|
||||
<button class="btn-block" type="submit">Upload</button>
|
||||
</form>
|
||||
<div class="upload-jobs"></div>
|
||||
<input class="input-block" type="text" placeholder="alt" id="alt"/>
|
||||
<input class="input-block" type="text" placeholder="description" id="description"/>
|
||||
<input class="input-block" type="text" placeholder="tags" id="tags"/>
|
||||
<button class="btn-block primary" type="submit">Upload</button>
|
||||
</form>
|
||||
<div class="upload-jobs"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="content">
|
||||
|
|
|
@ -119,9 +119,9 @@
|
|||
{% if groups %}
|
||||
<div class="gallery-grid">
|
||||
{% for group in groups %}
|
||||
<a id="group-{{ group.id }}" class="group-item" href="{{ url_for('group.group', group_id=group.id) }}">
|
||||
<a id="group-{{ group.id }}" class="group-item" href="{{ url_for('group.group', group_id=group.id) }}" {% if group.images|length > 0 %} style="background-color: rgba({{ group.images.0.colours.0.0 }}, {{ group.images.0.colours.0.1 }}, {{ group.images.0.colours.0.2 }}, 0.4);" {% endif %}>
|
||||
<div class="image-filter">
|
||||
<p class="image-subtitle">By {{ group.author_username }}</p>
|
||||
<p class="image-subtitle">By {{ group.author.username }}</p>
|
||||
<p class="image-title">{{ group.name }}</p>
|
||||
</div>
|
||||
<div class="images size-{{ group.images|length }}">
|
||||
|
|
|
@ -77,10 +77,9 @@ class Metadata:
|
|||
"raw": value,
|
||||
"formatted": (
|
||||
getattr(
|
||||
helpers, mapping_val[key][1] # pylint: disable=E0602
|
||||
)(
|
||||
value
|
||||
)
|
||||
helpers, # pylint: disable=E0602
|
||||
mapping_val[key][1],
|
||||
)(value)
|
||||
),
|
||||
}
|
||||
else:
|
||||
|
|
|
@ -42,7 +42,7 @@ def shutter(value):
|
|||
"""
|
||||
Formats the shutter speed into a standard format
|
||||
"""
|
||||
return str(value) + "s"
|
||||
return str(value) + " s"
|
||||
|
||||
|
||||
def focal_length(value):
|
||||
|
@ -50,16 +50,18 @@ def focal_length(value):
|
|||
Formats the focal length into a standard format
|
||||
"""
|
||||
try:
|
||||
return str(value[0] / value[1]) + "mm"
|
||||
calculated = value[0] / value[1]
|
||||
except TypeError:
|
||||
return str(value) + "mm"
|
||||
calculated = value
|
||||
|
||||
return str(calculated) + " mm"
|
||||
|
||||
|
||||
def exposure(value):
|
||||
"""
|
||||
Formats the exposure value into a standard format
|
||||
"""
|
||||
return str(value) + "EV"
|
||||
return str(value) + " EV"
|
||||
|
||||
|
||||
def color_space(value):
|
||||
|
@ -78,28 +80,28 @@ def flash(value):
|
|||
Maps the value of the flash to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: "Flash did not fire",
|
||||
1: "Flash fired",
|
||||
0: "Did not fire",
|
||||
1: "Fired",
|
||||
5: "Strobe return light not detected",
|
||||
7: "Strobe return light detected",
|
||||
9: "Flash fired, compulsory flash mode",
|
||||
13: "Flash fired, compulsory flash mode, return light not detected",
|
||||
15: "Flash fired, compulsory flash mode, return light detected",
|
||||
16: "Flash did not fire, compulsory flash mode",
|
||||
24: "Flash did not fire, auto mode",
|
||||
25: "Flash fired, auto mode",
|
||||
29: "Flash fired, auto mode, return light not detected",
|
||||
31: "Flash fired, auto mode, return light detected",
|
||||
32: "No flash function",
|
||||
65: "Flash fired, red-eye reduction mode",
|
||||
69: "Flash fired, red-eye reduction mode, return light not detected",
|
||||
71: "Flash fired, red-eye reduction mode, return light detected",
|
||||
73: "Flash fired, compulsory flash mode, red-eye reduction mode",
|
||||
77: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
|
||||
79: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
|
||||
89: "Flash fired, auto mode, red-eye reduction mode",
|
||||
93: "Flash fired, auto mode, return light not detected, red-eye reduction mode",
|
||||
95: "Flash fired, auto mode, return light detected, red-eye reduction mode",
|
||||
9: "Fired, compulsory",
|
||||
13: "Fired, compulsory, return light not detected",
|
||||
15: "Fired, compulsory, return light detected",
|
||||
16: "Did not fire, compulsory",
|
||||
24: "Did not fire, auto mode",
|
||||
25: "Fired, auto mode",
|
||||
29: "Fired, auto mode, return light not detected",
|
||||
31: "Fired, auto mode, return light detected",
|
||||
32: "No function",
|
||||
65: "Fired, red-eye reduction mode",
|
||||
69: "Fired, red-eye reduction mode, return light not detected",
|
||||
71: "Fired, red-eye reduction mode, return light detected",
|
||||
73: "Fired, compulsory, red-eye reduction mode",
|
||||
77: "Fired, compulsory, red-eye reduction mode, return light not detected",
|
||||
79: "Fired, compulsory, red-eye reduction mode, return light detected",
|
||||
89: "Fired, auto mode, red-eye reduction mode",
|
||||
93: "Fired, auto mode, return light not detected, red-eye reduction mode",
|
||||
95: "Fired, auto mode, return light detected, red-eye reduction mode",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -213,8 +215,8 @@ def white_balance(value):
|
|||
Maps the value of the white balance to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: "Auto white balance",
|
||||
1: "Manual white balance",
|
||||
0: "Auto",
|
||||
1: "Manual",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -227,8 +229,8 @@ def exposure_mode(value):
|
|||
Maps the value of the exposure mode to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: "Auto exposure",
|
||||
1: "Manual exposure",
|
||||
0: "Auto",
|
||||
1: "Manual",
|
||||
2: "Auto bracket",
|
||||
}
|
||||
try:
|
||||
|
@ -386,4 +388,34 @@ def pixel_dimension(value):
|
|||
"""
|
||||
Maps the value of the pixel dimension to a human readable format
|
||||
"""
|
||||
return str(value) + "px"
|
||||
return str(value) + " px"
|
||||
|
||||
|
||||
def title(value):
|
||||
"""
|
||||
Maps the value of the title to a human readable format
|
||||
"""
|
||||
return str(value.title())
|
||||
|
||||
|
||||
def subject_distance(value):
|
||||
"""
|
||||
Maps the value of the subject distance to a human readable format
|
||||
"""
|
||||
return str(value) + " m"
|
||||
|
||||
|
||||
def subject_distance_range(value):
|
||||
"""
|
||||
Maps the value of the subject distance range to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: "Unknown",
|
||||
1: "Macro",
|
||||
2: "Close view",
|
||||
3: "Distant view",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
except KeyError:
|
||||
return None
|
||||
|
|
|
@ -10,22 +10,22 @@ PHOTOGRAHER_MAPPING = {
|
|||
"Copyright": ["Copyright"],
|
||||
}
|
||||
CAMERA_MAPPING = {
|
||||
"Model": ["Model"],
|
||||
"Make": ["Make"],
|
||||
"Model": ["Model", "title"],
|
||||
"Make": ["Manifacturer", "title"],
|
||||
"BodySerialNumber": ["Camera Type"],
|
||||
"LensMake": ["Lens Make"],
|
||||
"LenseModel": ["Lens Model"],
|
||||
"LensMake": ["Lens Make", "title"],
|
||||
"LensModel": ["Lens Model", "title"],
|
||||
"LensSpecification": ["Lens Specification", "lens_specification"],
|
||||
"ComponentsConfiguration": ["Components Configuration", "components_configuration"],
|
||||
"DateTime": ["Date Processed", "date_format"],
|
||||
"DateTimeDigitized": ["Time Digitized", "date_format"],
|
||||
"DateTime": ["Date and Time", "date_format"],
|
||||
"DateTimeOriginal": ["Date and Time (Original)", "date_format"],
|
||||
"DateTimeDigitized": ["Date and Time (Digitized)", "date_format"],
|
||||
"OffsetTime": ["Time Offset"],
|
||||
"OffsetTimeOriginal": ["Time Offset - Original"],
|
||||
"OffsetTimeDigitized": ["Time Offset - Digitized"],
|
||||
"DateTimeOriginal": ["Date Original", "date_format"],
|
||||
"FNumber": ["F-Stop", "fnumber"],
|
||||
"OffsetTimeOriginal": ["Time Offset (Original)"],
|
||||
"OffsetTimeDigitized": ["Time Offset (Digitized)"],
|
||||
"FNumber": ["FNumber", "fnumber"],
|
||||
"FocalLength": ["Focal Length", "focal_length"],
|
||||
"FocalLengthIn35mmFilm": ["Focal Length (35mm format)", "focal_length"],
|
||||
"FocalLengthIn35mmFilm": ["Focal Length in 35mm format", "focal_length"],
|
||||
"MaxApertureValue": ["Max Aperture", "fnumber"],
|
||||
"ApertureValue": ["Aperture", "fnumber"],
|
||||
"ShutterSpeedValue": ["Shutter Speed", "shutter"],
|
||||
|
@ -41,6 +41,8 @@ CAMERA_MAPPING = {
|
|||
"MeteringMode": ["Metering Mode", "metering_mode"],
|
||||
"LightSource": ["Light Source", "light_source"],
|
||||
"SceneCaptureType": ["Scene Capture Type", "scene_capture_type"],
|
||||
"SubjectDistance": ["Subject Distance", "subject_distance"],
|
||||
"SubjectDistanceRange": ["Subject Distance Range", "subject_distance_range"],
|
||||
}
|
||||
SOFTWARE_MAPPING = {
|
||||
"Software": ["Software"],
|
||||
|
|
|
@ -3,16 +3,14 @@ Onlylegs - Image Groups
|
|||
Why groups? Because I don't like calling these albums
|
||||
sounds more limiting that it actually is in this gallery
|
||||
"""
|
||||
from flask import Blueprint, abort, render_template, url_for
|
||||
from flask import Blueprint, render_template, url_for
|
||||
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from gallery import db
|
||||
from gallery.models import Post, User, GroupJunction, Group
|
||||
from gallery.extensions import db
|
||||
from gallery.utils import contrast
|
||||
|
||||
|
||||
blueprint = Blueprint("group", __name__, url_prefix="/group")
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route("/", methods=["GET"])
|
||||
|
@ -20,21 +18,21 @@ def groups():
|
|||
"""
|
||||
Group overview, shows all image groups
|
||||
"""
|
||||
groups = db_session.query(db.Groups).all()
|
||||
groups = Group.query.all()
|
||||
|
||||
# For each group, get the 3 most recent images
|
||||
for group in groups:
|
||||
group.author_username = (
|
||||
db_session.query(db.Users.username)
|
||||
.filter(db.Users.id == group.author_id)
|
||||
User.query.with_entities(User.username)
|
||||
.filter(User.id == group.author_id)
|
||||
.first()[0]
|
||||
)
|
||||
|
||||
# Get the 3 most recent images
|
||||
images = (
|
||||
db_session.query(db.GroupJunction.post_id)
|
||||
.filter(db.GroupJunction.group_id == group.id)
|
||||
.order_by(db.GroupJunction.date_added.desc())
|
||||
GroupJunction.query.with_entities(GroupJunction.post_id)
|
||||
.filter(GroupJunction.group_id == group.id)
|
||||
.order_by(GroupJunction.date_added.desc())
|
||||
.limit(3)
|
||||
)
|
||||
|
||||
|
@ -42,10 +40,8 @@ def groups():
|
|||
group.images = []
|
||||
for image in images:
|
||||
group.images.append(
|
||||
db_session.query(
|
||||
db.Posts.filename, db.Posts.alt, db.Posts.colours, db.Posts.id
|
||||
)
|
||||
.filter(db.Posts.id == image[0])
|
||||
Post.query.with_entities(Post.filename, Post.alt, Post.colours, Post.id)
|
||||
.filter(Post.id == image[0])
|
||||
.first()
|
||||
)
|
||||
|
||||
|
@ -58,32 +54,20 @@ def group(group_id):
|
|||
Group view, shows all images in a group
|
||||
"""
|
||||
# Get the group, if it doesn't exist, 404
|
||||
group = db_session.query(db.Groups).filter(db.Groups.id == group_id).first()
|
||||
|
||||
if group is None:
|
||||
abort(404, "Group not found! D:")
|
||||
|
||||
# Get the group's author username
|
||||
group.author_username = (
|
||||
db_session.query(db.Users.username)
|
||||
.filter(db.Users.id == group.author_id)
|
||||
.first()[0]
|
||||
)
|
||||
group = db.get_or_404(Group, group_id, description="Group not found! D:")
|
||||
|
||||
# Get all images in the group from the junction table
|
||||
junction = (
|
||||
db_session.query(db.GroupJunction.post_id)
|
||||
.filter(db.GroupJunction.group_id == group_id)
|
||||
.order_by(db.GroupJunction.date_added.desc())
|
||||
GroupJunction.query.with_entities(GroupJunction.post_id)
|
||||
.filter(GroupJunction.group_id == group_id)
|
||||
.order_by(GroupJunction.date_added.desc())
|
||||
.all()
|
||||
)
|
||||
|
||||
# Get the image data for each image in the group
|
||||
images = []
|
||||
for image in junction:
|
||||
images.append(
|
||||
db_session.query(db.Posts).filter(db.Posts.id == image[0]).first()
|
||||
)
|
||||
images.append(Post.query.filter(Post.id == image[0]).first())
|
||||
|
||||
# Check contrast for the first image in the group for the banner
|
||||
text_colour = "rgb(var(--fg-black))"
|
||||
|
@ -103,21 +87,12 @@ def group_post(group_id, image_id):
|
|||
Image view, shows the image and its metadata from a specific group
|
||||
"""
|
||||
# Get the image, if it doesn't exist, 404
|
||||
image = db_session.query(db.Posts).filter(db.Posts.id == image_id).first()
|
||||
if image is None:
|
||||
abort(404, "Image not found")
|
||||
|
||||
# Get the image's author username
|
||||
image.author_username = (
|
||||
db_session.query(db.Users.username)
|
||||
.filter(db.Users.id == image.author_id)
|
||||
.first()[0]
|
||||
)
|
||||
image = db.get_or_404(Post, image_id, description="Image not found :<")
|
||||
|
||||
# Get all groups the image is in
|
||||
groups = (
|
||||
db_session.query(db.GroupJunction.group_id)
|
||||
.filter(db.GroupJunction.post_id == image_id)
|
||||
GroupJunction.query.with_entities(GroupJunction.group_id)
|
||||
.filter(GroupJunction.post_id == image_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
|
@ -125,24 +100,24 @@ def group_post(group_id, image_id):
|
|||
image.groups = []
|
||||
for group in groups:
|
||||
image.groups.append(
|
||||
db_session.query(db.Groups.id, db.Groups.name)
|
||||
.filter(db.Groups.id == group[0])
|
||||
Group.query.with_entities(Group.id, Group.name)
|
||||
.filter(Group.id == group[0])
|
||||
.first()
|
||||
)
|
||||
|
||||
# Get the next and previous images in the group
|
||||
next_url = (
|
||||
db_session.query(db.GroupJunction.post_id)
|
||||
.filter(db.GroupJunction.group_id == group_id)
|
||||
.filter(db.GroupJunction.post_id > image_id)
|
||||
.order_by(db.GroupJunction.date_added.asc())
|
||||
GroupJunction.query.with_entities(GroupJunction.post_id)
|
||||
.filter(GroupJunction.group_id == group_id)
|
||||
.filter(GroupJunction.post_id > image_id)
|
||||
.order_by(GroupJunction.date_added.asc())
|
||||
.first()
|
||||
)
|
||||
prev_url = (
|
||||
db_session.query(db.GroupJunction.post_id)
|
||||
.filter(db.GroupJunction.group_id == group_id)
|
||||
.filter(db.GroupJunction.post_id < image_id)
|
||||
.order_by(db.GroupJunction.date_added.desc())
|
||||
GroupJunction.query.with_entities(GroupJunction.post_id)
|
||||
.filter(GroupJunction.group_id == group_id)
|
||||
.filter(GroupJunction.post_id < image_id)
|
||||
.order_by(GroupJunction.date_added.desc())
|
||||
.first()
|
||||
)
|
||||
|
||||
|
|
|
@ -2,16 +2,12 @@
|
|||
Onlylegs - Image View
|
||||
"""
|
||||
from math import ceil
|
||||
|
||||
from flask import Blueprint, abort, render_template, url_for, current_app
|
||||
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from gallery import db
|
||||
from flask import Blueprint, render_template, url_for, current_app
|
||||
from gallery.models import Post, GroupJunction, Group
|
||||
from gallery.extensions import db
|
||||
|
||||
|
||||
blueprint = Blueprint("image", __name__, url_prefix="/image")
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route("/<int:image_id>")
|
||||
|
@ -20,45 +16,36 @@ def image(image_id):
|
|||
Image view, shows the image and its metadata
|
||||
"""
|
||||
# Get the image, if it doesn't exist, 404
|
||||
image = db_session.query(db.Posts).filter(db.Posts.id == image_id).first()
|
||||
if not image:
|
||||
abort(404, "Image not found :<")
|
||||
image = db.get_or_404(Post, image_id, description="Image not found :<")
|
||||
|
||||
# Get the image's author username
|
||||
image.author_username = (
|
||||
db_session.query(db.Users.username)
|
||||
.filter(db.Users.id == image.author_id)
|
||||
.first()[0]
|
||||
)
|
||||
|
||||
# Get the image's groups
|
||||
# Get all groups the image is in
|
||||
groups = (
|
||||
db_session.query(db.GroupJunction.group_id)
|
||||
.filter(db.GroupJunction.post_id == image_id)
|
||||
GroupJunction.query.with_entities(GroupJunction.group_id)
|
||||
.filter(GroupJunction.post_id == image_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
# For each group, get the group data and add it to the image item
|
||||
# Get the group data for each group the image is in
|
||||
image.groups = []
|
||||
for group in groups:
|
||||
image.groups.append(
|
||||
db_session.query(db.Groups.id, db.Groups.name)
|
||||
.filter(db.Groups.id == group[0])
|
||||
Group.query.with_entities(Group.id, Group.name)
|
||||
.filter(Group.id == group[0])
|
||||
.first()
|
||||
)
|
||||
|
||||
# Get the next and previous images
|
||||
# Check if there is a group ID set
|
||||
next_url = (
|
||||
db_session.query(db.Posts.id)
|
||||
.filter(db.Posts.id > image_id)
|
||||
.order_by(db.Posts.id.asc())
|
||||
Post.query.with_entities(Post.id)
|
||||
.filter(Post.id > image_id)
|
||||
.order_by(Post.id.asc())
|
||||
.first()
|
||||
)
|
||||
prev_url = (
|
||||
db_session.query(db.Posts.id)
|
||||
.filter(db.Posts.id < image_id)
|
||||
.order_by(db.Posts.id.desc())
|
||||
Post.query.with_entities(Post.id)
|
||||
.filter(Post.id < image_id)
|
||||
.order_by(Post.id.desc())
|
||||
.first()
|
||||
)
|
||||
|
||||
|
@ -69,7 +56,7 @@ def image(image_id):
|
|||
prev_url = url_for("image.image", image_id=prev_url[0])
|
||||
|
||||
# Yoink all the images in the database
|
||||
total_images = db_session.query(db.Posts.id).order_by(db.Posts.id.desc()).all()
|
||||
total_images = Post.query.with_entities(Post.id).order_by(Post.id.desc()).all()
|
||||
limit = current_app.config["UPLOAD_CONF"]["max-load"]
|
||||
|
||||
# If the number of items is less than the limit, no point of calculating the page
|
||||
|
@ -81,7 +68,7 @@ def image(image_id):
|
|||
# Slice the list of IDs into chunks of the limit
|
||||
for j in total_images[i * limit : (i + 1) * limit]:
|
||||
# Is our image in this chunk?
|
||||
if image_id in j:
|
||||
if not image_id > j[-1]:
|
||||
return_page = i + 1
|
||||
break
|
||||
|
||||
|
|
|
@ -6,13 +6,10 @@ from math import ceil
|
|||
from flask import Blueprint, render_template, request, current_app
|
||||
from werkzeug.exceptions import abort
|
||||
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from gallery import db
|
||||
from gallery.models import Post
|
||||
|
||||
|
||||
blueprint = Blueprint("gallery", __name__)
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route("/")
|
||||
|
@ -30,7 +27,7 @@ def index():
|
|||
|
||||
# get the total number of images in the database
|
||||
# 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 = Post.query.with_entities(Post.id).count()
|
||||
pages = ceil(max(total_images, limit) / limit)
|
||||
if page > pages:
|
||||
abort(
|
||||
|
@ -41,14 +38,10 @@ def index():
|
|||
|
||||
# get the images for the current page
|
||||
images = (
|
||||
db_session.query(
|
||||
db.Posts.filename,
|
||||
db.Posts.alt,
|
||||
db.Posts.colours,
|
||||
db.Posts.created_at,
|
||||
db.Posts.id,
|
||||
Post.query.with_entities(
|
||||
Post.filename, Post.alt, Post.colours, Post.created_at, Post.id
|
||||
)
|
||||
.order_by(db.Posts.id.desc())
|
||||
.order_by(Post.id.desc())
|
||||
.offset((page - 1) * limit)
|
||||
.limit(limit)
|
||||
.all()
|
||||
|
|
|
@ -5,16 +5,13 @@ from flask import Blueprint, render_template, request
|
|||
from werkzeug.exceptions import abort
|
||||
from flask_login import current_user
|
||||
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from gallery import db
|
||||
from gallery.models import Post, User
|
||||
|
||||
|
||||
blueprint = Blueprint("profile", __name__, url_prefix="/profile")
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route("/profile")
|
||||
@blueprint.route("/")
|
||||
def profile():
|
||||
"""
|
||||
Profile overview, shows all profiles on the onlylegs gallery
|
||||
|
@ -29,11 +26,11 @@ def profile():
|
|||
abort(404, "You must be logged in to view your own profile!")
|
||||
|
||||
# Get the user's data
|
||||
user = db_session.query(db.Users).filter(db.Users.id == user_id).first()
|
||||
user = User.query.filter(User.id == user_id).first()
|
||||
|
||||
if not user:
|
||||
abort(404, "User not found :c")
|
||||
|
||||
images = db_session.query(db.Posts).filter(db.Posts.author_id == user_id).all()
|
||||
images = Post.query.filter(Post.author_id == user_id).all()
|
||||
|
||||
return render_template("profile.html", user=user, images=images)
|
||||
|
|
200
poetry.lock
generated
200
poetry.lock
generated
|
@ -1,15 +1,37 @@
|
|||
# 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]]
|
||||
name = "astroid"
|
||||
version = "2.15.1"
|
||||
version = "2.15.2"
|
||||
description = "An abstract syntax tree for Python with inference support."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.2"
|
||||
files = [
|
||||
{file = "astroid-2.15.1-py3-none-any.whl", hash = "sha256:89860bda98fe2bbd1f5d262229be7629d778ce280de68d95d4a73d1f592ad268"},
|
||||
{file = "astroid-2.15.1.tar.gz", hash = "sha256:af4e0aff46e2868218502789898269ed95b663fba49e65d91c1e09c966266c34"},
|
||||
{file = "astroid-2.15.2-py3-none-any.whl", hash = "sha256:dea89d9f99f491c66ac9c04ebddf91e4acf8bd711722175fe6245c0725cc19bb"},
|
||||
{file = "astroid-2.15.2.tar.gz", hash = "sha256:6e61b85c891ec53b07471aec5878f4ac6446a41e590ede0f2ce095f39f7d49dd"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -279,6 +301,39 @@ files = [
|
|||
Flask = ">=1.0.4"
|
||||
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]]
|
||||
name = "greenlet"
|
||||
version = "2.0.2"
|
||||
|
@ -376,14 +431,14 @@ tornado = ["tornado (>=0.2)"]
|
|||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "6.1.0"
|
||||
version = "6.2.1"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "importlib_metadata-6.1.0-py3-none-any.whl", hash = "sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09"},
|
||||
{file = "importlib_metadata-6.1.0.tar.gz", hash = "sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20"},
|
||||
{file = "importlib_metadata-6.2.1-py3-none-any.whl", hash = "sha256:f65e478a7c2177bd19517a3a15dac094d253446d8690c5f3e71e735a04312374"},
|
||||
{file = "importlib_metadata-6.2.1.tar.gz", hash = "sha256:5a66966b39ff1c14ef5b2d60c1d842b0141fefff0f4cc6365b4bc9446c652807"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -394,6 +449,25 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker
|
|||
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)"]
|
||||
|
||||
[[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]]
|
||||
name = "isort"
|
||||
version = "5.12.0"
|
||||
|
@ -514,6 +588,26 @@ files = [
|
|||
{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]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.2"
|
||||
|
@ -684,18 +778,18 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-
|
|||
|
||||
[[package]]
|
||||
name = "pylint"
|
||||
version = "2.17.1"
|
||||
version = "2.17.2"
|
||||
description = "python code static checker"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.2"
|
||||
files = [
|
||||
{file = "pylint-2.17.1-py3-none-any.whl", hash = "sha256:8660a54e3f696243d644fca98f79013a959c03f979992c1ab59c24d3f4ec2700"},
|
||||
{file = "pylint-2.17.1.tar.gz", hash = "sha256:d4d009b0116e16845533bc2163493d6681846ac725eab8ca8014afb520178ddd"},
|
||||
{file = "pylint-2.17.2-py3-none-any.whl", hash = "sha256:001cc91366a7df2970941d7e6bbefcbf98694e00102c1f121c531a814ddc2ea8"},
|
||||
{file = "pylint-2.17.2.tar.gz", hash = "sha256:1b647da5249e7c279118f657ca28b6aaebb299f86bf92affc632acf199f7adbb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
astroid = ">=2.15.0,<=2.17.0-dev0"
|
||||
astroid = ">=2.15.2,<=2.17.0-dev0"
|
||||
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
|
||||
dill = [
|
||||
{version = ">=0.2", markers = "python_version < \"3.11\""},
|
||||
|
@ -796,53 +890,53 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (
|
|||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.8"
|
||||
version = "2.0.9"
|
||||
description = "Database Abstraction Library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:42207a5237d8b211df1c04da4a8700bb9f366f5d7be14b1a2e39cc33d330428c"},
|
||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:09b6a94bbb1507b3fc75a3c98491f990b80a859eb5227378f6dac908dff74396"},
|
||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd405d224c0f83f267bfcc430bd275195cb48bb9576f6949911faec16bba1216"},
|
||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e424a385a234e0bbaac69b6b09894ce99aa285518dd9397846e78214671ec0b4"},
|
||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6d82bb74a44f103d566f6c86d6db0ed944e99a7da93d6490c1e987a2b91d5613"},
|
||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c8e546c249f0d23821f4b31e5adfba8e39bc90140d39f7086d4a8422d1f0fa6"},
|
||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-win32.whl", hash = "sha256:32af8923cfe7b115d40b88604412450d2e4714f4b9a69b1966dfe6012e254789"},
|
||||
{file = "SQLAlchemy-2.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:692a2b1e0c82c678c84ccb3a9a226e0553dd115be3d5c54b1bb6ef35bc464da9"},
|
||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a215d3e9de2af881c4eee60bf2b3576fb2eeb591a9af37ff2919cd74b68f162"},
|
||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1906c954ee7287d46877636a66da157fc83553851013e42c862653ddacb380b"},
|
||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a64345a6f41c8c011df3f498cee8de39d9682ca2de14698b68a50bf3a03f6e5"},
|
||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4223002ab7faac63384019feb31eab4ae2233753fd06ea95690d4e148b5bf80d"},
|
||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3a1eb8ed5dc2a7bc554a612a57253f3682aea7e4c54c742153aef6a12fde10ee"},
|
||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:af0683c905c856db70adf6d0f84398785f7be6cb4ffdad20d8e16fe426bdf6e8"},
|
||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-win32.whl", hash = "sha256:6c5e056a6dc1ce29d5e0e54bc1ce669f3035ec18cb7e5c853122b5fdfe216f8f"},
|
||||
{file = "SQLAlchemy-2.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:21698ac65afe477400aacb56244b607477c192892e2f7a70492cae1e13f73040"},
|
||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df6722580ff89295e9d11b781d7a50ff6e48fdfe1c81362000925b4d4cbab31"},
|
||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8707ba6e783d144209dab0324cc72147a1de34d404a61a08eef96fbb3c8909f"},
|
||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cc70331b7aed25fed5260cf30a69e42eaf3d887a23a6e4f29a51167ae8a00e1"},
|
||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6f33587541eb6eb0292e82b314f5ef53159f63f14ae1c08b5ef6195732523afa"},
|
||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0cdc65cbb95ae6813c51acffa65dd8d8272a52da42e3967fc98788fe185a0e0c"},
|
||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-win32.whl", hash = "sha256:9a981a65dc0082009399eac6600b0983120bc3b850f3c1ed986f68648d9dbb4c"},
|
||||
{file = "SQLAlchemy-2.0.8-cp37-cp37m-win_amd64.whl", hash = "sha256:b3bdbd9a5b2788b5c51c4fffa2f875103d3c6d01b8f0dc4abbf075cea11741f5"},
|
||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3ca04c2ccf6f70278905a371ae6eb61494e8c388e98cba909bc9458ceb73dda5"},
|
||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5b030f91dd310c0f1d7da0654fcc9d8574fe430693e29c544f5558c2426e08eb"},
|
||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa72fb1be5e6704d697471eb5cc07bf9cd98689be11ffa724efe2a3666d0f6c1"},
|
||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf42183f93ad3801bc08c8a111de16802a2c32d16dcb9700daf1a8ae12d9d28"},
|
||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c2590fd682d3b9dbecb2710ba83b28ba9df18e0c3c26c78bb950715566784723"},
|
||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d6753512214d0de6a5a1c05fe716a7d643c927eb0495341db6526577960cbb55"},
|
||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-win32.whl", hash = "sha256:cf1447ec09d7c9ef0b3a65cea905690ed78ed88717beaac537271c005b0e4ea2"},
|
||||
{file = "SQLAlchemy-2.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:b4c73a3537f928ebf687244f2af9d477b3b14640b4472fcb2456ee0193850ba1"},
|
||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:50fa610ac1e45e9adc161227cccbd5c75f8eef25b2ad4a0e080d48c27023415d"},
|
||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:337ae79dc63b1c957dd2e9e7eed5235492886e2af45d8df4b28748ee4859b588"},
|
||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a091b9166ab9d8ff77c3155140105e76df1d585632fb9367a31b517960df78a"},
|
||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0f74c66b2d0081cee5093150407ec43d23d0d5c70aa5b41f438ead1d39bc8d"},
|
||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ea2976a98076f44e7e8ab9a90b6dfa357253edddf687294b1bcd852ffe20e526"},
|
||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d713d8c4b36c4f69eef33a70a4521e37b75a3ac9c86ee96626c4b052337978cc"},
|
||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-win32.whl", hash = "sha256:81ada150345ef9c709b647e2bcc8f688d62de6b8f06a7544bed76bef328c6636"},
|
||||
{file = "SQLAlchemy-2.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:68b328500a20f328db6af2c6839eb69e3791b2cd544f5808a4b0123b130c7571"},
|
||||
{file = "SQLAlchemy-2.0.8-py3-none-any.whl", hash = "sha256:f12cd526188f36f161eb71fb275b06d1879c565b07cf0ab2c2408c5059df1fed"},
|
||||
{file = "SQLAlchemy-2.0.8.tar.gz", hash = "sha256:250e158a1f36c965dde1f949366eae9a57504a8cd7a4d968e66c2d0b3c18198d"},
|
||||
{file = "SQLAlchemy-2.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:734805708632e3965c2c40081f9a59263c29ffa27cba9b02d4d92dfd57ba869f"},
|
||||
{file = "SQLAlchemy-2.0.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8d3ece5960b3e821e43a4927cc851b6e84a431976d3ffe02aadb96519044807e"},
|
||||
{file = "SQLAlchemy-2.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d118e233f416d713aac715e2c1101e17f91e696ff315fc9efbc75b70d11e740"},
|
||||
{file = "SQLAlchemy-2.0.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f005245e1cb9b8ca53df73ee85e029ac43155e062405015e49ec6187a2e3fb44"},
|
||||
{file = "SQLAlchemy-2.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:34eb96c1de91d8f31e988302243357bef3f7785e1b728c7d4b98bd0c117dafeb"},
|
||||
{file = "SQLAlchemy-2.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7e472e9627882f2d75b87ff91c5a2bc45b31a226efc7cc0a054a94fffef85862"},
|
||||
{file = "SQLAlchemy-2.0.9-cp310-cp310-win32.whl", hash = "sha256:0a865b5ec4ba24f57c33b633b728e43fde77b968911a6046443f581b25d29dd9"},
|
||||
{file = "SQLAlchemy-2.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:6e84ab63d25d8564d7a8c05dc080659931a459ee27f6ed1cf4c91f292d184038"},
|
||||
{file = "SQLAlchemy-2.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db4bd1c4792da753f914ff0b688086b9a8fd78bb9bc5ae8b6d2e65f176b81eb9"},
|
||||
{file = "SQLAlchemy-2.0.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad5363a1c65fde7b7466769d4261126d07d872fc2e816487ae6cec93da604b6b"},
|
||||
{file = "SQLAlchemy-2.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebc4eeb1737a5a9bdb0c24f4c982319fa6edd23cdee27180978c29cbb026f2bd"},
|
||||
{file = "SQLAlchemy-2.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbda1da8d541904ba262825a833c9f619e93cb3fd1156be0a5e43cd54d588dcd"},
|
||||
{file = "SQLAlchemy-2.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d5327f54a9c39e7871fc532639616f3777304364a0bb9b89d6033ad34ef6c5f8"},
|
||||
{file = "SQLAlchemy-2.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ac6a0311fb21a99855953f84c43fcff4bdca27a2ffcc4f4d806b26b54b5cddc9"},
|
||||
{file = "SQLAlchemy-2.0.9-cp311-cp311-win32.whl", hash = "sha256:d209594e68bec103ad5243ecac1b40bf5770c9ebf482df7abf175748a34f4853"},
|
||||
{file = "SQLAlchemy-2.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:865392a50a721445156809c1a6d6ab6437be70c1c2599f591a8849ed95d3c693"},
|
||||
{file = "SQLAlchemy-2.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0b49f1f71d7a44329a43d3edd38cc5ee4c058dfef4487498393d16172007954b"},
|
||||
{file = "SQLAlchemy-2.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a019f723b6c1e6b3781be00fb9e0844bc6156f9951c836ff60787cc3938d76"},
|
||||
{file = "SQLAlchemy-2.0.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9838bd247ee42eb74193d865e48dd62eb50e45e3fdceb0fdef3351133ee53dcf"},
|
||||
{file = "SQLAlchemy-2.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:78612edf4ba50d407d0eb3a64e9ec76e6efc2b5d9a5c63415d53e540266a230a"},
|
||||
{file = "SQLAlchemy-2.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f61ab84956dc628c8dfe9d105b6aec38afb96adae3e5e7da6085b583ff6ea789"},
|
||||
{file = "SQLAlchemy-2.0.9-cp37-cp37m-win32.whl", hash = "sha256:07950fc82f844a2de67ddb4e535f29b65652b4d95e8b847823ce66a6d540a41d"},
|
||||
{file = "SQLAlchemy-2.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:e62c4e762d6fd2901692a093f208a6a6575b930e9458ad58c2a7f080dd6132da"},
|
||||
{file = "SQLAlchemy-2.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b3e5864eba71a3718236a120547e52c8da2ccb57cc96cecd0480106a0c799c92"},
|
||||
{file = "SQLAlchemy-2.0.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d06e119cf79a3d80ab069f064a07152eb9ba541d084bdaee728d8a6f03fd03d"},
|
||||
{file = "SQLAlchemy-2.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee2946042cc7851842d7a086a92b9b7b494cbe8c3e7e4627e27bc912d3a7655e"},
|
||||
{file = "SQLAlchemy-2.0.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13f984a190d249769a050634b248aef8991acc035e849d02b634ea006c028fa8"},
|
||||
{file = "SQLAlchemy-2.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e4780be0f19e5894c17f75fc8de2fe1ae233ab37827125239ceb593c6f6bd1e2"},
|
||||
{file = "SQLAlchemy-2.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:68ed381bc340b4a3d373dbfec1a8b971f6350139590c4ca3cb722fdb50035777"},
|
||||
{file = "SQLAlchemy-2.0.9-cp38-cp38-win32.whl", hash = "sha256:aa5c270ece17c0c0e0a38f2530c16b20ea05d8b794e46c79171a86b93b758891"},
|
||||
{file = "SQLAlchemy-2.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:1b69666e25cc03c602d9d3d460e1281810109e6546739187044fc256c67941ef"},
|
||||
{file = "SQLAlchemy-2.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6e27189ff9aebfb2c02fd252c629ea58657e7a5ff1a321b7fc9c2bf6dc0b5f3"},
|
||||
{file = "SQLAlchemy-2.0.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8239ce63a90007bce479adf5460d48c1adae4b933d8e39a4eafecfc084e503c"},
|
||||
{file = "SQLAlchemy-2.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f759eccb66e6d495fb622eb7f4ac146ae674d829942ec18b7f5a35ddf029597"},
|
||||
{file = "SQLAlchemy-2.0.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246712af9fc761d6c13f4f065470982e175d902e77aa4218c9cb9fc9ff565a0c"},
|
||||
{file = "SQLAlchemy-2.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6b72dccc5864ea95c93e0a9c4e397708917fb450f96737b4a8395d009f90b868"},
|
||||
{file = "SQLAlchemy-2.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:93c78d42c14aa9a9e0866eacd5b48df40a50d0e2790ee377af7910d224afddcf"},
|
||||
{file = "SQLAlchemy-2.0.9-cp39-cp39-win32.whl", hash = "sha256:f49c5d3c070a72ecb96df703966c9678dda0d4cb2e2736f88d15f5e1203b4159"},
|
||||
{file = "SQLAlchemy-2.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:4c3020afb144572c7bfcba9d7cce57ad42bff6e6115dffcfe2d4ae6d444a214f"},
|
||||
{file = "SQLAlchemy-2.0.9-py3-none-any.whl", hash = "sha256:e730603cae5747bc6d6dece98b45a57d647ed553c8d5ecef602697b1c1501cf2"},
|
||||
{file = "SQLAlchemy-2.0.9.tar.gz", hash = "sha256:95719215e3ec7337b9f57c3c2eda0e6a7619be194a5166c07c1e599f6afc20fa"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1042,4 +1136,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "58c3430743ce1cfd8e5b89db371a0d454a478cbe79ced08c645b4628980ca9f1"
|
||||
content-hash = "88387c49c901feebd4685ee75f6b79c0bbc8cc9c1a64798cd0394b140e165603"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "onlylegs"
|
||||
version = "23.04.08"
|
||||
version = "23.04.10"
|
||||
description = "Gallery built for fast and simple image management"
|
||||
authors = ["Fluffy-Bean <michal-gdula@protonmail.com>"]
|
||||
license = "MIT"
|
||||
|
@ -9,19 +9,20 @@ readme = ".github/README.md"
|
|||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
Flask = "^2.2.2"
|
||||
Flask-Sqlalchemy = "^3.0.3"
|
||||
Flask-Migrate = "^4.0.4"
|
||||
Flask-Compress = "^1.13"
|
||||
Flask-Caching = "^2.0.2"
|
||||
Flask-Assets = "^2.0"
|
||||
Flask-Login = "^0.6.2"
|
||||
SQLAlchemy = "^2.0.3"
|
||||
python-dotenv = "^0.21.0"
|
||||
gunicorn = "^20.1.0"
|
||||
pyyaml = "^6.0"
|
||||
libsass = "^0.22.0"
|
||||
colorthief = "^0.2.1"
|
||||
Pillow = "^9.4.0"
|
||||
platformdirs = "^3.0.0"
|
||||
pylint = "^2.16.3"
|
||||
libsass = "^0.22.0"
|
||||
jsmin = "^3.0.1"
|
||||
cssmin = "^0.2.0"
|
||||
|
||||
|
|
Loading…
Reference in a new issue