Move most of the processing into the upload

This commit is contained in:
Michał 2023-03-05 16:22:11 +00:00
parent 970d3dcf66
commit 0a27d79a82
8 changed files with 107 additions and 100 deletions

View file

@ -6,11 +6,13 @@ from uuid import uuid4
import os import os
import io import io
import logging import logging
from datetime import datetime as dt
from flask import ( from flask import (
Blueprint, current_app, send_from_directory, send_file, request, g, abort, flash, jsonify) Blueprint, current_app, send_from_directory, send_file, request, g, abort, flash, jsonify)
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from colorthief import ColorThief
from PIL import Image, ImageOps # ImageFilter from PIL import Image, ImageOps # ImageFilter
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
@ -103,12 +105,15 @@ def upload():
""" """
form_file = request.files['file'] form_file = request.files['file']
form = request.form form = request.form
form_description = form['description']
form_alt = form['alt']
if not form_file: if not form_file:
return abort(404) return abort(404)
img_ext = os.path.splitext(form_file.filename)[-1].replace('.', '').lower() img_ext = os.path.splitext(form_file.filename)[-1].replace('.', '').lower()
img_name = f"GWAGWA_{str(uuid4())}.{img_ext}" img_name = "GWAGWA_"+str(uuid4())
img_path = os.path.join(current_app.config['UPLOAD_FOLDER'], img_name+'.'+img_ext)
if not img_ext in current_app.config['ALLOWED_EXTENSIONS'].keys(): if not img_ext in current_app.config['ALLOWED_EXTENSIONS'].keys():
logging.info('File extension not allowed: %s', img_ext) logging.info('File extension not allowed: %s', img_ext)
@ -117,22 +122,35 @@ def upload():
if os.path.isdir(current_app.config['UPLOAD_FOLDER']) is False: if os.path.isdir(current_app.config['UPLOAD_FOLDER']) is False:
os.mkdir(current_app.config['UPLOAD_FOLDER']) os.mkdir(current_app.config['UPLOAD_FOLDER'])
# Save to database
try:
db_session.add(db.posts(img_name, form['description'], form['alt'], g.user.id))
db_session.commit()
except Exception as err:
logging.error('Could not save to database: %s', err)
abort(500)
# Save file # Save file
try: try:
form_file.save( form_file.save(img_path)
os.path.join(current_app.config['UPLOAD_FOLDER'], img_name))
except Exception as err: except Exception as err:
logging.error('Could not save file: %s', err) logging.error('Could not save file: %s', err)
abort(500) abort(500)
# Get metadata and colors
img_exif = mt.Metadata(img_path).yoink()
img_colors = ColorThief(img_path).get_palette(color_count=3)
# Save to database
try:
query = db.Posts(author_id = g.user.id,
created_at = dt.now(),
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)
db_session.add(query)
db_session.commit()
except Exception as err:
logging.error('Could not save to database: %s', err)
abort(500)
return 'Gwa Gwa' return 'Gwa Gwa'
@ -142,7 +160,7 @@ def remove(img_id):
""" """
Deletes an image from the server and database Deletes an image from the server and database
""" """
img = db_session.query(db.posts).filter_by(id=img_id).first() img = db_session.query(db.Posts).filter_by(id=img_id).first()
if img is None: if img is None:
abort(404) abort(404)
@ -159,7 +177,7 @@ def remove(img_id):
abort(500) abort(500)
try: try:
db_session.query(db.posts).filter_by(id=img_id).delete() db_session.query(db.Posts).filter_by(id=img_id).delete()
db_session.commit() db_session.commit()
except Exception as err: except Exception as err:
logging.error('Could not remove from database: %s', err) logging.error('Could not remove from database: %s', err)
@ -175,7 +193,7 @@ def metadata(img_id):
""" """
Yoinks metadata from an image Yoinks metadata from an image
""" """
img = db_session.query(db.posts).filter_by(id=img_id).first() img = db_session.query(db.Posts).filter_by(id=img_id).first()
if img is None: if img is None:
abort(404) abort(404)

View file

