From e3a0eaf60b0b1240c6aa9b97454b61c089c087de Mon Sep 17 00:00:00 2001 From: Fluffy-Bean Date: Thu, 9 Mar 2023 23:31:58 +0000 Subject: [PATCH] Add basic Image Group functions Make the Upload Pannel usable on mobile Remove useless code as Django had built-in functions to read the config Remove useless JS code Cleanup tempaltes --- gallery/__init__.py | 6 ++ gallery/api.py | 63 +++++++++++-- gallery/db.py | 2 +- gallery/groups.py | 89 +++++++++++++++++++ gallery/routing.py | 61 +++---------- gallery/static/js/main.js | 52 +++++++---- gallery/templates/group.html | 43 --------- gallery/templates/groups/group.html | 60 +++++++++++++ gallery/templates/groups/list.html | 52 +++++++++++ gallery/templates/image.html | 84 +++++++++-------- gallery/templates/index.html | 72 +++++++-------- gallery/templates/layout.html | 12 +-- .../default/components/elements/tags.sass | 24 +++++ .../components/elements/upload-panel.sass | 36 ++++++-- .../themes/default/components/gallery.sass | 6 +- .../components/image-view/info-tab.sass | 16 ++-- .../default/components/image-view/view.sass | 1 - gallery/themes/default/style.sass | 65 ++++++++++++++ 18 files changed, 531 insertions(+), 213 deletions(-) create mode 100644 gallery/groups.py delete mode 100644 gallery/templates/group.html create mode 100644 gallery/templates/groups/group.html create mode 100644 gallery/templates/groups/list.html create mode 100644 gallery/themes/default/components/elements/tags.sass diff --git a/gallery/__init__.py b/gallery/__init__.py index 501afd9..3ade1ca 100644 --- a/gallery/__init__.py +++ b/gallery/__init__.py @@ -84,6 +84,8 @@ def create_app(test_config=None): ALLOWED_EXTENSIONS=conf['upload']['allowed-extensions'], MAX_CONTENT_LENGTH=1024 * 1024 * conf['upload']['max-size'], WEBSITE=conf['website'], + ADMIN=conf['admin'], + APP_VERSION='23.03.09', ) if test_config is None: @@ -138,6 +140,10 @@ def create_app(test_config=None): from . import routing app.register_blueprint(routing.blueprint) app.add_url_rule('/', endpoint='index') + + # Load routes for groups + from . import groups + app.register_blueprint(groups.blueprint) # Load routes for settings from . import settings diff --git a/gallery/api.py b/gallery/api.py index ede80c7..ee29111 100644 --- a/gallery/api.py +++ b/gallery/api.py @@ -6,10 +6,11 @@ from uuid import uuid4 import os import io import logging +import json from datetime import datetime as dt from flask import ( - Blueprint, current_app, send_from_directory, send_file, request, g, abort, flash, jsonify) + Blueprint, send_from_directory, send_file, abort, flash, jsonify, request, g, current_app) from werkzeug.utils import secure_filename from colorthief import ColorThief @@ -128,7 +129,6 @@ def upload(): if os.path.isdir(current_app.config['UPLOAD_FOLDER']) is False: os.mkdir(current_app.config['UPLOAD_FOLDER']) - # Save file try: form_file.save(img_path) @@ -159,14 +159,13 @@ def upload(): return 'Gwa Gwa' - -@blueprint.route('/remove/', methods=['POST']) +@blueprint.route('/delete/', methods=['POST']) @login_required -def remove(img_id): +def delete_image(image_id): """ 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=image_id).first() if img is None: abort(404) @@ -183,17 +182,65 @@ def remove(img_id): abort(500) try: - db_session.query(db.Posts).filter_by(id=img_id).delete() + db_session.query(db.Posts).filter_by(id=image_id).delete() + + groups = db_session.query(db.GroupJunction).filter_by(post_id=image_id).all() + for group in groups: + db_session.delete(group) + db_session.commit() except Exception as err: logging.error('Could not remove from database: %s', err) abort(500) - logging.info('Removed image (%s) %s', img_id, img.file_name) + logging.info('Removed image (%s) %s', image_id, img.file_name) flash(['Image was all in Le Head!', 1]) return 'Gwa Gwa' +@blueprint.route('/group/create', methods=['POST']) +@login_required +def create_group(): + """ + Creates a group + """ + group_name = request.form['name'] + group_description = request.form['description'] + group_author = g.user.id + + new_group = db.Groups(name=group_name, + description=group_description, + author_id=group_author, + created_at=dt.utcnow()) + + db_session.add(new_group) + db_session.commit() + + return ':3' + + +@blueprint.route('/group/modify', methods=['POST']) +@login_required +def modify_group(): + """ + Changes the images in a group + """ + group_id = request.form['group_id'] + image_id = request.form['images'] + action = request.form['action'] + + if action == 'add': + # Check if image is already in group + if db_session.query(db.GroupJunction).filter_by(group_id=group_id, post_id=image_id).first() is None: + db_session.add(db.GroupJunction(group_id=group_id, post_id=image_id, date_added=dt.utcnow())) + db_session.commit() + elif action == 'remove': + db_session.query(db.GroupJunction).filter_by(group_id=group_id, post_id=image_id).delete() + db_session.commit() + + return ':3' + + @blueprint.route('/metadata/', methods=['GET']) def metadata(img_id): """ diff --git a/gallery/db.py b/gallery/db.py index e903b6b..38eb061 100644 --- a/gallery/db.py +++ b/gallery/db.py @@ -3,7 +3,6 @@ OnlyLegs - Database Database models and functions for SQLAlchemy """ import os -from datetime import datetime import platformdirs from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime, ForeignKey, PickleType @@ -83,6 +82,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) group_id = Column(Integer, ForeignKey('groups.id')) post_id = Column(Integer, ForeignKey('posts.id')) diff --git a/gallery/groups.py b/gallery/groups.py new file mode 100644 index 0000000..e1b52a7 --- /dev/null +++ b/gallery/groups.py @@ -0,0 +1,89 @@ +""" +Onlylegs - API endpoints +Used intermally by the frontend and possibly by other applications +""" +import logging +import json +from datetime import datetime as dt + +from flask import Blueprint, abort, jsonify, render_template, url_for, request, g + +from sqlalchemy.orm import sessionmaker + +from gallery.auth import login_required + +from . import db # Import db to create a session + + +blueprint = Blueprint('group', __name__, url_prefix='/group') +db_session = sessionmaker(bind=db.engine) +db_session = db_session() + + +@blueprint.route('/', methods=['GET']) +def groups(): + """ + Group overview, shows all image groups + """ + groups = db_session.query(db.Groups).all() + + for group in groups: + thumbnail = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group.id).order_by(db.GroupJunction.date_added.desc()).first() + + if thumbnail is not None: + thumbnail_filename = db_session.query(db.Posts.file_name).filter(db.Posts.id == thumbnail[0]).first() + group.thumbnail = thumbnail_filename[0] + else: + group.thumbnail = None + + return render_template('groups/list.html', groups=groups) + +@blueprint.route('/') +def group(group_id): + """ + Group view, shows all images in a group + """ + group = db_session.query(db.Groups).filter(db.Groups.id == group_id).first() + + if group is None: + abort(404, 'Group not found') + + group_images = db_session.query(db.GroupJunction.post_id).filter(db.GroupJunction.group_id == group_id).order_by(db.GroupJunction.date_added.desc()).all() + + images = [] + for image in group_images: + image = db_session.query(db.Posts).filter(db.Posts.id == image[0]).first() + images.append(image) + + return render_template('groups/group.html', group=group, images=images) + +@blueprint.route('//') +def group_post(group_id, image_id): + """ + Image view, shows the image and its metadata from a specific group + """ + img = db_session.query(db.Posts).filter(db.Posts.id == image_id).first() + + if img is None: + abort(404, 'Image not found') + + img.author_username = db_session.query(db.Users.username).filter(db.Users.id == img.author_id).first()[0] + + groups = db_session.query(db.GroupJunction.group_id).filter(db.GroupJunction.post_id == image_id).all() + img.groups = [] + for group in groups: + group = db_session.query(db.Groups).filter(db.Groups.id == group[0]).first() + img.groups.append(group) + + next = 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 = 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() + + if next is not None: + next = url_for('group.group_post', group_id=group_id, image_id=next[0]) + if prev is not None: + prev = url_for('group.group_post', group_id=group_id, image_id=prev[0]) + + return render_template('image.html', + image=img, + next_url=next, + prev_url=prev) \ No newline at end of file diff --git a/gallery/routing.py b/gallery/routing.py index b242905..315fd78 100644 --- a/gallery/routing.py +++ b/gallery/routing.py @@ -3,7 +3,7 @@ Onlylegs Gallery - Routing """ from datetime import datetime as dt -from flask import Blueprint, render_template, current_app, request, g +from flask import Blueprint, render_template, url_for from werkzeug.exceptions import abort from sqlalchemy.orm import sessionmaker @@ -24,15 +24,10 @@ def index(): """ images = db_session.query(db.Posts.file_name, db.Posts.image_colours, - db.Posts.author_id, db.Posts.created_at, db.Posts.id).order_by(db.Posts.id.desc()).all() - return render_template('index.html', - images=images, - image_count=len(images), - name=current_app.config['WEBSITE']['name'], - motto=current_app.config['WEBSITE']['motto']) + return render_template('index.html', images=images) @blueprint.route('/image/') def image(image_id): @@ -42,53 +37,25 @@ def image(image_id): img = db_session.query(db.Posts).filter(db.Posts.id == image_id).first() if img is None: - abort(404, 'Image not found') + abort(404, 'Image not found :<') - author = db_session.query(db.Users.username).filter(db.Users.id == img.author_id).first()[0] - img.author_username = author + img.author_username = db_session.query(db.Users.username).filter(db.Users.id == img.author_id).first()[0] - next = db_session.query(db.Posts.id).filter(db.Posts.id > image_id).order_by(db.Posts.id.asc()).first() + groups = db_session.query(db.GroupJunction.group_id).filter(db.GroupJunction.post_id == image_id).all() + img.groups = [] + for group in groups: + group = db_session.query(db.Groups).filter(db.Groups.id == group[0]).first() + img.groups.append(group) + + next = db_session.query(db.Posts.id).filter(db.Posts.id > image_id).order_by(db.Posts.id.asc()).first() prev = db_session.query(db.Posts.id).filter(db.Posts.id < image_id).order_by(db.Posts.id.desc()).first() if next is not None: - next = next[0] + next = url_for('gallery.image', image_id=next[0]) if prev is not None: - prev = prev[0] + prev = url_for('gallery.image', image_id=prev[0]) - return render_template('image.html', - image=img, - exif=img.image_exif, - next=next, - prev=prev, - next_id=next, - prev_id=prev) - -@blueprint.route('/group', methods=['GET', 'POST']) -def groups(): - """ - Group overview, shows all image groups - """ - if request.method == 'GET': - groups = db_session.query(db.Groups.name, db.Groups.author_id).all() - - return render_template('group.html', groups=groups) - elif request.method == 'POST': - group_name = request.form['name'] - group_description = request.form['description'] - group_author = g.user.id - - new_group = db.Groups(name=group_name, description=group_description, author_id=group_author, created_at=dt.now()) - - db_session.add(new_group) - - return ':3' - -@blueprint.route('/group/') -def group(group_id): - """ - Group view, shows all images in a group - """ - return render_template('group.html', group_id=group_id) + return render_template('image.html', image=img, next_url=next, prev_url=prev) @blueprint.route('/profile') def profile(): diff --git a/gallery/static/js/main.js b/gallery/static/js/main.js index 0c0d38d..e93970b 100644 --- a/gallery/static/js/main.js +++ b/gallery/static/js/main.js @@ -1,18 +1,3 @@ -let navToggle = true; - -document.onscroll = function() { - if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) { - document.querySelector('.jumpUp').classList = 'jumpUp jumpUp--show'; - } else { - document.querySelector('.jumpUp').classList = 'jumpUp'; - } -} - -document.querySelector('.jumpUp').onclick = function() { - document.body.scrollTop = 0; - document.documentElement.scrollTop = 0; -} - function imgFade(obj) { $(obj).animate({opacity: 1}, 250); } @@ -36,6 +21,43 @@ for (let i = 0; i < times.length; i++) { times[i].innerHTML = dateTime.toLocaleDateString() + ' ' + dateTime.toLocaleTimeString(); } +let images = document.querySelectorAll('.gallery-item img'); +function loadOnView() { + for (let i = 0; i < images.length; i++) { + let image = images[i]; + if (image.getBoundingClientRect().top < window.innerHeight && image.getBoundingClientRect().bottom > 0) { + if (!image.src) { + image.src = `/api/uploads/${image.getAttribute('data-src')}?w=500&h=500` + } + } + } +} +if (images.length > 0) { + window.onload = function() { + loadOnView(); + }; + window.onscroll = function() { + loadOnView(); + }; + window.onresize = function() { + loadOnView(); + }; +} + + +document.onscroll = function() { + if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) { + document.querySelector('.jumpUp').classList = 'jumpUp jumpUp--show'; + } else { + document.querySelector('.jumpUp').classList = 'jumpUp'; + } +} +document.querySelector('.jumpUp').onclick = function() { + document.body.scrollTop = 0; + document.documentElement.scrollTop = 0; +} + + function uploadFile(){ // AJAX takes control of subby form event.preventDefault(); diff --git a/gallery/templates/group.html b/gallery/templates/group.html deleted file mode 100644 index af7f60c..0000000 --- a/gallery/templates/group.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends 'layout.html' %} - -{% block nav_groups %}navigation-item__selected{% endblock %} - -{% block content %} - - - -
- - - -
-{% endblock %} - -{% block script %} - -{% endblock %} \ No newline at end of file diff --git a/gallery/templates/groups/group.html b/gallery/templates/groups/group.html new file mode 100644 index 0000000..aff0b3b --- /dev/null +++ b/gallery/templates/groups/group.html @@ -0,0 +1,60 @@ +{% extends 'layout.html' %} + +{% block nav_groups %}navigation-item__selected{% endblock %} + +{% block content %} + + +
+ + + + +
+ + {% if images %} + + {% else %} +
+

