From 8a4fe891ef789b5776c15a92050dbc8bbadeae3e Mon Sep 17 00:00:00 2001
From: Fluffy-Bean <michal-gdula@protonmail.com>
Date: Sun, 24 Sep 2023 19:53:14 +0100
Subject: [PATCH] Add context menu script

---
 onlylegs/api.py                               | 107 ++++++++++----
 onlylegs/models.py                            |  24 +++-
 onlylegs/static/js/contextMenu.js             | 135 ++++++++++++++++++
 onlylegs/static/js/imagePage.js               |  64 ++++++++-
 onlylegs/static/js/popup.js                   |  16 ++-
 .../static/sass/components/context-menu.sass  | 128 +++++++++++++++++
 onlylegs/static/sass/style.sass               |   1 +
 onlylegs/templates/image.html                 |  67 +++------
 onlylegs/views/image.py                       |  17 +--
 9 files changed, 463 insertions(+), 96 deletions(-)
 create mode 100644 onlylegs/static/js/contextMenu.js
 create mode 100644 onlylegs/static/sass/components/context-menu.sass

diff --git a/onlylegs/api.py b/onlylegs/api.py
index fffeea5..44c187c 100644
--- a/onlylegs/api.py
+++ b/onlylegs/api.py
@@ -7,6 +7,9 @@ import re
 import logging
 from uuid import uuid4
 
+from PIL import Image
+from PIL.ExifTags import TAGS
+
 from flask import (
     Blueprint,
     abort,
@@ -20,8 +23,7 @@ from flask_login import login_required, current_user
 from colorthief import ColorThief
 
 from onlylegs.extensions import db
-from onlylegs.models import Users, Pictures
-from onlylegs.utils.metadata import yoink
+from onlylegs.models import Users, Pictures, Exif
 from onlylegs.utils.generate_image import generate_thumbnail
 
 
@@ -137,46 +139,91 @@ def upload():
     """
     Uploads an image to the server and saves it to the database
     """
-    form_file = request.files.get("file", None)
-    form = request.form
+    image_description = request.form.get("description", "").strip()
+    image_alt = request.form.get("alt", "").strip()
+    image_file = request.files.get("file", None)
 
-    if not form_file:
+    if not image_file:
         return jsonify({"message": "No file"}), 400
 
     # Get file extension, generate random name and set file path
-    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
-    )
+    image_mime = pathlib.Path(image_file.filename).suffix.replace(".", "").lower()
+    image_name = "GWAGWA_" + str(uuid4())
+    image_filename = image_name + "." + image_mime
+    save_path = os.path.join(current_app.config["UPLOAD_FOLDER"], image_filename)
 
-    # 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 image_mime not in current_app.config["ALLOWED_EXTENSIONS"].keys():
+        logging.info("File extension not allowed: %s", image_mime)
         return jsonify({"message": "File extension not allowed"}), 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)
-        return jsonify({"message": "Error saving file"}), 500
+    image_file.save(save_path)
 
-    img_exif = yoink(img_path)  # Get EXIF data
-    img_colors = ColorThief(img_path).get_palette(color_count=3)  # Get color palette
+    image_colours = ColorThief(save_path).get_palette(color_count=3)
 
