Change database structure

Update naming to something more useful
Date filled in automatically
This commit is contained in:
Michał Gdula 2023-04-05 16:35:59 +00:00
parent bf083a85ad
commit 4627498b8d
11 changed files with 83 additions and 89 deletions

View file

@ -54,7 +54,7 @@ def create_app(test_config=None):
# App configuration
app.config.from_mapping(
SECRET_KEY=os.environ.get('FLASK_SECRET'),
DATABASE=os.path.join(app.instance_path, 'gallery.sqlite'),
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'],

View file

@ -3,9 +3,7 @@ OnlyLegs - Authentication
User registration, login and logout and locking access to pages behind a login
"""
import re
from uuid import uuid4
import logging
from datetime import datetime as dt
from flask import Blueprint, flash, redirect, request, url_for, abort, jsonify
from werkzeug.security import check_password_hash, generate_password_hash
@ -34,7 +32,7 @@ def login():
user = db_session.query(db.Users).filter_by(username=username).first()
if not user and not check_password_hash(user.password, password):
if not user or not check_password_hash(user.password, password):
logging.error('Login attempt from %s', request.remote_addr)
error.append('Username or Password is incorrect!')
@ -45,7 +43,7 @@ def login():
logging.info('User %s logged in from %s', username, request.remote_addr)
flash(['Logged in successfully!', '4'])
return 'gwa gwa'
return 'ok', 200
@blueprint.route('/register', methods=['POST'])
@ -87,16 +85,16 @@ def register():
# If there are errors, return them
if error:
return jsonify(error)
print(error)
return jsonify(error), 400
register_user = db.Users(alt_id=str(uuid4()), username=username, email=email,
password=generate_password_hash(password, method='sha256'),
created_at=dt.utcnow())
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 'gwa gwa'
return 'ok', 200
@blueprint.route('/logout')
@ -106,4 +104,5 @@ def logout():
Clear the current session, including the stored user id
"""
logout_user()
flash(['Goodbye!!!', '4'])
return redirect(url_for('gallery.index'))

View file

@ -1,8 +1,10 @@
"""
OnlyLegs - Database models and functions for SQLAlchemy
"""
from uuid import uuid4
import os
import platformdirs
from datetime import datetime as dt
from sqlalchemy import create_engine, Column, Integer, String, DateTime, ForeignKey, PickleType
from sqlalchemy.orm import declarative_base, relationship
@ -11,7 +13,7 @@ from flask_login import UserMixin
USER_DIR = platformdirs.user_config_dir('onlylegs')
DB_PATH = os.path.join(USER_DIR, 'gallery.sqlite')
DB_PATH = os.path.join(USER_DIR, 'instance', 'gallery.sqlite3')
# In the future, I want to add support for other databases
@ -28,11 +30,12 @@ class Users (base, UserMixin): # pylint: disable=too-few-public-methods, C0103
# Gallery used information
id = Column(Integer, primary_key=True)
alt_id = Column(String, unique=True, nullable=False)
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)
created_at = Column(DateTime, nullable=False)
joined_at = Column(DateTime, nullable=False, default=dt.utcnow())
posts = relationship('Posts', backref='users')
groups = relationship('Groups', backref='users')
@ -51,20 +54,16 @@ class Posts (base): # pylint: disable=too-few-public-methods, C0103
id = Column(Integer, primary_key=True)
author_id = Column(Integer, ForeignKey('users.id'))
created_at = Column(DateTime, nullable=False)
file_name = Column(String, unique=True, nullable=False)
file_type = Column(String, nullable=False)
image_exif = Column(PickleType, nullable=False)
image_colours = Column(PickleType, nullable=False)
post_description = Column(String, nullable=False)
post_alt = Column(String, nullable=False)
created_at = Column(DateTime, nullable=False, default=dt.utcnow())
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
@ -76,7 +75,7 @@ class Groups (base): # pylint: disable=too-few-public-methods, C0103
name = Column(String, nullable=False)
description = Column(String, nullable=False)
author_id = Column(Integer, ForeignKey('users.id'))
created_at = Column(DateTime, nullable=False)
created_at = Column(DateTime, nullable=False, default=dt.utcnow())
junction = relationship('GroupJunction', backref='groups')
@ -89,7 +88,7 @@ class GroupJunction (base): # pylint: disable=too-few-public-methods, C0103
__tablename__ = 'group_junction'
id = Column(Integer, primary_key=True)
date_added = Column(DateTime, nullable=False)
date_added = Column(DateTime, nullable=False, default=dt.utcnow())
group_id = Column(Integer, ForeignKey('groups.id'))
post_id = Column(Integer, ForeignKey('posts.id'))
@ -105,8 +104,8 @@ class Logs (base): # pylint: disable=too-few-public-methods, C0103
user_id = Column(Integer, ForeignKey('users.id'))
ip_address = Column(String, nullable=False)
code = Column(Integer, nullable=False)
msg = Column(String, nullable=False)
created_at = Column(DateTime, nullable=False)
note = Column(String, nullable=False)
created_at = Column(DateTime, nullable=False, default=dt.utcnow())
class Bans (base): # pylint: disable=too-few-public-methods, C0103
@ -118,8 +117,8 @@ class Bans (base): # pylint: disable=too-few-public-methods, C0103
id = Column(Integer, primary_key=True)
ip_address = Column(String, nullable=False)
code = Column(Integer, nullable=False)
msg = Column(String, nullable=False)
created_at = Column(DateTime, nullable=False)
note = Column(String, nullable=False)
banned_at = Column(DateTime, nullable=False, default=dt.utcnow())
# check if database file exists, if not create it