No image!

+ {% if g.user %} +

You can get started by uploading an image!

+ {% else %} +

Login to start uploading images!

+ {% endif %} +
+ {% endif %} +{% endblock %} + +{% block script %} + +{% endblock %} \ No newline at end of file diff --git a/gallery/templates/groups/list.html b/gallery/templates/groups/list.html new file mode 100644 index 0000000..ac5b334 --- /dev/null +++ b/gallery/templates/groups/list.html @@ -0,0 +1,52 @@ +{% extends 'layout.html' %} + +{% block nav_groups %}navigation-item__selected{% endblock %} + +{% block content %} + + +
+ + + +
+ + {% if groups %} + + {% else %} +
+

No image groups!

+ {% if g.user %} +

You can get started by creating a new image group!

+ {% else %} +

Login to get started!

+ {% endif %} +
+ {% endif %} +{% endblock %} + +{% block script %} + +{% endblock %} \ No newline at end of file diff --git a/gallery/templates/image.html b/gallery/templates/image.html index dfe7940..2773c48 100644 --- a/gallery/templates/image.html +++ b/gallery/templates/image.html @@ -4,8 +4,8 @@ {% block content %}
- - + +
@@ -15,18 +15,18 @@
- {% if next_id %} + {% if next_url %} - {% if g.user['id'] == image['author_id'] %} + {% if image.author_id == g.user.id %}
{% endif %} - {% if prev_id %} + {% if prev_url %}
- {% if image['post_description'] %} + {% if image.post_description %}
- +