@ -5,6 +5,7 @@ User registration, login and logout and locking access to pages behind a login
import re import re
import uuid import uuid
import logging import logging
from datetime import datetime as dt
import functools import functools
from flask import Blueprint, flash, g, redirect, request, session, url_for, abort, jsonify from flask import Blueprint, flash, g, redirect, request, session, url_for, abort, jsonify
@ -49,14 +50,14 @@ def load_logged_in_user():
g.user = None g.user = None
session.clear() session.clear()
else: else:
is_alive = db_session.query(db.sessions).filter_by(session_uuid=user_uuid).first() is_alive = db_session.query(db.Sessions).filter_by(session_uuid=user_uuid).first()
if is_alive is None: if is_alive is None:
logging.info('Session expired') logging.info('Session expired')
flash(['Session expired!', '3']) flash(['Session expired!', '3'])
session.clear() session.clear()
else: else:
g.user = db_session.query(db.users).filter_by(id=user_id).first() g.user = db_session.query(db.Users).filter_by(id=user_id).first()
@blueprint.route('/register', methods=['POST']) @blueprint.route('/register', methods=['POST'])
@ -96,7 +97,11 @@ def register():
try: try:
db_session.add(db.users(username, email, generate_password_hash(password))) register_user = db.Users(username=username,
email=email,
password=generate_password_hash(password),
created_at=dt.now())
db_session.add(register_user)
db_session.commit() db_session.commit()
except exc.IntegrityError: except exc.IntegrityError:
return f'User {username} is already registered!' return f'User {username} is already registered!'
@ -116,7 +121,7 @@ def login():
username = request.form['username'] username = request.form['username']
password = request.form['password'] password = request.form['password']
user = db_session.query(db.users).filter_by(username=username).first() user = db_session.query(db.Users).filter_by(username=username).first()
error = [] error = []
@ -138,11 +143,14 @@ def login():
session['user_id'] = user.id session['user_id'] = user.id
session['uuid'] = str(uuid.uuid4()) session['uuid'] = str(uuid.uuid4())
db_session.add(db.sessions(user.id, session_query = db.Sessions(user_id=user.id,
session.get('uuid'), session_uuid=session.get('uuid'),
request.remote_addr, ip_address=request.remote_addr,
request.user_agent.string, user_agent=request.user_agent.string,
1)) active=True,
created_at=dt.now())
db_session.add(session_query)
db_session.commit() db_session.commit()
except Exception as err: except Exception as err:
logging.error('User %s could not be logged in: %s', username, err) logging.error('User %s could not be logged in: %s', username, err)

View file