-    # Save to database
-    query = Pictures(
+    image_record = Pictures(
         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"],
+        filename=image_filename,
+        mimetype=image_mime,
+        colours=image_colours,
+        description=image_description,
+        alt=image_alt,
     )
+    db.session.add(image_record)
+    db.session.commit()
 
-    db.session.add(query)
+    image_exif = []
+    with Image.open(save_path) as file:
+        image_exif.append(
+            Exif(
+                picture_id=image_record.id,
+                key="FileName",
+                value=image_filename,
+            )
+        )
+        image_exif.append(
+            Exif(
+                picture_id=image_record.id,
+                key="FileSize",
+                value=os.path.getsize(save_path),
+            )
+        )
+        image_exif.append(
+            Exif(
+                picture_id=image_record.id,
+                key="FileFormat",
+                value=image_mime,
+            )
+        )
+        image_exif.append(
+            Exif(
+                picture_id=image_record.id,
+                key="FileWidth",
+                value=file.size[0],
+            )
+        )
+        image_exif.append(
+            Exif(
+                picture_id=image_record.id,
+                key="FileHeight",
+                value=file.size[1],
+            )
+        )
+
+        try:
+            tags = file._getexif()
+            for tag, value in TAGS.items():
+                if tag in tags:
+                    image_exif.append(
+                        Exif(
+                            picture_id=image_record.id,
+                            key=value,
+                            value=tags[tag],
+                        )
+                    )
+        except TypeError:
+            pass
+
+    db.session.add_all(image_exif)
     db.session.commit()
 
     return jsonify({"message": "File uploaded"}), 200
diff --git a/onlylegs/models.py b/onlylegs/models.py
index 6970a9b..565b130 100644
--- a/onlylegs/models.py
+++ b/onlylegs/models.py
@@ -6,7 +6,7 @@ from flask_login import UserMixin
 from onlylegs.extensions import db
 
 
-class AlbumJunction(db.Model):  # pylint: disable=too-few-public-methods, C0103
+class AlbumJunction(db.Model):
     """
     Junction table for picturess and albums
     Joins with picturess and albums
@@ -26,7 +26,7 @@ class AlbumJunction(db.Model):  # pylint: disable=too-few-public-methods, C0103
     )
 
 
-class Pictures(db.Model):  # pylint: disable=too-few-public-methods, C0103
+class Pictures(db.Model):
     """
     Pictures table
     """
@@ -38,7 +38,6 @@ class Pictures(db.Model):  # pylint: disable=too-few-public-methods, C0103
 
     filename = db.Column(db.String, unique=True, nullable=False)
     mimetype = db.Column(db.String, nullable=False)
-    exif = db.Column(db.PickleType, nullable=False)
     colours = db.Column(db.PickleType, nullable=False)
 
     description = db.Column(db.String, nullable=False)
@@ -51,9 +50,24 @@ class Pictures(db.Model):  # pylint: disable=too-few-public-methods, C0103
     )
 
     album_fk = db.relationship("AlbumJunction", backref="pictures")
+    exif_fk = db.relationship("Exif", backref="pictures")
 
 
-class Albums(db.Model):  # pylint: disable=too-few-public-methods, C0103
+class Exif(db.Model):
+    """
+    Exif data for pictures
+    """
+
+    __tablename__ = "exif"
+
+    id = db.Column(db.Integer, primary_key=True)
+    picture_id = db.Column(db.Integer, db.ForeignKey("pictures.id"))
+
+    key = db.Column(db.String, nullable=False)
+    value = db.Column(db.String, nullable=False)
+
+
+class Albums(db.Model):
     """
     albums table
     """
@@ -75,7 +89,7 @@ class Albums(db.Model):  # pylint: disable=too-few-public-methods, C0103
     album_fk = db.relationship("AlbumJunction", backref="albums")
 
 
-class Users(db.Model, UserMixin):  # pylint: disable=too-few-public-methods, C0103
+class Users(db.Model, UserMixin):
     """
     Users table
     """
diff --git a/onlylegs/static/js/contextMenu.js b/onlylegs/static/js/contextMenu.js
new file mode 100644
index 0000000..c89829a
--- /dev/null
+++ b/onlylegs/static/js/contextMenu.js
@@ -0,0 +1,135 @@
+function showContextMenu(obj, menu, position='mouse') {
+    // If the context menu is already open, close it first
+    if (document.querySelector(".contextMenu")) {
+        dissmissContextMenu();
+    }
+
+    // Add span to close the context menu
+    let contextCloseSpan = document.createElement("span");
+        contextCloseSpan.className = "contextMenuClose";
+        contextCloseSpan.onclick = dissmissContextMenu;
+
+    // Create the context menu
+    let contextMenu = document.createElement("div");
+        contextMenu.className = "contextMenu";
+
+    // Create the menu items
+    menu.forEach(array => {
+        if (array.value === "divider") {
+            let divider = document.createElement("hr");
+                divider.className = "contextMenuDivider";
+
+            contextMenu.appendChild(divider);
+            return;
+        } else if (array['value'] === "title") {
+            let titleP = document.createElement("p");
+                titleP.className = "contextMenuTitle";
+                titleP.innerHTML = array.text;
+
+            contextMenu.appendChild(titleP);
+            let divider = document.createElement("hr");
+                divider.className = "contextMenuDivider";
+
+            contextMenu.appendChild(divider);
+            return;
+        }
+
+        let itemBtn = document.createElement("button");
+            itemBtn.className = "contextMenuItem";
+            itemBtn.onclick = array['function'];
+
+        if (array['type'] === "critical") {
+            itemBtn.classList.add("contextMenuItem__critical");
+        } else if (array['type'] === "warning") {
+            itemBtn.classList.add("contextMenuItem__warning");
+        } else if (array['type'] === "success") {
+            itemBtn.classList.add("contextMenuItem__success");
+        } else if (array['type'] === "info") {
+            itemBtn.classList.add("contextMenuItem__info");
+        }
+
+        let itemIcon = document.createElement("span");
+            itemIcon.className = "contextMenuIcon";
+        if (array['icon']) {
+            itemIcon.innerHTML = array['icon'];
+        } else {
+           itemIcon.innerHTML = '';
+        }
+        itemBtn.appendChild(itemIcon);
+
+        // Create the text for the action
+        let itemText = document.createElement("p");
+            itemText.className = "contextMenuText";
+            itemText.innerHTML = array.value;
+        itemBtn.appendChild(itemText);
+
+        contextMenu.appendChild(itemBtn);
+    });
+
+    // Add the context menu to the body
+    document.body.appendChild(contextMenu);
+    document.body.appendChild(contextCloseSpan);
+
+    let posX;
+    let posY;
+
+    if (position === 'mouse') {
+        posX = event.clientX + 5;
+        posY = event.clientY + 5;
+    } else if (position === 'button') {
+        posX = obj.offsetLeft + (obj.offsetWidth / 2) - (contextMenu.offsetWidth / 2);
+        posY = obj.offsetTop + obj.offsetHeight + 5;
+    } else if (position === 'center') {
+        posX = (window.innerWidth / 2) - (contextMenu.offsetWidth / 2);
+        posY = (window.innerHeight / 2) - (contextMenu.offsetHeight / 2);
+    } else {
+        posX = event.clientX + 5;
+        posY = event.clientY + 5;
+    }
+
+    // Move the context menu if it is off the screen
+    if (posX + contextMenu.offsetWidth > window.innerWidth) {
+        posX = window.innerWidth - (contextMenu.offsetWidth + 5);
+    } else if (posX < 0) {
+        posX = 5;
+    }
+    if (posY < 0) {
+        posY = 5;
+    }
+    contextMenu.style.left = posX + "px";
+    contextMenu.style.top = posY + "px";
+
+    // Timeout otherwise animation doesn't work
+    setTimeout(function() {
+        if (position === 'mouse') {
+            contextMenu.classList.add("contextMenu__show--mouse");
+        } else if (position === 'button') {
+            contextMenu.classList.add("contextMenu__show--button");
+        } else if (position === 'center') {
+            contextMenu.classList.add("contextMenu__show--center");
+        } else {
+            contextMenu.classList.add("contextMenu__show");
+        }
+    }, 1);
+}
+
+function dissmissContextMenu() {
+    // Remove the close span
+    let contextSpan = document.querySelectorAll(".contextMenuClose");
+    contextSpan.forEach(menu => {
+        menu.remove();
+    });
+
+    // Get the context menu
+    let contextMenu = document.querySelectorAll(".contextMenu");
+    contextMenu.forEach(menu => {
+        menu.classList.add("contextMenu__hide");
+        setTimeout(function() {
+            menu.remove();
+        }, 500);
+    });
+}
+
+window.onresize = () => {
+    dissmissContextMenu();
+}
diff --git a/onlylegs/static/js/imagePage.js b/onlylegs/static/js/imagePage.js
index b98d46d..a1eb24c 100644
--- a/onlylegs/static/js/imagePage.js
+++ b/onlylegs/static/js/imagePage.js
@@ -12,6 +12,69 @@ function imageFullscreen() {
         document.cookie = "image-info=1"
     }
 }
+
+function imageShowOptionsPopup(obj) {
+    // let title = 'Options';
+    // let subtitle = null;
+    //
+    // let body = document.createElement('div');
+    //     body.style.cssText = 'display: flex; flex-direction: column; gap: 0.5rem;';
+    //
+    // let copyBtn = document.createElement('button');
+    //     copyBtn.classList.add('btn-block');
+    //     copyBtn.innerHTML = 'Copy URL';
+    //     copyBtn.onclick = () => {
+    //         copyToClipboard(window.location.href)
+    //     }
+    //
+    // let downloadBtn = document.createElement('a');
+    //     downloadBtn.classList.add('btn-block');
+    //     downloadBtn.innerHTML = 'Download';
+    //     downloadBtn.href = '/api/media/uploads/' + image_data["filename"];
+    //     downloadBtn.download = '';
+    //
+    // body.appendChild(copyBtn);
+    // body.appendChild(downloadBtn);
+    //
+    // if (image_data["owner"]) {
+    //     let editBtn = document.createElement('button');
+    //         editBtn.classList.add('btn-block');
+    //         editBtn.classList.add('critical');
+    //         editBtn.innerHTML = 'Edit';
+    //         editBtn.onclick = imageEditPopup;
+    //
+    //     let deleteBtn = document.createElement('button');
+    //         deleteBtn.classList.add('btn-block');
+    //         deleteBtn.classList.add('critical');
+    //         deleteBtn.innerHTML = 'Delete';
+    //         deleteBtn.onclick = imageDeletePopup;
+    //
+    //     body.appendChild(editBtn);
+    //     body.appendChild(deleteBtn);
+    // }
+    //
+    // popupShow(title, subtitle, body, [popupCancelButton]);
+
+    showContextMenu(obj, [
+        {
+            'value': 'Edit',
+            'function': () => {
+                dissmissContextMenu();
+                imageEditPopup();
+            },
+            'type': 'critical'
+        },
+        {
+            'value': 'Delete',
+            'function': () => {
+                dissmissContextMenu();
+                imageDeletePopup();
+            },
+            'type': 'critical'
+        }
+    ], 'button')
+}
+
 function imageDeletePopup() {
     let title = 'DESTRUCTION!!!!!!';
     let subtitle =
@@ -92,7 +155,6 @@ function imageEditConfirm(event) {
         body: form,
     }).then(response => {
         if (response.ok) {
-            popupDismiss();
             window.location.reload();
         } else {
             addNotification('Image *clings*', 2);
diff --git a/onlylegs/static/js/popup.js b/onlylegs/static/js/popup.js
index 0ef8cc4..31914cd 100644
--- a/onlylegs/static/js/popup.js
+++ b/onlylegs/static/js/popup.js
@@ -9,13 +9,17 @@ function popupShow(titleText, subtitleText, bodyContent=null, userActions=null)
     actionsSelector.innerHTML = '';
 
     // Set popup header and subtitle
-    let titleElement = document.createElement('h2');
-        titleElement.innerHTML = titleText;
-        headerSelector.appendChild(titleElement);
+    if (titleText) {
+        let titleElement = document.createElement('h2');
+            titleElement.innerHTML = titleText;
+            headerSelector.appendChild(titleElement);
+    }
 
-    let subtitleElement = document.createElement('p');
-        subtitleElement.innerHTML = subtitleText;
-        headerSelector.appendChild(subtitleElement);
+    if (subtitleText) {
+        let subtitleElement = document.createElement('p');
+            subtitleElement.innerHTML = subtitleText;
+            headerSelector.appendChild(subtitleElement);
+    }
 
     if (bodyContent) { headerSelector.appendChild(bodyContent) }
 
diff --git a/onlylegs/static/sass/components/context-menu.sass b/onlylegs/static/sass/components/context-menu.sass
new file mode 100644
index 0000000..15ab78b
--- /dev/null
+++ b/onlylegs/static/sass/components/context-menu.sass
@@ -0,0 +1,128 @@
+.contextMenuClose
+    width: 100%
+    height: 100%
+
+    position: fixed
+    top: 0
+    left: 0
+
+    background-color: transparent
+    z-index: 99998
+
+.contextMenu
+    margin: 0
+    padding: 0.25rem
+
+    width: calc( 100vw - 10px )
+    height: auto
+    max-width: 300px
+
+    position: absolute
+
+    display: flex
+    flex-direction: column
+    justify-content: flex-start
+    align-items: flex-start
+    gap: 0.25rem
+
+    background-color: RGB($bg-300)
+    border: 1px solid RGB($bg-200)
+    border-radius: 6px
+
+    overflow: hidden
+
+    transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out
+    transform-origin: center center
+    opacity: 0.5
+    transform: scale(0, 0)
+    z-index: 99999
+
+.contextMenuTitle
+    margin: 0
+    padding: 0.25rem 0.5rem
+
+    width: 100%
+
+    text-align: center
+    font-size: 1.2rem
+    font-weight: 400
+    color: RGB($fg-white)
+
+.contextMenuItem
+    margin: 0
+    padding: 0.5rem
+
+    width: 100%
+    height: auto
+
+    display: flex
+    flex-direction: row
+    justify-content: flex-start
+    align-items: center
+    gap: 0.5rem
+
+    background-color: RGB($bg-300)
+    color: RGB($fg-white)
+    border: none
+    border-radius: 3px
+
+    cursor: pointer
+.contextMenuItem:hover
+    background-color: RGB($bg-200)
+.contextMenuItem__critical
+    color: RGB($critical)
+.contextMenuItem__warning
+    color: RGB($warning)
+.contextMenuItem__success
+    color: RGB($success)
+.contextMenuItem__info
+    color: RGB($primary)
+
+.contextMenuText
+    margin: 0
+    padding: 0
+
+    font-size: 1rem
+    font-weight: 400
+
+.contextMenuIcon
+    margin: 0
+    padding: 0
+
+    width: 1.25rem
+    height: 1.25rem
+
+    display: flex
+    justify-content: center
+    align-items: center
+
+.contextMenuDivider
+    margin: 0 auto
+    padding: 0
+
+    width: 100%
+    height: 1px
+
+    border: none
+    background-color: RGB($bg-200)
+
+.contextMenu__show
+    opacity: 1
+    transform: scale(1, 1)
+
+.contextMenu__show--mouse
+    opacity: 1
+    transform: scale(1, 1)
+    transform-origin: top left
+.contextMenu__show--button
+    opacity: 1
+    transform: scale(1, 1)
+    transform-origin: top center
+.contextMenu__show--center
+    opacity: 1
+    transform: scale(1, 1)
+    transform-origin: center center
+
+.contextMenu__hide
+    opacity: 0
+    transform: scale(0, 0)
diff --git a/onlylegs/static/sass/style.sass b/onlylegs/static/sass/style.sass
index daf7094..c363234 100644
--- a/onlylegs/static/sass/style.sass
+++ b/onlylegs/static/sass/style.sass
@@ -6,6 +6,7 @@
 
 @import "components/notification"
 @import "components/pop-up"
+@import "components/context-menu"
 @import "components/upload-panel"
 @import "components/tags"
 
diff --git a/onlylegs/templates/image.html b/onlylegs/templates/image.html
index 2a76cf0..edb8a12 100644
--- a/onlylegs/templates/image.html
+++ b/onlylegs/templates/image.html
@@ -13,7 +13,8 @@
             'id': {{ image.id }},
             'description': '{{ image.description }}',
             'alt': '{{ image.alt }}',
-        };
+            'filename': '{{ image.filename }}',
+        }
     </script>
 
     <style>
@@ -31,16 +32,12 @@
             <div class="pill-row">
                 {% if next_url %}<div><a class="pill-item" href="{{ next_url }}"><i class="ph ph-arrow-left"></i></a></div>{% endif %}
                 <div>
-                    <button class="pill-item" onclick="imageFullscreen()" id="fullscreenImage"><i class="ph ph-info"></i></button>
+                    <button class="pill-item" onclick="imageFullscreen()"><i class="ph ph-info"></i></button>
                     <button class="pill-item" onclick="copyToClipboard(window.location.href)"><i class="ph ph-export"></i></button>
-                    <a class="pill-item" href="{{ url_for('api.media', path='uploads/' + image.filename) }}" download onclick="addNotification('Download started!', 4)"><i class="ph ph-file-arrow-down"></i></a>
+                    {% if image.author.id == current_user.id %}
+                        <button class="pill-item pill__critical" onclick="imageShowOptionsPopup(this)"><i class="ph-fill ph-dots-three-outline-vertical"></i></button>
+                    {% endif %}
                 </div>
-                {% if current_user.id == image.author.id %}
-                    <div>
-                        <button class="pill-item pill__critical" onclick="imageDeletePopup()"><i class="ph ph-trash"></i></button>
-                        <button class="pill-item pill__critical" onclick="imageEditPopup()"><i class="ph ph-pencil-simple"></i></button>
-                    </div>
-                {% endif %}
                 {% if prev_url %}<div><a class="pill-item" href="{{ prev_url }}"><i class="ph ph-arrow-right"></i></a></div>{% endif %}
             </div>
         </div>
@@ -116,42 +113,20 @@
                 </div>
             {% endif %}
         </details>
-        {% for tag in image.exif %}
-            <details open>
-                <summary>
-                    {% if tag == 'Photographer' %}
-                        <i class="ph ph-person"></i><h2>Photographer</h2>
-                    {% elif tag == 'Camera' %}
-                        <i class="ph ph-camera"></i><h2>Camera</h2>
-                    {% elif tag == 'Software' %}
-                        <i class="ph ph-desktop-tower"></i><h2>Software</h2>
-                    {% elif tag == 'File' %}
-                        <i class="ph ph-file-image"></i><h2>File</h2>
-                    {% else %}
-                        <i class="ph ph-file-image"></i><h2>{{ tag }}</h2>
-                    {% endif %}
-                    <span style="width: 100%"></span>
-                    <i class="ph ph-caret-down collapse-indicator"></i>
-                </summary>
-                <table>
-                    {% for subtag in image.exif[tag] %}
-                        <tr>
-                            <td>{{ subtag }}</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.exif[tag][subtag]['formatted'] }}</td>
-                                {% endif %}
-                            {% elif image.exif[tag][subtag]['raw'] %}
-                                <td>{{ image.exif[tag][subtag]['raw'] }}</td>
-                            {% else %}
-                                <td class="empty-table">Oops, an error</td>
-                            {% endif %}
-                        </tr>
-                    {% endfor %}
-                </table>
-            </details>
-        {% endfor %}
+        <details open>
+            <summary>
+                <i class="ph ph-file-image"></i><h2>Metadata</h2>
+                <span style="width: 100%"></span>
+                <i class="ph ph-caret-down collapse-indicator"></i>
+            </summary>
+            <table>
+                {% for tag in image_exif %}
+                    <tr>
+                        <td>{{ tag.key }}</td>
+                        <td>{{ tag.value }}</td>
+                    </tr>
+                {% endfor %}
+            </table>
+        </details>
     </div>
 {% endblock %}
diff --git a/onlylegs/views/image.py b/onlylegs/views/image.py
index 75a70fd..16ee582 100644
--- a/onlylegs/views/image.py
+++ b/onlylegs/views/image.py
@@ -15,7 +15,7 @@ from flask import (
     jsonify,
 )
 from flask_login import current_user
-from onlylegs.models import Pictures, AlbumJunction, Albums
+from onlylegs.models import Pictures, AlbumJunction, Albums, Exif
 from onlylegs.extensions import db
 
 
@@ -28,7 +28,8 @@ def image(image_id):
     Image view, shows the image and its metadata
     """
     # Get the image, if it doesn't exist, 404
-    image = db.get_or_404(Pictures, image_id, description="Image not found :<")
+    image_record = db.get_or_404(Pictures, image_id, description="Image not found :<")
+    image_record_exif = Exif.query.filter(Exif.picture_id == image_id).all()
 
     # Get all groups the image is in
     groups = (
@@ -38,9 +39,9 @@ def image(image_id):
     )
 
     # Get the group data for each group the image is in
-    image.groups = []
+    image_record.groups = []
     for group in groups:
-        image.groups.append(
+        image_record.groups.append(
             Albums.query.with_entities(Albums.id, Albums.name)
             .filter(Albums.id == group[0])
             .first()
@@ -72,9 +73,8 @@ def image(image_id):
     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:
-        return_page = None
-    else:
+    return_page = None
+    if len(total_images) > limit:
         # 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
@@ -90,7 +90,8 @@ def image(image_id):
 
     return render_template(
         "image.html",
-        image=image,
+        image=image_record,
+        image_exif=image_record_exif,
         next_url=next_url,
         prev_url=prev_url,
         return_page=return_page,