Description

-

{{ image['post_description'] }}

+

{{ image.post_description }}

{% endif %}
- {% for tag in exif %} + {% for tag in image.image_exif %}
{% if tag == 'Photographer' %} @@ -155,7 +169,7 @@

File

{% else %} -

{{tag}}

+

{{ tag }}

{% endif %} @@ -163,17 +177,17 @@
- {% for subtag in exif[tag] %} + {% for subtag in image.image_exif[tag] %} - - {% if exif[tag][subtag]['formatted'] %} - {% if exif[tag][subtag]['type'] == 'date' %} - + + {% if image.image_exif[tag][subtag]['formatted'] %} + {% if image.image_exif[tag][subtag]['type'] == 'date' %} + {% else %} - + {% endif %} - {% elif exif[tag][subtag]['raw'] %} - + {% elif image.image_exif[tag][subtag]['raw'] %} + {% else %} {% endif %} @@ -213,7 +227,7 @@ $('.image-fullscreen').addClass('image-fullscreen__active'); if ($('.image-fullscreen img').attr('src') == '') { - $('.image-fullscreen img').attr('src', '/api/uploads/{{ image['file_name'] }}'); + $('.image-fullscreen img').attr('src', '/api/uploads/{{ image.file_name }}'); } }); @@ -235,18 +249,14 @@ 'DESTRUCTION!!!!!!', 'This will delete the image and all of its data!!! This action is irreversible!!!!! Are you sure you want to do this?????', '', - '' + '' ); }); - $('#img-edit').click(function() { - window.location.href = '/image/{{ image['id'] }}/edit'; - }); - function deleteImage() { popupDissmiss(); $.ajax({ - url: '/api/remove/{{ image['id'] }}', + url: '{{ url_for('api.delete_image', image_id=image['id']) }}', type: 'post', data: { action: 'delete' @@ -259,6 +269,10 @@ } }); } + + $('#img-edit').click(function() { + window.location.href = '/image/{{ image.id }}/edit'; + }); {% endif %} {% endblock %} \ No newline at end of file diff --git a/gallery/templates/index.html b/gallery/templates/index.html index 8d5b249..148214c 100644 --- a/gallery/templates/index.html +++ b/gallery/templates/index.html @@ -8,53 +8,45 @@ - + {% if images %} + + {% else %} +
+

No image!

+ {% if g.user %} +

You can get started by uploading an image!

+ {% else %} +

Login to start uploading images!

+ {% endif %} +
+ {% endif %} + + {% endblock %} {% block script %}
{{subtag}}{{exif[tag][subtag]['formatted']}}{{ subtag }}{{ image.image_exif[tag][subtag]['formatted'] }}{{exif[tag][subtag]['formatted']}}{{ image.image_exif[tag][subtag]['formatted'] }}{{exif[tag][subtag]['raw']}}{{ image.image_exif[tag][subtag]['raw'] }}Oops, an error