View file

@ -2,7 +2,7 @@
{% block nav_groups %}selected{% endblock %}
{% block head %}
{% if images %}
<meta name="theme-color" content="rgb({{ images.0.image_colours.0.0 }}{{ images.0.image_colours.0.1 }}{{ images.0.image_colours.0.2 }})"/>
<meta name="theme-color" content="rgb({{ images.0.colours.0.0 }}{{ images.0.colours.0.1 }}{{ images.0.colours.0.2 }})"/>
{% endif %}
<script type="text/javascript">
@ -175,7 +175,7 @@
<style>
{% if images %}
.banner::after {
box-shadow: 0 calc(var(--rad) * -1) 0 0 rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }});
box-shadow: 0 calc(var(--rad) * -1) 0 0 rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }});
}
.banner-content p {
color: {{ text_colour }} !important;
@ -185,16 +185,16 @@
}
.banner-filter {
background: linear-gradient(90deg, rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}), rgba({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}, 0.3)) !important;
background: linear-gradient(90deg, rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}), rgba({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}, 0.3)) !important;
}
@media (max-width: 800px) {
.banner-filter {
background: linear-gradient(180deg, rgba({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}, 0.8), rgba({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}, 0.5)) !important;
background: linear-gradient(180deg, rgba({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}, 0.8), rgba({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}, 0.5)) !important;
}
}
.navigation {
background-color: rgb({{ images.0.image_colours.0.0 }}, {{ images.0.image_colours.0.1 }}, {{ images.0.image_colours.0.2 }}) !important;
background-color: rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}) !important;
}
.navigation-item > svg {
fill: {{ text_colour }} !important;
@ -209,7 +209,7 @@
{% block content %}
{% if images %}
<div class="banner">
<img src="{{ url_for('api.file', file_name=images.0.file_name ) }}?r=prev" onload="imgFade(this)" style="opacity:0;"/>
<img src="{{ url_for('api.file', file_name=images.0.filename ) }}?r=prev" onload="imgFade(this)" style="opacity:0;"/>
<span class="banner-filter"></span>
<div class="banner-content">
<p class="banner-info">By {{ group.author_username }} - {{ images|length }} Images</p>
@ -266,7 +266,7 @@
<p class="image-subtitle"></p>
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
</div>
<img alt="{{ image.post_alt }}" data-src="{{ url_for('api.file', file_name=image.file_name) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
<img alt="{{ image.alt }}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
</a>
{% endfor %}
</div>

View file

@ -2,7 +2,7 @@
{% block nav_groups %}selected{% endblock %}
{% block head %}
{% if images %}
<meta name="theme-color" content="rgb({{ images.0.image_colours.0.0 }}{{ images.0.image_colours.0.1 }}{{ images.0.image_colours.0.2 }})"/>
<meta name="theme-color" content="rgb({{ images.0.colours.0.0 }}{{ images.0.colours.0.1 }}{{ images.0.colours.0.2 }})"/>
{% endif %}
{% if current_user.is_authenticated %}
@ -127,7 +127,7 @@
<div class="images size-{{ group.images|length }}">
{% if group.images|length > 0 %}
{% for image in group.images %}
<img data-src="{{ url_for('api.file', file_name=image.file_name) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load" class="data-{{ loop.index }}"/>
<img data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load" class="data-{{ loop.index }}"/>
{% endfor %}
{% else %}
<img src="{{ url_for('static', filename='error.png') }}" class="loaded"/>

View file

@ -1,8 +1,8 @@
{% extends 'layout.html' %}
{% block wrapper_class %}image-wrapper{% endblock %}
{% block head %}
<meta property="og:image" content="{{ url_for('api.file', file_name=image.file_name) }}"/>
<meta name="theme-color" content="rgb({{ image.image_colours.0.0 }}{{ image.image_colours.0.1 }}{{ image.image_colours.0.2 }})"/>
<meta property="og:image" content="{{ url_for('api.file', file_name=image.filename) }}"/>
<meta name="theme-color" content="rgb({{ image.colours.0.0 }}{{ image.colours.0.1 }}{{ image.colours.0.2 }})"/>
<script type="text/javascript">
function imageFullscreenOff() {
@ -18,7 +18,7 @@
function imageFullscreenOn() {
let fullscreen = document.querySelector('.image-fullscreen')
fullscreen.querySelector('img').src = '{{ url_for('api.file', file_name=image.file_name) }}';
fullscreen.querySelector('img').src = '{{ url_for('api.file', file_name=image.filename) }}';
document.querySelector("html").style.overflow = "hidden";
fullscreen.style.display = 'flex';
@ -80,31 +80,31 @@
<style>
.background span {
background-image: linear-gradient(to top, rgba({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }}, 1), transparent);
background-image: linear-gradient(to top, rgba({{ image.colours.0.0 }}, {{ image.colours.0.1 }}, {{ image.colours.0.2 }}, 1), transparent);
}
</style>
{% endblock %}
{% block content %}
<div class="background">
<img src="{{ url_for('api.file', file_name=image.file_name) }}?r=prev" alt="{{ image.post_alt }}" onload="imgFade(this)" style="opacity:0;"/>
<img src="{{ url_for('api.file', file_name=image.filename) }}?r=prev" alt="{{ image.alt }}" onload="imgFade(this)" style="opacity:0;"/>
<span></span>
</div>
<div class="image-fullscreen" onclick="imageFullscreenOff()">
<img src="" alt="{{ image.post_alt }}"/>
<img src="" alt="{{ image.alt }}"/>
</div>
<div class="image-grid">
<div class="image-container" id="image-container">
<img
src="{{ url_for('api.file', file_name=image.file_name) }}?r=prev"
alt="{{ image.post_alt }}"
src="{{ url_for('api.file', file_name=image.filename) }}?r=prev"
alt="{{ image.alt }}"
onload="imgFade(this)"
style="opacity: 0;"
onerror="this.src='{{ url_for('static', filename='error.png')}}'"
{% if "File" in image.image_exif %}
width="{{ image.image_exif.File.Width.raw }}"
height="{{ image.image_exif.File.Height.raw }}"
{% if "File" in image.exif %}
width="{{ image.exif.File.Width.raw }}"
height="{{ image.exif.File.Height.raw }}"
{% endif %}
/>
</div>
@ -136,7 +136,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z"></path></svg>
</span>
</button>
<a class="pill-item" href="/api/file/{{ image.file_name }}" download onclick="addNotification('Download started!', 4)">
<a class="pill-item" href="/api/file/{{ image.filename }}" download onclick="addNotification('Download started!', 4)">
<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>
<span class="tool-tip">
Download
@ -210,7 +210,7 @@
</tr>
</table>
<div class="img-colours">
{% for col in image.image_colours %}
{% for col in image.colours %}
<span style="background-color: rgb({{col.0}}, {{col.1}}, {{col.2}})"></span>
{% endfor %}
</div>
@ -226,7 +226,7 @@
{% endif %}
</div>
</div>
{% for tag in image.image_exif %}
{% for tag in image.exif %}
<div class="info-tab">
<div class="info-header">
{% if tag == 'Photographer' %}
@ -251,17 +251,17 @@
</div>
<div class="info-table">
<table>
{% for subtag in image.image_exif[tag] %}
{% for subtag in image.exif[tag] %}
<tr>
<td>{{ subtag }}</td>
{% if image.image_exif[tag][subtag]['formatted'] %}
{% if image.image_exif[tag][subtag]['type'] == 'date' %}
<td><span class="time">{{ image.image_exif[tag][subtag]['formatted'] }}</span></td>
{% if image.exif[tag][subtag]['formatted'] %}
{% if image.exif[tag][subtag]['type'] == 'date' %}
<td><span class="time">{{ image.exif[tag][subtag]['formatted'] }}</span></td>
{% else %}
<td>{{ image.image_exif[tag][subtag]['formatted'] }}</td>
<td>{{ image.exif[tag][subtag]['formatted'] }}</td>
{% endif %}
{% elif image.image_exif[tag][subtag]['raw'] %}
<td>{{ image.image_exif[tag][subtag]['raw'] }}</td>
{% elif image.exif[tag][subtag]['raw'] %}
<td>{{ image.exif[tag][subtag]['raw'] }}</td>
{% else %}
<td class="empty-table">Oops, an error</td>
{% endif %}

View file

@ -16,12 +16,12 @@
{% if images %}
<div class="gallery-grid">
{% for image in images %}
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('gallery.image', image_id=image.id) }}" style="background-color: rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }})">
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('gallery.image', image_id=image.id) }}" style="background-color: rgb({{ image.colours.0.0 }}, {{ image.colours.0.1 }}, {{ image.colours.0.2 }})">
<div class="image-filter">
<p class="image-subtitle"></p>
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
</div>
<img fetchpriority="low" alt="{{ image.post_alt }}" data-src="{{ url_for('api.file', file_name=image.file_name) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
<img fetchpriority="low" alt="{{ image.alt }}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
</a>
{% endfor %}
</div>