@ -6,8 +6,8 @@ import os
from datetime import datetime from datetime import datetime
import platformdirs import platformdirs
from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime, ForeignKey from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime, ForeignKey, PickleType
from sqlalchemy.orm import declarative_base, relationship from sqlalchemy.orm import declarative_base, relationship, backref, mapped_column
path_to_db = os.path.join(platformdirs.user_config_dir('onlylegs'), 'gallery.sqlite') path_to_db = os.path.join(platformdirs.user_config_dir('onlylegs'), 'gallery.sqlite')
@ -15,7 +15,7 @@ engine = create_engine(f'sqlite:///{path_to_db}', echo=False)
base = declarative_base() base = declarative_base()
class users (base): # pylint: disable=too-few-public-methods, C0103 class Users (base): # pylint: disable=too-few-public-methods, C0103
""" """
User table User table
Joins with post, groups, session and log Joins with post, groups, session and log
@ -28,19 +28,13 @@ class users (base): # pylint: disable=too-few-public-methods, C0103
password = Column(String, nullable=False) password = Column(String, nullable=False)
created_at = Column(DateTime, nullable=False) created_at = Column(DateTime, nullable=False)
posts = relationship('posts') posts = relationship('Posts', backref='users')
groups = relationship('groups') groups = relationship('Groups', backref='users')
session = relationship('sessions') session = relationship('Sessions', backref='users')
log = relationship('logs') log = relationship('Logs', backref='users')
def __init__(self, username, email, password):
self.username = username
self.email = email
self.password = password
self.created_at = datetime.now()
class posts (base): # pylint: disable=too-few-public-methods, C0103 class Posts (base): # pylint: disable=too-few-public-methods, C0103
""" """
Post table Post table
Joins with group_junction Joins with group_junction
@ -48,23 +42,22 @@ class posts (base): # pylint: disable=too-few-public-methods, C0103
__tablename__ = 'posts' __tablename__ = 'posts'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
file_name = Column(String, unique=True, nullable=False)
description = Column(String, nullable=False)
alt = Column(String, nullable=False)
author_id = Column(Integer, ForeignKey('users.id')) author_id = Column(Integer, ForeignKey('users.id'))
created_at = Column(DateTime, nullable=False) 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)
junction = relationship('group_junction') junction = relationship('GroupJunction', backref='posts')
def __init__(self, file_name, description, alt, author_id):
self.file_name = file_name
self.description = description
self.alt = alt
self.author_id = author_id
self.created_at = datetime.now()
class groups (base): # pylint: disable=too-few-public-methods, C0103 class Groups (base): # pylint: disable=too-few-public-methods, C0103
""" """
Group table Group table
Joins with group_junction Joins with group_junction
@ -77,16 +70,10 @@ class groups (base): # pylint: disable=too-few-public-methods, C0103
author_id = Column(Integer, ForeignKey('users.id')) author_id = Column(Integer, ForeignKey('users.id'))
created_at = Column(DateTime, nullable=False) created_at = Column(DateTime, nullable=False)
junction = relationship('group_junction') junction = relationship('GroupJunction', backref='groups')
def __init__(self, name, description, author_id):
self.name = name
self.description = description
self.author_id = author_id
self.created_at = datetime.now()
class group_junction (base): # pylint: disable=too-few-public-methods, C0103 class GroupJunction (base): # pylint: disable=too-few-public-methods, C0103
""" """
Junction table for posts and groups Junction table for posts and groups
Joins with posts and groups Joins with posts and groups
@ -97,12 +84,8 @@ class group_junction (base): # pylint: disable=too-few-public-methods, C0103
group_id = Column(Integer, ForeignKey('groups.id')) group_id = Column(Integer, ForeignKey('groups.id'))
post_id = Column(Integer, ForeignKey('posts.id')) post_id = Column(Integer, ForeignKey('posts.id'))
def __init__(self, group_id, post_id):
self.group_id = group_id
self.post_id = post_id
class Sessions (base): # pylint: disable=too-few-public-methods, C0103
class sessions (base): # pylint: disable=too-few-public-methods, C0103
""" """
Session table Session table
Joins with user Joins with user
@ -117,16 +100,8 @@ class sessions (base): # pylint: disable=too-few-public-methods, C0103
active = Column(Boolean, nullable=False) active = Column(Boolean, nullable=False)
created_at = Column(DateTime, nullable=False) created_at = Column(DateTime, nullable=False)
def __init__(self, user_id, session_uuid, ip_address, user_agent, active): # pylint: disable=too-many-arguments, C0103
self.user_id = user_id
self.session_uuid = session_uuid
self.ip_address = ip_address
self.user_agent = user_agent
self.active = active
self.created_at = datetime.now()
class Logs (base): # pylint: disable=too-few-public-methods, C0103
class logs (base): # pylint: disable=too-few-public-methods, C0103
""" """
Log table Log table
Joins with user Joins with user
@ -140,15 +115,8 @@ class logs (base): # pylint: disable=too-few-public-methods, C0103
msg = Column(String, nullable=False) msg = Column(String, nullable=False)
created_at = Column(DateTime, nullable=False) created_at = Column(DateTime, nullable=False)
def __init__(self, user_id, ip_address, code, msg):
self.user_id = user_id
self.ip_address = ip_address
self.code = code
self.msg = msg
self.created_at = datetime.now()
class Bans (base): # pylint: disable=too-few-public-methods, C0103
class bans (base): # pylint: disable=too-few-public-methods, C0103
""" """
Bans table Bans table
""" """
@ -160,11 +128,5 @@ class bans (base): # pylint: disable=too-few-public-methods, C0103
msg = Column(String, nullable=False) msg = Column(String, nullable=False)
created_at = Column(DateTime, nullable=False) created_at = Column(DateTime, nullable=False)
def __init__(self, ip_address, code, msg):
self.ip_address = ip_address
self.code = code
self.msg = msg
self.created_at = datetime.now()
base.metadata.create_all(engine) base.metadata.create_all(engine)

View file

