mirror of
https://github.com/Derpy-Leggies/OnlyLegs.git
synced 2024-12-29 10:56:10 +00:00
Format code with Black
This commit is contained in:
parent
fef8a557d2
commit
128464d43f
|
@ -25,14 +25,14 @@ from sqlalchemy.orm import sessionmaker
|
|||
from gallery import db
|
||||
|
||||
|
||||
USER_DIR = platformdirs.user_config_dir('onlylegs')
|
||||
USER_DIR = platformdirs.user_config_dir("onlylegs")
|
||||
|
||||
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
login_manager = LoginManager()
|
||||
assets = Environment()
|
||||
cache = Cache(config={'CACHE_TYPE': 'SimpleCache', 'CACHE_DEFAULT_TIMEOUT': 300})
|
||||
cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 300})
|
||||
compress = Compress()
|
||||
|
||||
|
||||
|
@ -40,37 +40,37 @@ def create_app(test_config=None):
|
|||
"""
|
||||
Create and configure the main app
|
||||
"""
|
||||
app = Flask(__name__, instance_path=os.path.join(USER_DIR, 'instance'))
|
||||
app = Flask(__name__, instance_path=os.path.join(USER_DIR, "instance"))
|
||||
|
||||
# Get environment variables
|
||||
load_dotenv(os.path.join(USER_DIR, '.env'))
|
||||
load_dotenv(os.path.join(USER_DIR, ".env"))
|
||||
print("Loaded environment variables")
|
||||
|
||||
# Get config file
|
||||
with open(os.path.join(USER_DIR, 'conf.yml'), encoding='utf-8', mode='r') as file:
|
||||
with open(os.path.join(USER_DIR, "conf.yml"), encoding="utf-8", mode="r") as file:
|
||||
conf = safe_load(file)
|
||||
print("Loaded config")
|
||||
|
||||
# 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'],
|
||||
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"],
|
||||
)
|
||||
|
||||
if test_config is None:
|
||||
app.config.from_pyfile('config.py', silent=True)
|
||||
app.config.from_pyfile("config.py", silent=True)
|
||||
else:
|
||||
app.config.from_mapping(test_config)
|
||||
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = 'gallery.index'
|
||||
login_manager.session_protection = 'strong'
|
||||
login_manager.login_view = "gallery.index"
|
||||
login_manager.session_protection = "strong"
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
|
@ -79,49 +79,46 @@ def create_app(test_config=None):
|
|||
@login_manager.unauthorized_handler
|
||||
def unauthorized():
|
||||
error = 401
|
||||
msg = 'You are not authorized to view this page!!!!'
|
||||
return render_template('error.html', error=error, msg=msg), error
|
||||
msg = "You are not authorized to view this page!!!!"
|
||||
return render_template("error.html", error=error, msg=msg), error
|
||||
|
||||
js_pre = Bundle(
|
||||
'js/pre/*.js',
|
||||
filters='jsmin',
|
||||
output='gen/pre_packed.js',
|
||||
depends='**'
|
||||
"js/pre/*.js", filters="jsmin", output="gen/pre_packed.js", depends="**"
|
||||
)
|
||||
js_post = Bundle(
|
||||
'js/post/*.js',
|
||||
filters='jsmin',
|
||||
output='gen/post_packed.js',
|
||||
depends='**'
|
||||
"js/post/*.js", filters="jsmin", output="gen/post_packed.js", depends="**"
|
||||
)
|
||||
styles = Bundle(
|
||||
'sass/*.sass',
|
||||
filters='libsass,cssmin',
|
||||
output='gen/styles.css',
|
||||
depends='**'
|
||||
"sass/*.sass", filters="libsass,cssmin", output="gen/styles.css", depends="**"
|
||||
)
|
||||
|
||||
assets.register('js_pre', js_pre)
|
||||
assets.register('js_post', js_post)
|
||||
assets.register('styles', styles)
|
||||
assets.register("js_pre", js_pre)
|
||||
assets.register("js_post", js_post)
|
||||
assets.register("styles", styles)
|
||||
|
||||
# Error handlers, if the error is not a HTTP error, return 500
|
||||
@app.errorhandler(Exception)
|
||||
def error_page(err): # noqa
|
||||
if not isinstance(err, HTTPException):
|
||||
abort(500)
|
||||
return render_template('error.html', error=err.code, msg=err.description), err.code
|
||||
return (
|
||||
render_template("error.html", error=err.code, msg=err.description),
|
||||
err.code,
|
||||
)
|
||||
|
||||
# Load login, registration and logout manager
|
||||
from gallery import auth
|
||||
|
||||
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)
|
||||
|
@ -129,7 +126,7 @@ def create_app(test_config=None):
|
|||
app.register_blueprint(settings.blueprint)
|
||||
|
||||
# Log to file that the app has started
|
||||
logging.info('Gallery started successfully!')
|
||||
logging.info("Gallery started successfully!")
|
||||
|
||||
# Initialize extensions and return app
|
||||
assets.init_app(app)
|
||||
|
|
111
gallery/api.py
111
gallery/api.py
|
@ -21,26 +21,28 @@ from gallery.utils import metadata as mt
|
|||
from gallery.utils.generate_image import generate_thumbnail
|
||||
|
||||
|
||||
blueprint = Blueprint('api', __name__, url_prefix='/api')
|
||||
blueprint = Blueprint("api", __name__, url_prefix="/api")
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route('/file/<file_name>', methods=['GET'])
|
||||
@blueprint.route("/file/<file_name>", methods=["GET"])
|
||||
def file(file_name):
|
||||
"""
|
||||
Returns a file from the uploads folder
|
||||
r for resolution, 400x400 or thumb for thumbnail
|
||||
"""
|
||||
res = request.args.get('r', default=None, type=str) # Type of file (thumb, etc)
|
||||
ext = request.args.get('e', default=None, type=str) # File extension
|
||||
res = request.args.get("r", default=None, type=str) # Type of file (thumb, etc)
|
||||
ext = request.args.get("e", default=None, type=str) # File extension
|
||||
file_name = secure_filename(file_name) # Sanitize file name
|
||||
|
||||
# if no args are passed, return the raw file
|
||||
if not res and not ext:
|
||||
if not os.path.exists(os.path.join(current_app.config['UPLOAD_FOLDER'], file_name)):
|
||||
if not os.path.exists(
|
||||
os.path.join(current_app.config["UPLOAD_FOLDER"], file_name)
|
||||
):
|
||||
abort(404)
|
||||
return send_from_directory(current_app.config['UPLOAD_FOLDER'], file_name)
|
||||
return send_from_directory(current_app.config["UPLOAD_FOLDER"], file_name)
|
||||
|
||||
thumb = generate_thumbnail(file_name, res, ext)
|
||||
if not thumb:
|
||||
|
@ -91,13 +93,13 @@ def file(file_name):
|
|||
# return jsonify(image_list)
|
||||
|
||||
|
||||
@blueprint.route('/upload', methods=['POST'])
|
||||
@blueprint.route("/upload", methods=["POST"])
|
||||
@login_required
|
||||
def upload():
|
||||
"""
|
||||
Uploads an image to the server and saves it to the database
|
||||
"""
|
||||
form_file = request.files['file']
|
||||
form_file = request.files["file"]
|
||||
form = request.form
|
||||
|
||||
# If no image is uploaded, return 404 error
|
||||
|
@ -105,41 +107,45 @@ def upload():
|
|||
return abort(404)
|
||||
|
||||
# Get file extension, generate random name and set file path
|
||||
img_ext = pathlib.Path(form_file.filename).suffix.replace('.', '').lower()
|
||||
img_ext = pathlib.Path(form_file.filename).suffix.replace(".", "").lower()
|
||||
img_name = "GWAGWA_" + str(uuid4())
|
||||
img_path = os.path.join(current_app.config['UPLOAD_FOLDER'], img_name+'.'+img_ext)
|
||||
img_path = os.path.join(
|
||||
current_app.config["UPLOAD_FOLDER"], img_name + "." + img_ext
|
||||
)
|
||||
|
||||
# Check if file extension is allowed
|
||||
if img_ext not in current_app.config['ALLOWED_EXTENSIONS'].keys():
|
||||
logging.info('File extension not allowed: %s', img_ext)
|
||||
if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys():
|
||||
logging.info("File extension not allowed: %s", img_ext)
|
||||
abort(403)
|
||||
|
||||
# Save file
|
||||
try:
|
||||
form_file.save(img_path)
|
||||
except OSError as err:
|
||||
logging.info('Error saving file %s because of %s', img_path, err)
|
||||
logging.info("Error saving file %s because of %s", img_path, err)
|
||||
abort(500)
|
||||
|
||||
img_exif = mt.Metadata(img_path).yoink() # Get EXIF data
|
||||
img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette
|
||||
|
||||
# Save to database
|
||||
query = db.Posts(author_id=current_user.id,
|
||||
filename=img_name + '.' + img_ext,
|
||||
query = db.Posts(
|
||||
author_id=current_user.id,
|
||||
filename=img_name + "." + img_ext,
|
||||
mimetype=img_ext,
|
||||
exif=img_exif,
|
||||
colours=img_colors,
|
||||
description=form['description'],
|
||||
alt=form['alt'])
|
||||
description=form["description"],
|
||||
alt=form["alt"],
|
||||
)
|
||||
|
||||
db_session.add(query)
|
||||
db_session.commit()
|
||||
|
||||
return 'Gwa Gwa' # Return something so the browser doesn't show an error
|
||||
return "Gwa Gwa" # Return something so the browser doesn't show an error
|
||||
|
||||
|
||||
@blueprint.route('/delete/<int:image_id>', methods=['POST'])
|
||||
@blueprint.route("/delete/<int:image_id>", methods=["POST"])
|
||||
@login_required
|
||||
def delete_image(image_id):
|
||||
"""
|
||||
|
@ -155,14 +161,16 @@ def delete_image(image_id):
|
|||
|
||||
# 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"], img.filename))
|
||||
except FileNotFoundError:
|
||||
logging.warning('File not found: %s, already deleted or never existed', img.filename)
|
||||
logging.warning(
|
||||
"File not found: %s, already deleted or never existed", img.filename
|
||||
)
|
||||
|
||||
# Delete cached files
|
||||
cache_path = os.path.join(platformdirs.user_config_dir('onlylegs'), 'cache')
|
||||
cache_name = img.filename.rsplit('.')[0]
|
||||
for cache_file in pathlib.Path(cache_path).glob(cache_name + '*'):
|
||||
cache_path = os.path.join(platformdirs.user_config_dir("onlylegs"), "cache")
|
||||
cache_name = img.filename.rsplit(".")[0]
|
||||
for cache_file in pathlib.Path(cache_path).glob(cache_name + "*"):
|
||||
os.remove(cache_file)
|
||||
|
||||
# Delete from database
|
||||
|
@ -176,36 +184,38 @@ def delete_image(image_id):
|
|||
# Commit all changes
|
||||
db_session.commit()
|
||||
|
||||
logging.info('Removed image (%s) %s', image_id, img.filename)
|
||||
flash(['Image was all in Le Head!', '1'])
|
||||
return 'Gwa Gwa'
|
||||
logging.info("Removed image (%s) %s", image_id, img.filename)
|
||||
flash(["Image was all in Le Head!", "1"])
|
||||
return "Gwa Gwa"
|
||||
|
||||
|
||||
@blueprint.route('/group/create', methods=['POST'])
|
||||
@blueprint.route("/group/create", methods=["POST"])
|
||||
@login_required
|
||||
def create_group():
|
||||
"""
|
||||
Creates a group
|
||||
"""
|
||||
new_group = db.Groups(name=request.form['name'],
|
||||
description=request.form['description'],
|
||||
author_id=current_user.id)
|
||||
new_group = db.Groups(
|
||||
name=request.form["name"],
|
||||
description=request.form["description"],
|
||||
author_id=current_user.id,
|
||||
)
|
||||
|
||||
db_session.add(new_group)
|
||||
db_session.commit()
|
||||
|
||||
return ':3'
|
||||
return ":3"
|
||||
|
||||
|
||||
@blueprint.route('/group/modify', methods=['POST'])
|
||||
@blueprint.route("/group/modify", methods=["POST"])
|
||||
@login_required
|
||||
def modify_group():
|
||||
"""
|
||||
Changes the images in a group
|
||||
"""
|
||||
group_id = request.form['group']
|
||||
image_id = request.form['image']
|
||||
action = request.form['action']
|
||||
group_id = request.form["group"]
|
||||
image_id = request.form["image"]
|
||||
action = request.form["action"]
|
||||
|
||||
group = db_session.query(db.Groups).filter_by(id=group_id).first()
|
||||
|
||||
|
@ -214,28 +224,31 @@ def modify_group():
|
|||
elif group.author_id != current_user.id:
|
||||
abort(403)
|
||||
|
||||
if action == 'add':
|
||||
if not (db_session.query(db.GroupJunction)
|
||||
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))
|
||||
elif request.form['action'] == 'remove':
|
||||
(db_session.query(db.GroupJunction)
|
||||
.first()
|
||||
):
|
||||
db_session.add(db.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())
|
||||
.delete()
|
||||
)
|
||||
|
||||
db_session.commit()
|
||||
|
||||
return ':3'
|
||||
return ":3"
|
||||
|
||||
|
||||
@blueprint.route('/group/delete', methods=['POST'])
|
||||
@blueprint.route("/group/delete", methods=["POST"])
|
||||
def delete_group():
|
||||
"""
|
||||
Deletes a group
|
||||
"""
|
||||
group_id = request.form['group']
|
||||
group_id = request.form["group"]
|
||||
|
||||
group = db_session.query(db.Groups).filter_by(id=group_id).first()
|
||||
|
||||
|
@ -248,5 +261,5 @@ def delete_group():
|
|||
db_session.query(db.GroupJunction).filter_by(group_id=group_id).delete()
|
||||
db_session.commit()
|
||||
|
||||
flash(['Group yeeted!', '1'])
|
||||
return ':3'
|
||||
flash(["Group yeeted!", "1"])
|
||||
return ":3"
|
||||
|
|
|
@ -14,39 +14,39 @@ from sqlalchemy.orm import sessionmaker
|
|||
from gallery import db
|
||||
|
||||
|
||||
blueprint = Blueprint('auth', __name__, url_prefix='/auth')
|
||||
blueprint = Blueprint("auth", __name__, url_prefix="/auth")
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route('/login', methods=['POST'])
|
||||
@blueprint.route("/login", methods=["POST"])
|
||||
def login():
|
||||
"""
|
||||
Log in a registered user by adding the user id to the session
|
||||
"""
|
||||
error = []
|
||||
|
||||
username = request.form['username'].strip()
|
||||
password = request.form['password'].strip()
|
||||
remember = bool(request.form['remember-me'])
|
||||
username = request.form["username"].strip()
|
||||
password = request.form["password"].strip()
|
||||
remember = bool(request.form["remember-me"])
|
||||
|
||||
user = db_session.query(db.Users).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)
|
||||
error.append('Username or Password is incorrect!')
|
||||
logging.error("Login attempt from %s", request.remote_addr)
|
||||
error.append("Username or Password is incorrect!")
|
||||
|
||||
if error:
|
||||
abort(403)
|
||||
|
||||
login_user(user, remember=remember)
|
||||
|
||||
logging.info('User %s logged in from %s', username, request.remote_addr)
|
||||
flash(['Logged in successfully!', '4'])
|
||||
return 'ok', 200
|
||||
logging.info("User %s logged in from %s", username, request.remote_addr)
|
||||
flash(["Logged in successfully!", "4"])
|
||||
return "ok", 200
|
||||
|
||||
|
||||
@blueprint.route('/register', methods=['POST'])
|
||||
@blueprint.route("/register", methods=["POST"])
|
||||
def register():
|
||||
"""
|
||||
Register a new user
|
||||
|
@ -54,55 +54,58 @@ def register():
|
|||
error = []
|
||||
|
||||
# Thanks Fennec for reminding me to strip out the whitespace lol
|
||||
username = request.form['username'].strip()
|
||||
email = request.form['email'].strip()
|
||||
password = request.form['password'].strip()
|
||||
password_repeat = request.form['password-repeat'].strip()
|
||||
username = request.form["username"].strip()
|
||||
email = request.form["email"].strip()
|
||||
password = request.form["password"].strip()
|
||||
password_repeat = request.form["password-repeat"].strip()
|
||||
|
||||
email_regex = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
|
||||
username_regex = re.compile(r'\b[A-Za-z0-9._-]+\b')
|
||||
email_regex = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b")
|
||||
username_regex = re.compile(r"\b[A-Za-z0-9._-]+\b")
|
||||
|
||||
# Validate the form
|
||||
if not username or not username_regex.match(username):
|
||||
error.append('Username is invalid!')
|
||||
error.append("Username is invalid!")
|
||||
|
||||
if not email or not email_regex.match(email):
|
||||
error.append('Email is invalid!')
|
||||
error.append("Email is invalid!")
|
||||
|
||||
if not password:
|
||||
error.append('Password is empty!')
|
||||
error.append("Password is empty!")
|
||||
elif len(password) < 8:
|
||||
error.append('Password is too short! Longer than 8 characters pls')
|
||||
error.append("Password is too short! Longer than 8 characters pls")
|
||||
|
||||
if not password_repeat:
|
||||
error.append('Enter password again!')
|
||||
error.append("Enter password again!")
|
||||
elif password_repeat != password:
|
||||
error.append('Passwords do not match!')
|
||||
error.append("Passwords do not match!")
|
||||
|
||||
user_exists = db_session.query(db.Users).filter_by(username=username).first()
|
||||
if user_exists:
|
||||
error.append('User already exists!')
|
||||
error.append("User already exists!")
|
||||
|
||||
# If there are errors, return them
|
||||
if error:
|
||||
print(error)
|
||||
return jsonify(error), 400
|
||||
|
||||
register_user = db.Users(username=username, email=email,
|
||||
password=generate_password_hash(password, method='sha256'))
|
||||
register_user = db.Users(
|
||||
username=username,
|
||||
email=email,
|
||||
password=generate_password_hash(password, method="sha256"),
|
||||
)
|
||||
db_session.add(register_user)
|
||||
db_session.commit()
|
||||
|
||||
logging.info('User %s registered', username)
|
||||
return 'ok', 200
|
||||
logging.info("User %s registered", username)
|
||||
return "ok", 200
|
||||
|
||||
|
||||
@blueprint.route('/logout')
|
||||
@blueprint.route("/logout")
|
||||
@login_required
|
||||
def logout():
|
||||
"""
|
||||
Clear the current session, including the stored user id
|
||||
"""
|
||||
logout_user()
|
||||
flash(['Goodbye!!!', '4'])
|
||||
return redirect(url_for('gallery.index'))
|
||||
flash(["Goodbye!!!", "4"])
|
||||
return redirect(url_for("gallery.index"))
|
||||
|
|
|
@ -5,27 +5,36 @@ from uuid import uuid4
|
|||
import os
|
||||
import platformdirs
|
||||
|
||||
from sqlalchemy import (create_engine, Column, Integer, String,
|
||||
DateTime, ForeignKey, PickleType, func)
|
||||
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')
|
||||
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)
|
||||
engine = create_engine(f"sqlite:///{DB_PATH}", echo=False)
|
||||
base = declarative_base()
|
||||
|
||||
|
||||
class Users (base, UserMixin): # pylint: disable=too-few-public-methods, C0103
|
||||
class Users(base, UserMixin): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
User table
|
||||
Joins with post, groups, session and log
|
||||
"""
|
||||
__tablename__ = 'users'
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
# Gallery used information
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
@ -34,26 +43,31 @@ class Users (base, UserMixin): # pylint: disable=too-few-public-methods, C0103
|
|||
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
|
||||
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')
|
||||
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
|
||||
class Posts(base): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
Post table
|
||||
Joins with group_junction
|
||||
"""
|
||||
__tablename__ = 'posts'
|
||||
|
||||
__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
|
||||
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)
|
||||
|
@ -61,66 +75,79 @@ class Posts (base): # pylint: disable=too-few-public-methods, C0103
|
|||
description = Column(String, nullable=False)
|
||||
alt = Column(String, nullable=False)
|
||||
|
||||
junction = relationship('GroupJunction', backref='posts')
|
||||
junction = relationship("GroupJunction", backref="posts")
|
||||
|
||||
class Groups (base): # pylint: disable=too-few-public-methods, C0103
|
||||
|
||||
class Groups(base): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
Group table
|
||||
Joins with group_junction
|
||||
"""
|
||||
__tablename__ = 'groups'
|
||||
|
||||
__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
|
||||
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')
|
||||
junction = relationship("GroupJunction", backref="groups")
|
||||
|
||||
|
||||
class GroupJunction (base): # pylint: disable=too-few-public-methods, C0103
|
||||
class GroupJunction(base): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
Junction table for posts and groups
|
||||
Joins with posts and groups
|
||||
"""
|
||||
__tablename__ = 'group_junction'
|
||||
|
||||
__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'))
|
||||
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
|
||||
class Logs(base): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
Log table
|
||||
Joins with user
|
||||
"""
|
||||
__tablename__ = 'logs'
|
||||
|
||||
__tablename__ = "logs"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey('users.id'))
|
||||
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
|
||||
created_at = Column(
|
||||
DateTime, nullable=False, server_default=func.now()
|
||||
) # pylint: disable=E1102
|
||||
|
||||
|
||||
class Bans (base): # pylint: disable=too-few-public-methods, C0103
|
||||
class Bans(base): # pylint: disable=too-few-public-methods, C0103
|
||||
"""
|
||||
Bans table
|
||||
"""
|
||||
__tablename__ = 'bans'
|
||||
|
||||
__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
|
||||
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')
|
||||
print("Database created")
|
||||
|
|
|
@ -16,7 +16,10 @@ def contrast(background, light, dark, threshold=0.179):
|
|||
|
||||
# Calculate contrast
|
||||
uicolors = [red / 255, green / 255, blue / 255]
|
||||
cont = [col / 12.92 if col <= 0.03928 else ((col + 0.055) / 1.055) ** 2.4 for col in uicolors]
|
||||
cont = [
|
||||
col / 12.92 if col <= 0.03928 else ((col + 0.055) / 1.055) ** 2.4
|
||||
for col in uicolors
|
||||
]
|
||||
lightness = (0.2126 * cont[0]) + (0.7152 * cont[1]) + (0.0722 * cont[2])
|
||||
|
||||
return light if lightness > threshold else dark
|
||||
|
|
|
@ -8,8 +8,8 @@ from PIL import Image, ImageOps
|
|||
from werkzeug.utils import secure_filename
|
||||
|
||||
|
||||
CACHE_PATH = platformdirs.user_config_dir('onlylegs') + '/cache'
|
||||
UPLOAD_PATH = platformdirs.user_config_dir('onlylegs') + '/uploads'
|
||||
CACHE_PATH = os.path.join(platformdirs.user_config_dir("onlylegs"), "cache")
|
||||
UPLOAD_PATH = os.path.join(platformdirs.user_config_dir("onlylegs"), "uploads")
|
||||
|
||||
|
||||
def generate_thumbnail(file_name, resolution, ext=None):
|
||||
|
@ -25,34 +25,34 @@ def generate_thumbnail(file_name, resolution, ext=None):
|
|||
os.makedirs(CACHE_PATH)
|
||||
|
||||
# no sussy business
|
||||
file_name, file_ext = secure_filename(file_name).rsplit('.')
|
||||
file_name, file_ext = secure_filename(file_name).rsplit(".")
|
||||
if not ext:
|
||||
ext = file_ext.strip('.')
|
||||
ext = file_ext.strip(".")
|
||||
|
||||
# PIL doesnt like jpg so we convert it to jpeg
|
||||
if ext.lower() == "jpg":
|
||||
ext = "jpeg"
|
||||
|
||||
# Set resolution based on preset resolutions
|
||||
if resolution in ['prev', 'preview']:
|
||||
if resolution in ["prev", "preview"]:
|
||||
res_x, res_y = (1920, 1080)
|
||||
elif resolution in ['thumb', 'thumbnail']:
|
||||
elif resolution in ["thumb", "thumbnail"]:
|
||||
res_x, res_y = (400, 400)
|
||||
elif resolution in ['icon', 'favicon']:
|
||||
elif resolution in ["icon", "favicon"]:
|
||||
res_x, res_y = (10, 10)
|
||||
else:
|
||||
return None
|
||||
|
||||
# If image has been already generated, return it from the cache
|
||||
if os.path.exists(os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}')):
|
||||
return os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}')
|
||||
if os.path.exists(os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}")):
|
||||
return os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}")
|
||||
|
||||
# Check if image exists in the uploads directory
|
||||
if not os.path.exists(os.path.join(UPLOAD_PATH, f'{file_name}.{file_ext}')):
|
||||
if not os.path.exists(os.path.join(UPLOAD_PATH, f"{file_name}.{file_ext}")):
|
||||
return None
|
||||
|
||||
# Open image and rotate it based on EXIF data and get ICC profile so colors are correct
|
||||
image = Image.open(os.path.join(UPLOAD_PATH, f'{file_name}.{file_ext}'))
|
||||
image = Image.open(os.path.join(UPLOAD_PATH, f"{file_name}.{file_ext}"))
|
||||
image_icc = image.info.get("icc_profile")
|
||||
img_x, img_y = image.size
|
||||
|
||||
|
@ -62,16 +62,20 @@ def generate_thumbnail(file_name, resolution, ext=None):
|
|||
|
||||
# Save image to cache directory
|
||||
try:
|
||||
image.save(os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}'),
|
||||
icc_profile=image_icc)
|
||||
image.save(
|
||||
os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}"),
|
||||
icc_profile=image_icc,
|
||||
)
|
||||
except OSError:
|
||||
# This usually happens when saving a JPEG with an ICC profile,
|
||||
# so we convert to RGB and try again
|
||||
image = image.convert('RGB')
|
||||
image.save(os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}'),
|
||||
icc_profile=image_icc)
|
||||
image = image.convert("RGB")
|
||||
image.save(
|
||||
os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}"),
|
||||
icc_profile=image_icc,
|
||||
)
|
||||
|
||||
# No need to keep the image in memory, learned the hard way
|
||||
image.close()
|
||||
|
||||
return os.path.join(CACHE_PATH, f'{file_name}_{res_x}x{res_y}.{ext}')
|
||||
return os.path.join(CACHE_PATH, f"{file_name}_{res_x}x{res_y}.{ext}")
|
||||
|
|
|
@ -16,6 +16,7 @@ class Metadata:
|
|||
"""
|
||||
Metadata parser
|
||||
"""
|
||||
|
||||
def __init__(self, file_path):
|
||||
"""
|
||||
Initialize the metadata parser
|
||||
|
@ -32,17 +33,17 @@ class Metadata:
|
|||
if tag in tags:
|
||||
img_exif[value] = tags[tag]
|
||||
|
||||
img_exif['FileName'] = os.path.basename(file_path)
|
||||
img_exif['FileSize'] = os.path.getsize(file_path)
|
||||
img_exif['FileFormat'] = img_exif['FileName'].split('.')[-1]
|
||||
img_exif['FileWidth'], img_exif['FileHeight'] = file.size
|
||||
img_exif["FileName"] = os.path.basename(file_path)
|
||||
img_exif["FileSize"] = os.path.getsize(file_path)
|
||||
img_exif["FileFormat"] = img_exif["FileName"].split(".")[-1]
|
||||
img_exif["FileWidth"], img_exif["FileHeight"] = file.size
|
||||
|
||||
file.close()
|
||||
except TypeError:
|
||||
img_exif['FileName'] = os.path.basename(file_path)
|
||||
img_exif['FileSize'] = os.path.getsize(file_path)
|
||||
img_exif['FileFormat'] = img_exif['FileName'].split('.')[-1]
|
||||
img_exif['FileWidth'], img_exif['FileHeight'] = file.size
|
||||
img_exif["FileName"] = os.path.basename(file_path)
|
||||
img_exif["FileSize"] = os.path.getsize(file_path)
|
||||
img_exif["FileFormat"] = img_exif["FileName"].split(".")[-1]
|
||||
img_exif["FileWidth"], img_exif["FileHeight"] = file.size
|
||||
|
||||
self.encoded = img_exif
|
||||
|
||||
|
@ -60,10 +61,10 @@ class Metadata:
|
|||
Formats the data into a dictionary
|
||||
"""
|
||||
exif = {
|
||||
'Photographer': {},
|
||||
'Camera': {},
|
||||
'Software': {},
|
||||
'File': {},
|
||||
"Photographer": {},
|
||||
"Camera": {},
|
||||
"Software": {},
|
||||
"File": {},
|
||||
}
|
||||
|
||||
# Thanks chatGPT xP
|
||||
|
@ -73,26 +74,29 @@ class Metadata:
|
|||
if len(mapping_val[key]) == 2:
|
||||
# the helper function works, so not sure why it triggers pylint
|
||||
exif[mapping_name][mapping_val[key][0]] = {
|
||||
'raw': value,
|
||||
'formatted': (
|
||||
getattr(helpers, mapping_val[key][1]) # pylint: disable=E0602
|
||||
(value)
|
||||
"raw": value,
|
||||
"formatted": (
|
||||
getattr(
|
||||
helpers, mapping_val[key][1]
|
||||
)( # pylint: disable=E0602
|
||||
value
|
||||
)
|
||||
),
|
||||
}
|
||||
else:
|
||||
exif[mapping_name][mapping_val[key][0]] = {
|
||||
'raw': value,
|
||||
"raw": value,
|
||||
}
|
||||
continue
|
||||
|
||||
# Remove empty keys
|
||||
if not exif['Photographer']:
|
||||
del exif['Photographer']
|
||||
if not exif['Camera']:
|
||||
del exif['Camera']
|
||||
if not exif['Software']:
|
||||
del exif['Software']
|
||||
if not exif['File']:
|
||||
del exif['File']
|
||||
if not exif["Photographer"]:
|
||||
del exif["Photographer"]
|
||||
if not exif["Camera"]:
|
||||
del exif["Camera"]
|
||||
if not exif["Software"]:
|
||||
del exif["Software"]
|
||||
if not exif["File"]:
|
||||
del exif["File"]
|
||||
|
||||
return exif
|
||||
|
|
|
@ -21,28 +21,28 @@ def date_format(value):
|
|||
"""
|
||||
Formats the date into a standard format
|
||||
"""
|
||||
return str(datetime.strptime(value, '%Y:%m:%d %H:%M:%S'))
|
||||
return str(datetime.strptime(value, "%Y:%m:%d %H:%M:%S"))
|
||||
|
||||
|
||||
def fnumber(value):
|
||||
"""
|
||||
Formats the f-number into a standard format
|
||||
"""
|
||||
return 'ƒ/' + str(value)
|
||||
return "ƒ/" + str(value)
|
||||
|
||||
|
||||
def iso(value):
|
||||
"""
|
||||
Formats the ISO into a standard format
|
||||
"""
|
||||
return 'ISO ' + str(value)
|
||||
return "ISO " + str(value)
|
||||
|
||||
|
||||
def shutter(value):
|
||||
"""
|
||||
Formats the shutter speed into a standard format
|
||||
"""
|
||||
return str(value) + 's'
|
||||
return str(value) + "s"
|
||||
|
||||
|
||||
def focal_length(value):
|
||||
|
@ -50,27 +50,23 @@ def focal_length(value):
|
|||
Formats the focal length into a standard format
|
||||
"""
|
||||
try:
|
||||
return str(value[0] / value[1]) + 'mm'
|
||||
return str(value[0] / value[1]) + "mm"
|
||||
except TypeError:
|
||||
return str(value) + 'mm'
|
||||
return str(value) + "mm"
|
||||
|
||||
|
||||
def exposure(value):
|
||||
"""
|
||||
Formats the exposure value into a standard format
|
||||
"""
|
||||
return str(value) + 'EV'
|
||||
return str(value) + "EV"
|
||||
|
||||
|
||||
def color_space(value):
|
||||
"""
|
||||
Maps the value of the color space to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: 'Reserved',
|
||||
1: 'sRGB',
|
||||
65535: 'Uncalibrated'
|
||||
}
|
||||
value_map = {0: "Reserved", 1: "sRGB", 65535: "Uncalibrated"}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
except KeyError:
|
||||
|
@ -82,28 +78,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',
|
||||
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'
|
||||
0: "Flash did not fire",
|
||||
1: "Flash 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",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -116,15 +112,15 @@ def exposure_program(value):
|
|||
Maps the value of the exposure program to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: 'Not defined',
|
||||
1: 'Manual',
|
||||
2: 'Normal program',
|
||||
3: 'Aperture priority',
|
||||
4: 'Shutter priority',
|
||||
5: 'Creative program',
|
||||
6: 'Action program',
|
||||
7: 'Portrait mode',
|
||||
8: 'Landscape mode'
|
||||
0: "Not defined",
|
||||
1: "Manual",
|
||||
2: "Normal program",
|
||||
3: "Aperture priority",
|
||||
4: "Shutter priority",
|
||||
5: "Creative program",
|
||||
6: "Action program",
|
||||
7: "Portrait mode",
|
||||
8: "Landscape mode",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -137,14 +133,14 @@ def metering_mode(value):
|
|||
Maps the value of the metering mode to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: 'Unknown',
|
||||
1: 'Average',
|
||||
2: 'Center-Weighted Average',
|
||||
3: 'Spot',
|
||||
4: 'Multi-Spot',
|
||||
5: 'Pattern',
|
||||
6: 'Partial',
|
||||
255: 'Other'
|
||||
0: "Unknown",
|
||||
1: "Average",
|
||||
2: "Center-Weighted Average",
|
||||
3: "Spot",
|
||||
4: "Multi-Spot",
|
||||
5: "Pattern",
|
||||
6: "Partial",
|
||||
255: "Other",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -156,11 +152,7 @@ def resolution_unit(value):
|
|||
"""
|
||||
Maps the value of the resolution unit to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
1: 'No absolute unit of measurement',
|
||||
2: 'Inch',
|
||||
3: 'Centimeter'
|
||||
}
|
||||
value_map = {1: "No absolute unit of measurement", 2: "Inch", 3: "Centimeter"}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
except KeyError:
|
||||
|
@ -172,27 +164,27 @@ def light_source(value):
|
|||
Maps the value of the light source to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: 'Unknown',
|
||||
1: 'Daylight',
|
||||
2: 'Fluorescent',
|
||||
3: 'Tungsten (incandescent light)',
|
||||
4: 'Flash',
|
||||
9: 'Fine weather',
|
||||
10: 'Cloudy weather',
|
||||
11: 'Shade',
|
||||
12: 'Daylight fluorescent (D 5700 - 7100K)',
|
||||
13: 'Day white fluorescent (N 4600 - 5400K)',
|
||||
14: 'Cool white fluorescent (W 3900 - 4500K)',
|
||||
15: 'White fluorescent (WW 3200 - 3700K)',
|
||||
17: 'Standard light A',
|
||||
18: 'Standard light B',
|
||||
19: 'Standard light C',
|
||||
20: 'D55',
|
||||
21: 'D65',
|
||||
22: 'D75',
|
||||
23: 'D50',
|
||||
24: 'ISO studio tungsten',
|
||||
255: 'Other light source',
|
||||
0: "Unknown",
|
||||
1: "Daylight",
|
||||
2: "Fluorescent",
|
||||
3: "Tungsten (incandescent light)",
|
||||
4: "Flash",
|
||||
9: "Fine weather",
|
||||
10: "Cloudy weather",
|
||||
11: "Shade",
|
||||
12: "Daylight fluorescent (D 5700 - 7100K)",
|
||||
13: "Day white fluorescent (N 4600 - 5400K)",
|
||||
14: "Cool white fluorescent (W 3900 - 4500K)",
|
||||
15: "White fluorescent (WW 3200 - 3700K)",
|
||||
17: "Standard light A",
|
||||
18: "Standard light B",
|
||||
19: "Standard light C",
|
||||
20: "D55",
|
||||
21: "D65",
|
||||
22: "D75",
|
||||
23: "D50",
|
||||
24: "ISO studio tungsten",
|
||||
255: "Other light source",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -205,10 +197,10 @@ def scene_capture_type(value):
|
|||
Maps the value of the scene capture type to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: 'Standard',
|
||||
1: 'Landscape',
|
||||
2: 'Portrait',
|
||||
3: 'Night scene',
|
||||
0: "Standard",
|
||||
1: "Landscape",
|
||||
2: "Portrait",
|
||||
3: "Night scene",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -221,8 +213,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 white balance",
|
||||
1: "Manual white balance",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -235,9 +227,9 @@ def exposure_mode(value):
|
|||
Maps the value of the exposure mode to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: 'Auto exposure',
|
||||
1: 'Manual exposure',
|
||||
2: 'Auto bracket',
|
||||
0: "Auto exposure",
|
||||
1: "Manual exposure",
|
||||
2: "Auto bracket",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -250,22 +242,14 @@ def sensitivity_type(value):
|
|||
Maps the value of the sensitivity type to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0:
|
||||
'Unknown',
|
||||
1:
|
||||
'Standard Output Sensitivity',
|
||||
2:
|
||||
'Recommended Exposure Index',
|
||||
3:
|
||||
'ISO Speed',
|
||||
4:
|
||||
'Standard Output Sensitivity and Recommended Exposure Index',
|
||||
5:
|
||||
'Standard Output Sensitivity and ISO Speed',
|
||||
6:
|
||||
'Recommended Exposure Index and ISO Speed',
|
||||
7:
|
||||
'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed',
|
||||
0: "Unknown",
|
||||
1: "Standard Output Sensitivity",
|
||||
2: "Recommended Exposure Index",
|
||||
3: "ISO Speed",
|
||||
4: "Standard Output Sensitivity and Recommended Exposure Index",
|
||||
5: "Standard Output Sensitivity and ISO Speed",
|
||||
6: "Recommended Exposure Index and ISO Speed",
|
||||
7: "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -278,7 +262,7 @@ def lens_specification(value):
|
|||
Maps the value of the lens specification to a human readable format
|
||||
"""
|
||||
try:
|
||||
return str(value[0] / value[1]) + 'mm - ' + str(value[2] / value[3]) + 'mm'
|
||||
return str(value[0] / value[1]) + "mm - " + str(value[2] / value[3]) + "mm"
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
|
@ -288,55 +272,55 @@ def compression_type(value):
|
|||
Maps the value of the compression type to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
1: 'Uncompressed',
|
||||
2: 'CCITT 1D',
|
||||
3: 'T4/Group 3 Fax',
|
||||
4: 'T6/Group 4 Fax',
|
||||
5: 'LZW',
|
||||
6: 'JPEG (old-style)',
|
||||
7: 'JPEG',
|
||||
8: 'Adobe Deflate',
|
||||
9: 'JBIG B&W',
|
||||
10: 'JBIG Color',
|
||||
99: 'JPEG',
|
||||
262: 'Kodak 262',
|
||||
32766: 'Next',
|
||||
32767: 'Sony ARW Compressed',
|
||||
32769: 'Packed RAW',
|
||||
32770: 'Samsung SRW Compressed',
|
||||
32771: 'CCIRLEW',
|
||||
32772: 'Samsung SRW Compressed 2',
|
||||
32773: 'PackBits',
|
||||
32809: 'Thunderscan',
|
||||
32867: 'Kodak KDC Compressed',
|
||||
32895: 'IT8CTPAD',
|
||||
32896: 'IT8LW',
|
||||
32897: 'IT8MP',
|
||||
32898: 'IT8BL',
|
||||
32908: 'PixarFilm',
|
||||
32909: 'PixarLog',
|
||||
32946: 'Deflate',
|
||||
32947: 'DCS',
|
||||
33003: 'Aperio JPEG 2000 YCbCr',
|
||||
33005: 'Aperio JPEG 2000 RGB',
|
||||
34661: 'JBIG',
|
||||
34676: 'SGILog',
|
||||
34677: 'SGILog24',
|
||||
34712: 'JPEG 2000',
|
||||
34713: 'Nikon NEF Compressed',
|
||||
34715: 'JBIG2 TIFF FX',
|
||||
34718: '(MDI) Binary Level Codec',
|
||||
34719: '(MDI) Progressive Transform Codec',
|
||||
34720: '(MDI) Vector',
|
||||
34887: 'ESRI Lerc',
|
||||
34892: 'Lossy JPEG',
|
||||
34925: 'LZMA2',
|
||||
34926: 'Zstd',
|
||||
34927: 'WebP',
|
||||
34933: 'PNG',
|
||||
34934: 'JPEG XR',
|
||||
65000: 'Kodak DCR Compressed',
|
||||
65535: 'Pentax PEF Compressed',
|
||||
1: "Uncompressed",
|
||||
2: "CCITT 1D",
|
||||
3: "T4/Group 3 Fax",
|
||||
4: "T6/Group 4 Fax",
|
||||
5: "LZW",
|
||||
6: "JPEG (old-style)",
|
||||
7: "JPEG",
|
||||
8: "Adobe Deflate",
|
||||
9: "JBIG B&W",
|
||||
10: "JBIG Color",
|
||||
99: "JPEG",
|
||||
262: "Kodak 262",
|
||||
32766: "Next",
|
||||
32767: "Sony ARW Compressed",
|
||||
32769: "Packed RAW",
|
||||
32770: "Samsung SRW Compressed",
|
||||
32771: "CCIRLEW",
|
||||
32772: "Samsung SRW Compressed 2",
|
||||
32773: "PackBits",
|
||||
32809: "Thunderscan",
|
||||
32867: "Kodak KDC Compressed",
|
||||
32895: "IT8CTPAD",
|
||||
32896: "IT8LW",
|
||||
32897: "IT8MP",
|
||||
32898: "IT8BL",
|
||||
32908: "PixarFilm",
|
||||
32909: "PixarLog",
|
||||
32946: "Deflate",
|
||||
32947: "DCS",
|
||||
33003: "Aperio JPEG 2000 YCbCr",
|
||||
33005: "Aperio JPEG 2000 RGB",
|
||||
34661: "JBIG",
|
||||
34676: "SGILog",
|
||||
34677: "SGILog24",
|
||||
34712: "JPEG 2000",
|
||||
34713: "Nikon NEF Compressed",
|
||||
34715: "JBIG2 TIFF FX",
|
||||
34718: "(MDI) Binary Level Codec",
|
||||
34719: "(MDI) Progressive Transform Codec",
|
||||
34720: "(MDI) Vector",
|
||||
34887: "ESRI Lerc",
|
||||
34892: "Lossy JPEG",
|
||||
34925: "LZMA2",
|
||||
34926: "Zstd",
|
||||
34927: "WebP",
|
||||
34933: "PNG",
|
||||
34934: "JPEG XR",
|
||||
65000: "Kodak DCR Compressed",
|
||||
65535: "Pentax PEF Compressed",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -349,15 +333,15 @@ def orientation(value):
|
|||
Maps the value of the orientation to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: 'Undefined',
|
||||
1: 'Horizontal (normal)',
|
||||
2: 'Mirror horizontal',
|
||||
3: 'Rotate 180',
|
||||
4: 'Mirror vertical',
|
||||
5: 'Mirror horizontal and rotate 270 CW',
|
||||
6: 'Rotate 90 CW',
|
||||
7: 'Mirror horizontal and rotate 90 CW',
|
||||
8: 'Rotate 270 CW',
|
||||
0: "Undefined",
|
||||
1: "Horizontal (normal)",
|
||||
2: "Mirror horizontal",
|
||||
3: "Rotate 180",
|
||||
4: "Mirror vertical",
|
||||
5: "Mirror horizontal and rotate 270 CW",
|
||||
6: "Rotate 90 CW",
|
||||
7: "Mirror horizontal and rotate 90 CW",
|
||||
8: "Rotate 270 CW",
|
||||
}
|
||||
try:
|
||||
return value_map[int(value)]
|
||||
|
@ -370,16 +354,16 @@ def components_configuration(value):
|
|||
Maps the value of the components configuration to a human readable format
|
||||
"""
|
||||
value_map = {
|
||||
0: '',
|
||||
1: 'Y',
|
||||
2: 'Cb',
|
||||
3: 'Cr',
|
||||
4: 'R',
|
||||
5: 'G',
|
||||
6: 'B',
|
||||
0: "",
|
||||
1: "Y",
|
||||
2: "Cb",
|
||||
3: "Cr",
|
||||
4: "R",
|
||||
5: "G",
|
||||
6: "B",
|
||||
}
|
||||
try:
|
||||
return ''.join([value_map[int(x)] for x in value])
|
||||
return "".join([value_map[int(x)] for x in value])
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
@ -388,18 +372,18 @@ def rating(value):
|
|||
"""
|
||||
Maps the value of the rating to a human readable format
|
||||
"""
|
||||
return str(value) + ' stars'
|
||||
return str(value) + " stars"
|
||||
|
||||
|
||||
def rating_percent(value):
|
||||
"""
|
||||
Maps the value of the rating to a human readable format
|
||||
"""
|
||||
return str(value) + '%'
|
||||
return str(value) + "%"
|
||||
|
||||
|
||||
def pixel_dimension(value):
|
||||
"""
|
||||
Maps the value of the pixel dimension to a human readable format
|
||||
"""
|
||||
return str(value) + 'px'
|
||||
return str(value) + "px"
|
||||
|
|
|
@ -4,66 +4,66 @@ Mapping for metadata
|
|||
"""
|
||||
|
||||
PHOTOGRAHER_MAPPING = {
|
||||
'Artist': ['Artist'],
|
||||
'UserComment': ['Comment'],
|
||||
'ImageDescription': ['Description'],
|
||||
'Copyright': ['Copyright'],
|
||||
"Artist": ["Artist"],
|
||||
"UserComment": ["Comment"],
|
||||
"ImageDescription": ["Description"],
|
||||
"Copyright": ["Copyright"],
|
||||
}
|
||||
CAMERA_MAPPING = {
|
||||
'Model': ['Model'],
|
||||
'Make': ['Make'],
|
||||
'BodySerialNumber': ['Camera Type'],
|
||||
'LensMake': ['Lens Make'],
|
||||
'LenseModel': ['Lens Model'],
|
||||
'LensSpecification': ['Lens Specification', 'lens_specification'],
|
||||
'ComponentsConfiguration': ['Components Configuration', 'components_configuration'],
|
||||
'DateTime': ['Date Processed', 'date_format'],
|
||||
'DateTimeDigitized': ['Time Digitized', 'date_format'],
|
||||
'OffsetTime': ['Time Offset'],
|
||||
'OffsetTimeOriginal': ['Time Offset - Original'],
|
||||
'OffsetTimeDigitized': ['Time Offset - Digitized'],
|
||||
'DateTimeOriginal': ['Date Original', 'date_format'],
|
||||
'FNumber': ['F-Stop', 'fnumber'],
|
||||
'FocalLength': ['Focal Length', 'focal_length'],
|
||||
'FocalLengthIn35mmFilm': ['Focal Length (35mm format)', 'focal_length'],
|
||||
'MaxApertureValue': ['Max Aperture', 'fnumber'],
|
||||
'ApertureValue': ['Aperture', 'fnumber'],
|
||||
'ShutterSpeedValue': ['Shutter Speed', 'shutter'],
|
||||
'ISOSpeedRatings': ['ISO Speed Ratings', 'iso'],
|
||||
'ISOSpeed': ['ISO Speed', 'iso'],
|
||||
'SensitivityType': ['Sensitivity Type', 'sensitivity_type'],
|
||||
'ExposureBiasValue': ['Exposure Bias', 'exposure'],
|
||||
'ExposureTime': ['Exposure Time', 'shutter'],
|
||||
'ExposureMode': ['Exposure Mode', 'exposure_mode'],
|
||||
'ExposureProgram': ['Exposure Program', 'exposure_program'],
|
||||
'WhiteBalance': ['White Balance', 'white_balance'],
|
||||
'Flash': ['Flash', 'flash'],
|
||||
'MeteringMode': ['Metering Mode', 'metering_mode'],
|
||||
'LightSource': ['Light Source', 'light_source'],
|
||||
'SceneCaptureType': ['Scene Capture Type', 'scene_capture_type'],
|
||||
"Model": ["Model"],
|
||||
"Make": ["Make"],
|
||||
"BodySerialNumber": ["Camera Type"],
|
||||
"LensMake": ["Lens Make"],
|
||||
"LenseModel": ["Lens Model"],
|
||||
"LensSpecification": ["Lens Specification", "lens_specification"],
|
||||
"ComponentsConfiguration": ["Components Configuration", "components_configuration"],
|
||||
"DateTime": ["Date Processed", "date_format"],
|
||||
"DateTimeDigitized": ["Time Digitized", "date_format"],
|
||||
"OffsetTime": ["Time Offset"],
|
||||
"OffsetTimeOriginal": ["Time Offset - Original"],
|
||||
"OffsetTimeDigitized": ["Time Offset - Digitized"],
|
||||
"DateTimeOriginal": ["Date Original", "date_format"],
|
||||
"FNumber": ["F-Stop", "fnumber"],
|
||||
"FocalLength": ["Focal Length", "focal_length"],
|
||||
"FocalLengthIn35mmFilm": ["Focal Length (35mm format)", "focal_length"],
|
||||
"MaxApertureValue": ["Max Aperture", "fnumber"],
|
||||
"ApertureValue": ["Aperture", "fnumber"],
|
||||
"ShutterSpeedValue": ["Shutter Speed", "shutter"],
|
||||
"ISOSpeedRatings": ["ISO Speed Ratings", "iso"],
|
||||
"ISOSpeed": ["ISO Speed", "iso"],
|
||||
"SensitivityType": ["Sensitivity Type", "sensitivity_type"],
|
||||
"ExposureBiasValue": ["Exposure Bias", "exposure"],
|
||||
"ExposureTime": ["Exposure Time", "shutter"],
|
||||
"ExposureMode": ["Exposure Mode", "exposure_mode"],
|
||||
"ExposureProgram": ["Exposure Program", "exposure_program"],
|
||||
"WhiteBalance": ["White Balance", "white_balance"],
|
||||
"Flash": ["Flash", "flash"],
|
||||
"MeteringMode": ["Metering Mode", "metering_mode"],
|
||||
"LightSource": ["Light Source", "light_source"],
|
||||
"SceneCaptureType": ["Scene Capture Type", "scene_capture_type"],
|
||||
}
|
||||
SOFTWARE_MAPPING = {
|
||||
'Software': ['Software'],
|
||||
'ColorSpace': ['Colour Space', 'color_space'],
|
||||
'Compression': ['Compression', 'compression_type'],
|
||||
"Software": ["Software"],
|
||||
"ColorSpace": ["Colour Space", "color_space"],
|
||||
"Compression": ["Compression", "compression_type"],
|
||||
}
|
||||
FILE_MAPPING = {
|
||||
'FileName': ['Name'],
|
||||
'FileSize': ['Size', 'human_size'],
|
||||
'FileFormat': ['Format'],
|
||||
'FileWidth': ['Width', 'pixel_dimension'],
|
||||
'FileHeight': ['Height', 'pixel_dimension'],
|
||||
'Orientation': ['Orientation', 'orientation'],
|
||||
'XResolution': ['X-resolution'],
|
||||
'YResolution': ['Y-resolution'],
|
||||
'ResolutionUnit': ['Resolution Units', 'resolution_unit'],
|
||||
'Rating': ['Rating', 'rating'],
|
||||
'RatingPercent': ['Rating Percent', 'rating_percent'],
|
||||
"FileName": ["Name"],
|
||||
"FileSize": ["Size", "human_size"],
|
||||
"FileFormat": ["Format"],
|
||||
"FileWidth": ["Width", "pixel_dimension"],
|
||||
"FileHeight": ["Height", "pixel_dimension"],
|
||||
"Orientation": ["Orientation", "orientation"],
|
||||
"XResolution": ["X-resolution"],
|
||||
"YResolution": ["Y-resolution"],
|
||||
"ResolutionUnit": ["Resolution Units", "resolution_unit"],
|
||||
"Rating": ["Rating", "rating"],
|
||||
"RatingPercent": ["Rating Percent", "rating_percent"],
|
||||
}
|
||||
|
||||
EXIF_MAPPING = [
|
||||
('Photographer', PHOTOGRAHER_MAPPING),
|
||||
('Camera', CAMERA_MAPPING),
|
||||
('Software', SOFTWARE_MAPPING),
|
||||
('File', FILE_MAPPING)
|
||||
("Photographer", PHOTOGRAHER_MAPPING),
|
||||
("Camera", CAMERA_MAPPING),
|
||||
("Software", SOFTWARE_MAPPING),
|
||||
("File", FILE_MAPPING),
|
||||
]
|
||||
|
|
|
@ -10,12 +10,12 @@ from gallery import db
|
|||
from gallery.utils import contrast
|
||||
|
||||
|
||||
blueprint = Blueprint('group', __name__, url_prefix='/group')
|
||||
blueprint = Blueprint("group", __name__, url_prefix="/group")
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route('/', methods=['GET'])
|
||||
@blueprint.route("/", methods=["GET"])
|
||||
def groups():
|
||||
"""
|
||||
Group overview, shows all image groups
|
||||
|
@ -24,116 +24,134 @@ def groups():
|
|||
|
||||
# For each group, get the 3 most recent images
|
||||
for group in groups:
|
||||
group.author_username = (db_session.query(db.Users.username)
|
||||
group.author_username = (
|
||||
db_session.query(db.Users.username)
|
||||
.filter(db.Users.id == group.author_id)
|
||||
.first()[0])
|
||||
.first()[0]
|
||||
)
|
||||
|
||||
# Get the 3 most recent images
|
||||
images = (db_session.query(db.GroupJunction.post_id)
|
||||
images = (
|
||||
db_session.query(db.GroupJunction.post_id)
|
||||
.filter(db.GroupJunction.group_id == group.id)
|
||||
.order_by(db.GroupJunction.date_added.desc())
|
||||
.limit(3))
|
||||
.limit(3)
|
||||
)
|
||||
|
||||
# For each image, get the image data and add it to the group item
|
||||
group.images = []
|
||||
for image in images:
|
||||
group.images.append(db_session.query(db.Posts.filename, db.Posts.alt,
|
||||
db.Posts.colours, db.Posts.id)
|
||||
group.images.append(
|
||||
db_session.query(
|
||||
db.Posts.filename, db.Posts.alt, db.Posts.colours, db.Posts.id
|
||||
)
|
||||
.filter(db.Posts.id == image[0])
|
||||
.first())
|
||||
.first()
|
||||
)
|
||||
|
||||
return render_template('list.html', groups=groups)
|
||||
return render_template("list.html", groups=groups)
|
||||
|
||||
|
||||
@blueprint.route('/<int:group_id>')
|
||||
@blueprint.route("/<int:group_id>")
|
||||
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())
|
||||
group = db_session.query(db.Groups).filter(db.Groups.id == group_id).first()
|
||||
|
||||
if group is None:
|
||||
abort(404, 'Group not found! D:')
|
||||
abort(404, "Group not found! D:")
|
||||
|
||||
# Get the group's author username
|
||||
group.author_username = (db_session.query(db.Users.username)
|
||||
group.author_username = (
|
||||
db_session.query(db.Users.username)
|
||||
.filter(db.Users.id == group.author_id)
|
||||
.first()[0])
|
||||
.first()[0]
|
||||
)
|
||||
|
||||
# Get all images in the group from the junction table
|
||||
junction = (db_session.query(db.GroupJunction.post_id)
|
||||
junction = (
|
||||
db_session.query(db.GroupJunction.post_id)
|
||||
.filter(db.GroupJunction.group_id == group_id)
|
||||
.order_by(db.GroupJunction.date_added.desc())
|
||||
.all())
|
||||
.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(
|
||||
db_session.query(db.Posts).filter(db.Posts.id == image[0]).first()
|
||||
)
|
||||
|
||||
# Check contrast for the first image in the group for the banner
|
||||
text_colour = 'rgb(var(--fg-black))'
|
||||
text_colour = "rgb(var(--fg-black))"
|
||||
if images:
|
||||
text_colour = contrast.contrast(images[0].colours[0],
|
||||
'rgb(var(--fg-black))',
|
||||
'rgb(var(--fg-white))')
|
||||
text_colour = contrast.contrast(
|
||||
images[0].colours[0], "rgb(var(--fg-black))", "rgb(var(--fg-white))"
|
||||
)
|
||||
|
||||
return render_template('group.html',
|
||||
group=group,
|
||||
images=images,
|
||||
text_colour=text_colour)
|
||||
return render_template(
|
||||
"group.html", group=group, images=images, text_colour=text_colour
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route('/<int:group_id>/<int:image_id>')
|
||||
@blueprint.route("/<int:group_id>/<int:image_id>")
|
||||
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())
|
||||
image = db_session.query(db.Posts).filter(db.Posts.id == image_id).first()
|
||||
if image is None:
|
||||
abort(404, 'Image not found')
|
||||
abort(404, "Image not found")
|
||||
|
||||
# Get the image's author username
|
||||
image.author_username = (db_session.query(db.Users.username)
|
||||
image.author_username = (
|
||||
db_session.query(db.Users.username)
|
||||
.filter(db.Users.id == image.author_id)
|
||||
.first()[0])
|
||||
.first()[0]
|
||||
)
|
||||
|
||||
# Get all groups the image is in
|
||||
groups = (db_session.query(db.GroupJunction.group_id)
|
||||
groups = (
|
||||
db_session.query(db.GroupJunction.group_id)
|
||||
.filter(db.GroupJunction.post_id == image_id)
|
||||
.all())
|
||||
.all()
|
||||
)
|
||||
|
||||
# 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)
|
||||
image.groups.append(
|
||||
db_session.query(db.Groups.id, db.Groups.name)
|
||||
.filter(db.Groups.id == group[0])
|
||||
.first())
|
||||
.first()
|
||||
)
|
||||
|
||||
# Get the next and previous images in the group
|
||||
next_url = (db_session.query(db.GroupJunction.post_id)
|
||||
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())
|
||||
.first())
|
||||
prev_url = (db_session.query(db.GroupJunction.post_id)
|
||||
.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())
|
||||
.first())
|
||||
.first()
|
||||
)
|
||||
|
||||
# If there is a next or previous image, get the URL for it
|
||||
if next_url:
|
||||
next_url = url_for('group.group_post', group_id=group_id, image_id=next_url[0])
|
||||
next_url = url_for("group.group_post", group_id=group_id, image_id=next_url[0])
|
||||
if prev_url:
|
||||
prev_url = url_for('group.group_post', group_id=group_id, image_id=prev_url[0])
|
||||
prev_url = url_for("group.group_post", group_id=group_id, image_id=prev_url[0])
|
||||
|
||||
return render_template('image.html', image=image, next_url=next_url, prev_url=prev_url)
|
||||
return render_template(
|
||||
"image.html", image=image, next_url=next_url, prev_url=prev_url
|
||||
)
|
||||
|
|
|
@ -9,12 +9,12 @@ from sqlalchemy.orm import sessionmaker
|
|||
from gallery import db
|
||||
|
||||
|
||||
blueprint = Blueprint('image', __name__, url_prefix='/image')
|
||||
blueprint = Blueprint("image", __name__, url_prefix="/image")
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route('/<int:image_id>')
|
||||
@blueprint.route("/<int:image_id>")
|
||||
def image(image_id):
|
||||
"""
|
||||
Image view, shows the image and its metadata
|
||||
|
@ -22,48 +22,55 @@ def image(image_id):
|
|||
# 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 :<')
|
||||
abort(404, "Image not found :<")
|
||||
|
||||
# Get the image's author username
|
||||
image.author_username = (db_session.query(db.Users.username)
|
||||
image.author_username = (
|
||||
db_session.query(db.Users.username)
|
||||
.filter(db.Users.id == image.author_id)
|
||||
.first()[0])
|
||||
.first()[0]
|
||||
)
|
||||
|
||||
# Get the image's groups
|
||||
groups = (db_session.query(db.GroupJunction.group_id)
|
||||
groups = (
|
||||
db_session.query(db.GroupJunction.group_id)
|
||||
.filter(db.GroupJunction.post_id == image_id)
|
||||
.all())
|
||||
.all()
|
||||
)
|
||||
|
||||
# For each group, get the group data and add it to the image item
|
||||
image.groups = []
|
||||
for group in groups:
|
||||
image.groups.append(db_session.query(db.Groups.id, db.Groups.name)
|
||||
image.groups.append(
|
||||
db_session.query(db.Groups.id, db.Groups.name)
|
||||
.filter(db.Groups.id == group[0])
|
||||
.first())
|
||||
.first()
|
||||
)
|
||||
|
||||
# Get the next and previous images
|
||||
# Check if there is a group ID set
|
||||
next_url = (db_session.query(db.Posts.id)
|
||||
next_url = (
|
||||
db_session.query(db.Posts.id)
|
||||
.filter(db.Posts.id > image_id)
|
||||
.order_by(db.Posts.id.asc())
|
||||
.first())
|
||||
prev_url = (db_session.query(db.Posts.id)
|
||||
.first()
|
||||
)
|
||||
prev_url = (
|
||||
db_session.query(db.Posts.id)
|
||||
.filter(db.Posts.id < image_id)
|
||||
.order_by(db.Posts.id.desc())
|
||||
.first())
|
||||
.first()
|
||||
)
|
||||
|
||||
# If there is a next or previous image, get the url
|
||||
if next_url:
|
||||
next_url = url_for('image.image', image_id=next_url[0])
|
||||
next_url = url_for("image.image", image_id=next_url[0])
|
||||
if prev_url:
|
||||
prev_url = url_for('image.image', image_id=prev_url[0])
|
||||
|
||||
prev_url = url_for("image.image", image_id=prev_url[0])
|
||||
|
||||
# Yoink all the images in the database
|
||||
total_images = (db_session.query(db.Posts.id)
|
||||
.order_by(db.Posts.id.desc())
|
||||
.all())
|
||||
limit = current_app.config['UPLOAD_CONF']['max-load']
|
||||
total_images = db_session.query(db.Posts.id).order_by(db.Posts.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
|
||||
if len(total_images) <= limit:
|
||||
|
@ -72,11 +79,16 @@ def image(image_id):
|
|||
# How many pages should there be
|
||||
for i in range(ceil(len(total_images) / limit)):
|
||||
# Slice the list of IDs into chunks of the limit
|
||||
for j in total_images[i * limit:(i + 1) * limit]:
|
||||
for j in total_images[i * limit : (i + 1) * limit]:
|
||||
# Is our image in this chunk?
|
||||
if image_id in j:
|
||||
return_page = i + 1
|
||||
break
|
||||
|
||||
return render_template('image.html', image=image, next_url=next_url,
|
||||
prev_url=prev_url, return_page=return_page)
|
||||
return render_template(
|
||||
"image.html",
|
||||
image=image,
|
||||
next_url=next_url,
|
||||
prev_url=prev_url,
|
||||
return_page=return_page,
|
||||
)
|
||||
|
|
|
@ -10,40 +10,50 @@ from sqlalchemy.orm import sessionmaker
|
|||
from gallery import db
|
||||
|
||||
|
||||
blueprint = Blueprint('gallery', __name__)
|
||||
blueprint = Blueprint("gallery", __name__)
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route('/')
|
||||
@blueprint.route("/")
|
||||
def index():
|
||||
"""
|
||||
Home page of the website, shows the feed of the latest images
|
||||
"""
|
||||
# meme
|
||||
if request.args.get('coffee') == 'please':
|
||||
if request.args.get("coffee") == "please":
|
||||
abort(418)
|
||||
|
||||
# pagination, defaults to page 1 if no page is specified
|
||||
page = request.args.get('page', default=1, type=int)
|
||||
limit = current_app.config['UPLOAD_CONF']['max-load']
|
||||
page = request.args.get("page", default=1, type=int)
|
||||
limit = current_app.config["UPLOAD_CONF"]["max-load"]
|
||||
|
||||
# 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()
|
||||
pages = ceil(max(total_images, limit) / limit)
|
||||
if page > pages:
|
||||
abort(404, 'You have reached the far and beyond, ' +
|
||||
'but you will not find your answers here.')
|
||||
abort(
|
||||
404,
|
||||
"You have reached the far and beyond, "
|
||||
+ "but you will not find your answers here.",
|
||||
)
|
||||
|
||||
# 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)
|
||||
images = (
|
||||
db_session.query(
|
||||
db.Posts.filename,
|
||||
db.Posts.alt,
|
||||
db.Posts.colours,
|
||||
db.Posts.created_at,
|
||||
db.Posts.id,
|
||||
)
|
||||
.order_by(db.Posts.id.desc())
|
||||
.offset((page - 1) * limit)
|
||||
.limit(limit)
|
||||
.all())
|
||||
.all()
|
||||
)
|
||||
|
||||
return render_template('index.html', images=images,
|
||||
total_images=total_images,
|
||||
pages=pages, page=page)
|
||||
return render_template(
|
||||
"index.html", images=images, total_images=total_images, pages=pages, page=page
|
||||
)
|
||||
|
|
|
@ -9,31 +9,31 @@ from sqlalchemy.orm import sessionmaker
|
|||
from gallery import db
|
||||
|
||||
|
||||
blueprint = Blueprint('profile', __name__, url_prefix='/profile')
|
||||
blueprint = Blueprint("profile", __name__, url_prefix="/profile")
|
||||
db_session = sessionmaker(bind=db.engine)
|
||||
db_session = db_session()
|
||||
|
||||
|
||||
@blueprint.route('/profile')
|
||||
@blueprint.route("/profile")
|
||||
def profile():
|
||||
"""
|
||||
Profile overview, shows all profiles on the onlylegs gallery
|
||||
"""
|
||||
user_id = request.args.get('id', default=None, type=int)
|
||||
user_id = request.args.get("id", default=None, type=int)
|
||||
|
||||
# If there is no userID set, check if the user is logged in and display their profile
|
||||
if not user_id:
|
||||
if current_user.is_authenticated:
|
||||
user_id = current_user.id
|
||||
else:
|
||||
abort(404, 'You must be logged in to view your own profile!')
|
||||
abort(404, "You must be logged in to view your own profile!")
|
||||
|
||||
# Get the user's data
|
||||
user = db_session.query(db.Users).filter(db.Users.id == user_id).first()
|
||||
|
||||
if not user:
|
||||
abort(404, 'User not found :c')
|
||||
abort(404, "User not found :c")
|
||||
|
||||
images = db_session.query(db.Posts).filter(db.Posts.author_id == user_id).all()
|
||||
|
||||
return render_template('profile.html', user=user, images=images)
|
||||
return render_template("profile.html", user=user, images=images)
|
||||
|
|
|
@ -4,40 +4,40 @@ OnlyLegs - Settings page
|
|||
from flask import Blueprint, render_template
|
||||
from flask_login import login_required
|
||||
|
||||
blueprint = Blueprint('settings', __name__, url_prefix='/settings')
|
||||
blueprint = Blueprint("settings", __name__, url_prefix="/settings")
|
||||
|
||||
|
||||
@blueprint.route('/')
|
||||
@blueprint.route("/")
|
||||
@login_required
|
||||
def general():
|
||||
"""
|
||||
General settings page
|
||||
"""
|
||||
return render_template('settings/general.html')
|
||||
return render_template("settings/general.html")
|
||||
|
||||
|
||||
@blueprint.route('/server')
|
||||
@blueprint.route("/server")
|
||||
@login_required
|
||||
def server():
|
||||
"""
|
||||
Server settings page
|
||||
"""
|
||||
return render_template('settings/server.html')
|
||||
return render_template("settings/server.html")
|
||||
|
||||
|
||||
@blueprint.route('/account')
|
||||
@blueprint.route("/account")
|
||||
@login_required
|
||||
def account():
|
||||
"""
|
||||
Account settings page
|
||||
"""
|
||||
return render_template('settings/account.html')
|
||||
return render_template("settings/account.html")
|
||||
|
||||
|
||||
@blueprint.route('/logs')
|
||||
@blueprint.route("/logs")
|
||||
@login_required
|
||||
def logs():
|
||||
"""
|
||||
Logs settings page
|
||||
"""
|
||||
return render_template('settings/logs.html')
|
||||
return render_template("settings/logs.html")
|
||||
|
|
11
run.py
11
run.py
|
@ -5,7 +5,8 @@ from setup.args import PORT, ADDRESS, WORKERS, DEBUG
|
|||
from setup.configuration import Configuration
|
||||
|
||||
|
||||
print("""
|
||||
print(
|
||||
"""
|
||||
:::::::: :::: ::: ::: ::: ::: ::: ::::::::: ::::::::: ::::::::
|
||||
:+: :+: :+:+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+:
|
||||
+:+ +:+ :+:+:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+
|
||||
|
@ -15,7 +16,8 @@ print("""
|
|||
######## ### #### ########## ### ########## ######### ######### ########
|
||||
|
||||
Created by Fluffy Bean - Version 23.04.06
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
# Run pre-startup checks and load configuration
|
||||
|
@ -24,6 +26,7 @@ Configuration()
|
|||
|
||||
if DEBUG:
|
||||
from gallery import create_app
|
||||
|
||||
create_app().run(host=ADDRESS, port=PORT, debug=True, threaded=True)
|
||||
else:
|
||||
from setup.runner import OnlyLegs # pylint: disable=C0412
|
||||
|
@ -33,8 +36,8 @@ else:
|
|||
sys.argv = [sys.argv[0]]
|
||||
|
||||
options = {
|
||||
'bind': f'{ADDRESS}:{PORT}',
|
||||
'workers': WORKERS,
|
||||
"bind": f"{ADDRESS}:{PORT}",
|
||||
"workers": WORKERS,
|
||||
}
|
||||
|
||||
OnlyLegs(options).run()
|
||||
|
|
|
@ -13,11 +13,17 @@ Startup arguments for the OnlyLegs gallery
|
|||
import argparse
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Run the OnlyLegs gallery')
|
||||
parser.add_argument('-p', '--port', type=int, default=5000, help='Port to run on')
|
||||
parser.add_argument('-a', '--address', type=str, default='127.0.0.0', help='Address to run on')
|
||||
parser.add_argument('-w', '--workers', type=int, default=4, help='Number of workers to run')
|
||||
parser.add_argument('-d', '--debug', action='store_true', help='Run as Flask app in debug mode')
|
||||
parser = argparse.ArgumentParser(description="Run the OnlyLegs gallery")
|
||||
parser.add_argument("-p", "--port", type=int, default=5000, help="Port to run on")
|
||||
parser.add_argument(
|
||||
"-a", "--address", type=str, default="127.0.0.0", help="Address to run on"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-w", "--workers", type=int, default=4, help="Number of workers to run"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d", "--debug", action="store_true", help="Run as Flask app in debug mode"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
|
|
|
@ -9,13 +9,14 @@ import platformdirs
|
|||
import yaml
|
||||
|
||||
|
||||
USER_DIR = platformdirs.user_config_dir('onlylegs')
|
||||
USER_DIR = platformdirs.user_config_dir("onlylegs")
|
||||
|
||||
|
||||
class Configuration:
|
||||
"""
|
||||
Setup the application on first run
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Main setup function
|
||||
|
@ -27,11 +28,11 @@ class Configuration:
|
|||
self.make_dir()
|
||||
|
||||
# Check if the .env file exists
|
||||
if not os.path.exists(os.path.join(USER_DIR, '.env')):
|
||||
if not os.path.exists(os.path.join(USER_DIR, ".env")):
|
||||
self.make_env()
|
||||
|
||||
# Check if the conf.yml file exists
|
||||
if not os.path.exists(os.path.join(USER_DIR, 'conf.yml')):
|
||||
if not os.path.exists(os.path.join(USER_DIR, "conf.yml")):
|
||||
self.make_yaml()
|
||||
|
||||
# Load the config files
|
||||
|
@ -43,8 +44,8 @@ class Configuration:
|
|||
Create the user directory
|
||||
"""
|
||||
os.makedirs(USER_DIR)
|
||||
os.makedirs(os.path.join(USER_DIR, 'instance'))
|
||||
os.makedirs(os.path.join(USER_DIR, 'uploads'))
|
||||
os.makedirs(os.path.join(USER_DIR, "instance"))
|
||||
os.makedirs(os.path.join(USER_DIR, "uploads"))
|
||||
|
||||
print("Created user directory at:", USER_DIR)
|
||||
|
||||
|
@ -54,21 +55,23 @@ class Configuration:
|
|||
Create the .env file with default values
|
||||
"""
|
||||
env_conf = {
|
||||
'FLASK_SECRET': os.urandom(32).hex(),
|
||||
"FLASK_SECRET": os.urandom(32).hex(),
|
||||
}
|
||||
|
||||
with open(os.path.join(USER_DIR, '.env'), encoding='utf-8', mode='w+') as file:
|
||||
with open(os.path.join(USER_DIR, ".env"), encoding="utf-8", mode="w+") as file:
|
||||
for key, value in env_conf.items():
|
||||
file.write(f"{key}={value}\n")
|
||||
|
||||
print("""
|
||||
print(
|
||||
"""
|
||||
####################################################
|
||||
# A NEW KEY WAS GENERATED FOR YOU! PLEASE NOTE #
|
||||
# DOWN THE FLASK_SECRET KEY LOCATED IN YOUR #
|
||||
# .config/onlylegs/.env FOLDER! LOOSING THIS KEY #
|
||||
# WILL RESULT IN YOU BEING UNABLE TO LOG IN! #
|
||||
####################################################
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def make_yaml():
|
||||
|
@ -76,8 +79,8 @@ class Configuration:
|
|||
Create the YAML config file with default values
|
||||
"""
|
||||
is_correct = False
|
||||
email_regex = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
|
||||
username_regex = re.compile(r'\b[A-Za-z0-9._%+-]+\b')
|
||||
email_regex = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b")
|
||||
username_regex = re.compile(r"\b[A-Za-z0-9._%+-]+\b")
|
||||
|
||||
print("\nNo config file found, please enter the following information:")
|
||||
while not is_correct:
|
||||
|
@ -99,47 +102,52 @@ class Configuration:
|
|||
continue
|
||||
|
||||
# Check if user is happy with the values
|
||||
if input("Is this correct? (y/n): ").lower() == 'y':
|
||||
if input("Is this correct? (y/n): ").lower() == "y":
|
||||
is_correct = True
|
||||
|
||||
yaml_conf = {
|
||||
'admin': {
|
||||
'name': name,
|
||||
'username': username,
|
||||
'email': email,
|
||||
"admin": {
|
||||
"name": name,
|
||||
"username": username,
|
||||
"email": email,
|
||||
},
|
||||
'upload': {
|
||||
'allowed-extensions': {
|
||||
'jpg': 'jpeg',
|
||||
'jpeg': 'jpeg',
|
||||
'png': 'png',
|
||||
'webp': 'webp',
|
||||
"upload": {
|
||||
"allowed-extensions": {
|
||||
"jpg": "jpeg",
|
||||
"jpeg": "jpeg",
|
||||
"png": "png",
|
||||
"webp": "webp",
|
||||
},
|
||||
'max-size': 69,
|
||||
'max-load': 50,
|
||||
'rename': 'GWA_{{username}}_{{time}}',
|
||||
"max-size": 69,
|
||||
"max-load": 50,
|
||||
"rename": "GWA_{{username}}_{{time}}",
|
||||
},
|
||||
"website": {
|
||||
"name": "OnlyLegs",
|
||||
"motto": "A gallery built for fast and simple image management!",
|
||||
"language": "en",
|
||||
},
|
||||
'website': {
|
||||
'name': 'OnlyLegs',
|
||||
'motto': 'A gallery built for fast and simple image management!',
|
||||
'language': 'en',
|
||||
}
|
||||
}
|
||||
|
||||
with open(os.path.join(USER_DIR, 'conf.yml'), encoding='utf-8', mode='w+') as file:
|
||||
with open(
|
||||
os.path.join(USER_DIR, "conf.yml"), encoding="utf-8", mode="w+"
|
||||
) as file:
|
||||
yaml.dump(yaml_conf, file, default_flow_style=False)
|
||||
|
||||
print("Generated config file, you can change these values in the settings of the app")
|
||||
print(
|
||||
"Generated config file, you can change these values in the settings of the app"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def logging_config():
|
||||
"""
|
||||
Set the logging config
|
||||
"""
|
||||
logging.getLogger('werkzeug').disabled = True
|
||||
logging.getLogger("werkzeug").disabled = True
|
||||
logging.basicConfig(
|
||||
filename=os.path.join(USER_DIR, 'only.log'),
|
||||
filename=os.path.join(USER_DIR, "only.log"),
|
||||
level=logging.INFO,
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s',
|
||||
encoding='utf-8')
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
format="%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
|
|
@ -9,6 +9,7 @@ class OnlyLegs(Application):
|
|||
"""
|
||||
Gunicorn application
|
||||
"""
|
||||
|
||||
def __init__(self, options={}): # pylint: disable=W0102, W0231
|
||||
self.usage = None
|
||||
self.callable = None
|
||||
|
@ -27,7 +28,7 @@ class OnlyLegs(Application):
|
|||
|
||||
@staticmethod
|
||||
def prog(): # pylint: disable=C0116, E0202
|
||||
return 'OnlyLegs'
|
||||
return "OnlyLegs"
|
||||
|
||||
def load(self):
|
||||
return util.import_app('gallery:create_app()')
|
||||
return util.import_app("gallery:create_app()")
|
||||
|
|
Loading…
Reference in a new issue