View file

@ -5,18 +5,18 @@
<div class="banner-small">
<div class="banner-content">
<h1 class="banner-header">{{ user.username }}</h1>
<p class="banner-info">Member since <span class="time">{{ user.created_at }}</span></p>
<p class="banner-info">Member since <span class="time">{{ user.joined_at }}</span></p>
</div>
</div>
{% if images %}
<div class="gallery-grid">
{% for image in images %}
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('gallery.image', image_id=image.id) }}" style="background-color: rgb({{ image.image_colours.0.0 }}, {{ image.image_colours.0.1 }}, {{ image.image_colours.0.2 }})">
<a id="image-{{ image.id }}" class="gallery-item" href="{{ url_for('gallery.image', image_id=image.id) }}" style="background-color: rgb({{ image.colours.0.0 }}, {{ image.colours.0.1 }}, {{ image.colours.0.2 }})">
<div class="image-filter">
<p class="image-subtitle"></p>
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
</div>
<img fetchpriority="low" alt="{{ image.post_alt }}" data-src="{{ url_for('api.file', file_name=image.file_name) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
<img fetchpriority="low" alt="{{ image.alt }}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
</a>
{% endfor %}
</div>

View file

@ -5,10 +5,9 @@ from uuid import uuid4
import os
import pathlib
import logging
from datetime import datetime as dt
import platformdirs
from flask import Blueprint, send_from_directory, abort, flash, jsonify, request, current_app
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
@ -85,13 +84,12 @@ def upload():
# Save to database
query = db.Posts(author_id=current_user.id,
created_at=dt.utcnow(),
file_name=img_name+'.'+img_ext,
file_type=img_ext,
image_exif=img_exif,
image_colours=img_colors,
post_description=form['description'],
post_alt=form['alt'])
filename=img_name + '.' + img_ext,
mimetype=img_ext,
exif=img_exif,
colours=img_colors,
description=form['description'],
alt=form['alt'])
db_session.add(query)
db_session.commit()
@ -115,13 +113,13 @@ def delete_image(image_id):
# Delete file
try:
os.remove(os.path.join(current_app.config['UPLOAD_FOLDER'], img.file_name))
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.file_name)
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.file_name.rsplit('.')[0]
cache_name = img.filename.rsplit('.')[0]
for cache_file in pathlib.Path(cache_path).glob(cache_name + '*'):
os.remove(cache_file)
@ -136,7 +134,7 @@ def delete_image(image_id):
# Commit all changes
db_session.commit()
logging.info('Removed image (%s) %s', image_id, img.file_name)
logging.info('Removed image (%s) %s', image_id, img.filename)
flash(['Image was all in Le Head!', '1'])
return 'Gwa Gwa'
@ -149,8 +147,7 @@ def create_group():
"""
new_group = db.Groups(name=request.form['name'],
description=request.form['description'],
author_id=current_user.id,
created_at=dt.utcnow())
author_id=current_user.id)
db_session.add(new_group)
db_session.commit()
@ -180,8 +177,7 @@ def modify_group():
.filter_by(group_id=group_id, post_id=image_id)
.first()):
db_session.add(db.GroupJunction(group_id=group_id,
post_id=image_id,
date_added=dt.utcnow()))
post_id=image_id))
elif request.form['action'] == 'remove':
(db_session.query(db.GroupJunction)
.filter_by(group_id=group_id, post_id=image_id)

View file

@ -37,8 +37,8 @@ def groups():
# 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.file_name, db.Posts.post_alt,
db.Posts.image_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())
@ -79,7 +79,7 @@ def group(group_id):
# Check contrast for the first image in the group for the banner
text_colour = 'rgb(var(--fg-black))'
if images:
text_colour = contrast.contrast(images[0].image_colours[0],
text_colour = contrast.contrast(images[0].colours[0],
'rgb(var(--fg-black))',
'rgb(var(--fg-white))')

View file

@ -19,9 +19,9 @@ def index():
"""
Home page of the website, shows the feed of the latest images
"""
images = db_session.query(db.Posts.file_name,
db.Posts.post_alt,
db.Posts.image_colours,
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()).all()