@ -4,10 +4,9 @@ Parse metadata from images if available
otherwise get some basic information from the file otherwise get some basic information from the file
""" """
import os import os
import logging
from PIL import Image from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS from PIL.ExifTags import TAGS
from .helpers import * from .helpers import *
from .mapping import * from .mapping import *
@ -72,6 +71,7 @@ class Metadata:
} }
elif data in CAMERA_MAPPING: elif data in CAMERA_MAPPING:
if len(CAMERA_MAPPING[data]) == 2: if len(CAMERA_MAPPING[data]) == 2:
# Camera - Exif Tag name
exif['Camera'][CAMERA_MAPPING[data][0]] = { exif['Camera'][CAMERA_MAPPING[data][0]] = {
'raw': encoded_exif[data], 'raw': encoded_exif[data],
'formatted': 'formatted':

View file

@ -41,8 +41,6 @@ CAMERA_MAPPING = {
'LightSource': ['Light Source', 'light_source'], 'LightSource': ['Light Source', 'light_source'],
'SceneCaptureType': ['Scene Capture Type', 'scene_capture_type'], 'SceneCaptureType': ['Scene Capture Type', 'scene_capture_type'],
'SceneType': ['Scene Type', 'scene_type'], 'SceneType': ['Scene Type', 'scene_type'],
'Rating': ['Rating', 'rating'],
'RatingPercent': ['Rating Percent', 'rating_percent'],
} }
SOFTWARE_MAPPING = { SOFTWARE_MAPPING = {
'Software': ['Software'], 'Software': ['Software'],
@ -59,4 +57,6 @@ FILE_MAPPING = {
'XResolution': ['X-resolution'], 'XResolution': ['X-resolution'],
'YResolution': ['Y-resolution'], 'YResolution': ['Y-resolution'],
'ResolutionUnit': ['Resolution Units', 'resolution_unit'], 'ResolutionUnit': ['Resolution Units', 'resolution_unit'],
'Rating': ['Rating', 'rating'],
'RatingPercent': ['Rating Percent', 'rating_percent'],
} }

View file

@ -22,8 +22,8 @@ def index():
""" """
Home page of the website, shows the feed of latest images Home page of the website, shows the feed of latest images
""" """
images = db_session.query(db.posts).order_by(db.posts.id.desc()).all() images = db_session.query(db.Posts.file_name, db.Posts.id, db.Posts.created_at).order_by(db.Posts.id.desc()).all()
return render_template('index.html', return render_template('index.html',
images=images, images=images,
image_count=len(images), image_count=len(images),
@ -35,15 +35,12 @@ def image(image_id):
""" """
Image view, shows the image and its metadata Image view, shows the image and its metadata
""" """
img = db_session.query(db.posts).filter_by(id=image_id).first() img = db_session.query(db.Posts).filter_by(id=image_id).first()
if img is None: if img is None:
abort(404) abort(404)
img_path = os.path.join(current_app.config['UPLOAD_FOLDER'], img.file_name) return render_template('image.html', image=img, exif=img.image_exif)
exif = mt.Metadata(img_path).yoink()
return render_template('image.html', image=img, exif=exif)
@blueprint.route('/group') @blueprint.route('/group')
def groups(): def groups():

View file

@ -20,13 +20,15 @@ function uploadFile(){
addNotification("Please select a file to upload", 2); addNotification("Please select a file to upload", 2);
} else { } else {
// Make form // Make form
var formData = new FormData(); let formData = new FormData();
formData.append("file", $("#file").prop("files")[0]); formData.append("file", $("#file").prop("files")[0]);
formData.append("alt", $("#alt").val()); formData.append("alt", $("#alt").val());
formData.append("description", $("#description").val()); formData.append("description", $("#description").val());
formData.append("tags", $("#tags").val()); formData.append("tags", $("#tags").val());
formData.append("submit", $("#submit").val()); formData.append("submit", $("#submit").val());
//let bar = $('.bar');
// Upload the information // Upload the information
$.ajax({ $.ajax({
url: '/api/upload', url: '/api/upload',
@ -34,9 +36,25 @@ function uploadFile(){
data: formData, data: formData,
contentType: false, contentType: false,
processData: false, processData: false,
beforeSend: function() {
//bar.width('0%');
var percentVal = 0;
console.log("Uploading...");
},
uploadProgress: function(event, position, total, percentComplete) {
//bar.width(percentComplete + '%');
percentVal = percentComplete;
console.log(percentVal);
},
complete: function(xhr) {
//bar.width('100%');
//bar.class += " loading";
console.log("Upload complete");
},
success: function (response) { success: function (response) {
addNotification("File uploaded successfully!", 1); addNotification("File uploaded successfully!", 1);
// popupDissmiss(); // Close popup // popupDissmiss(); // Close popup
console.log('File processed successfully');
}, },
error: function (response) { error: function (response) {
switch (response.status) { switch (response.status) {
@ -57,6 +75,10 @@ function uploadFile(){
addNotification('Error uploading file, blame someone', 2); addNotification('Error uploading file, blame someone', 2);
break; break;
} }
},
always: function (response) {
//bar.class += "";
console.log("Upload complete");
} }
}); });

View file

@ -73,7 +73,7 @@
</div> </div>
<div class="image-info__container"> <div class="image-info__container">
{% if image['alt'] != '' %} {% if image['post_alt'] %}
<div class="image-info"> <div class="image-info">
<span class="image-info__collapse" id="collapse-info"> <span class="image-info__collapse" id="collapse-info">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -8 24 24" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -8 24 24" fill="currentColor">
@ -87,11 +87,11 @@
<h2>Alt</h2> <h2>Alt</h2>
</div> </div>
<div class="image-info__content"> <div class="image-info__content">
<p>{{ image['alt'] }}</p> <p>{{ image['post_alt'] }}</p>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if image['description'] != '' %} {% if image['post_description'] %}
<div class="image-info"> <div class="image-info">
<span class="image-info__collapse" id="collapse-info"> <span class="image-info__collapse" id="collapse-info">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -8 24 24" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -8 24 24" fill="currentColor">
@ -105,7 +105,7 @@
<h2>Description</h2> <h2>Description</h2>
</div> </div>
<div class="image-info__content"> <div class="image-info__content">
<p>{{ image['description'] }}</p> <p>{{ image['post_description'] }}</p>
</div> </div>
</div> </div>
{% endif %} {% endif %}