217 lines
6.5 KiB
Python
217 lines
6.5 KiB
Python
"""
|
|
Image-related rendering helpers for the gallery image view.
|
|
"""
|
|
|
|
# Standard library imports.
|
|
from pathlib import Path
|
|
import datetime
|
|
|
|
# Django imports.
|
|
from django.http import HttpResponseNotFound
|
|
from django.shortcuts import render, redirect
|
|
from django.conf import settings
|
|
from django.utils import timezone
|
|
|
|
# Third-party
|
|
from PIL import Image
|
|
|
|
# Project imports.
|
|
from .models import Image as Im
|
|
|
|
from .common import (
|
|
normalize_sort,
|
|
normalize_theme,
|
|
build_query,
|
|
gallery_url,
|
|
sort_images,
|
|
build_sort_options,
|
|
build_breadcrumbs,
|
|
get_first_image_thumbnail_url,
|
|
is_image_file,
|
|
make_thumbnail,
|
|
)
|
|
|
|
|
|
def render_image(request, path_text, full_path):
|
|
"""
|
|
Renders the view corresponding to an image file.
|
|
"""
|
|
|
|
try:
|
|
img = Im.objects.get(path=full_path, user=request.user)
|
|
|
|
if request.method == 'POST':
|
|
img.favorite = not img.favorite
|
|
|
|
except Im.DoesNotExist:
|
|
img = Im(path=full_path, user=request.user)
|
|
|
|
finally:
|
|
img.last_visited = timezone.now()
|
|
img.visits = img.visits + 1
|
|
img.save()
|
|
|
|
# Preserve query state (sort, search, theme) similar to gallery view
|
|
search_text = request.GET.get("search", "").strip()
|
|
sort_key = normalize_sort(request.GET.get("sort", "abc"))
|
|
theme = normalize_theme(request.GET.get("theme", "dark"))
|
|
query_state = build_query(search_text, sort_key, theme)
|
|
|
|
image = Path("/imgs/").joinpath(path_text)
|
|
img_dir = full_path.parent
|
|
|
|
try:
|
|
entries = [
|
|
entry
|
|
for entry in img_dir.iterdir()
|
|
if entry.is_file() and not entry.is_symlink() and is_image_file(entry)
|
|
]
|
|
except OSError:
|
|
return HttpResponseNotFound("Not found")
|
|
|
|
# Sort siblings according to requested sort mode
|
|
images_sorted = sort_images(entries, sort_key)
|
|
|
|
# Find index of current image
|
|
try:
|
|
index = next(i for i, p in enumerate(images_sorted) if p.name == full_path.name)
|
|
except StopIteration:
|
|
return HttpResponseNotFound("Not found")
|
|
|
|
prev_path = images_sorted[index - 1] if index > 0 else None
|
|
next_path = images_sorted[index + 1] if index < len(images_sorted) - 1 else None
|
|
|
|
# Helper to produce thumb url for a Path or None
|
|
def thumb_for(path_obj):
|
|
if path_obj is None:
|
|
return None
|
|
try:
|
|
make_thumbnail(path_obj)
|
|
rel = path_obj.relative_to(settings.GALLERY_ROOT)
|
|
thumb_path = settings.THUMBNAILS_ROOT.joinpath(rel)
|
|
if thumb_path.exists():
|
|
return "/thumbs/" + str(rel).replace("\\", "/")
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
# Build URLs (preserving query state)
|
|
prev_url = None
|
|
next_url = None
|
|
if prev_path is not None:
|
|
rel = prev_path.relative_to(settings.GALLERY_ROOT)
|
|
prev_url = gallery_url(rel, False, query_state)
|
|
|
|
if next_path is not None:
|
|
rel = next_path.relative_to(settings.GALLERY_ROOT)
|
|
next_url = gallery_url(rel, False, query_state)
|
|
|
|
# Back (directory) and Home (root) links and thumbnails
|
|
dir_rel = None
|
|
try:
|
|
# derive directory path text relative to gallery root
|
|
dir_rel = full_path.parent.relative_to(settings.GALLERY_ROOT)
|
|
dir_text = str(dir_rel).replace("\\", "/")
|
|
except Exception:
|
|
dir_text = ""
|
|
|
|
back_url = gallery_url(
|
|
Path(dir_text) if dir_text != "" else None, True, query_state
|
|
)
|
|
back_thumb = get_first_image_thumbnail_url(full_path.parent)
|
|
home_url = gallery_url(None, True, query_state)
|
|
home_thumb = get_first_image_thumbnail_url(settings.GALLERY_ROOT)
|
|
|
|
# Prev/next thumbnails
|
|
prev_thumb = thumb_for(prev_path)
|
|
next_thumb = thumb_for(next_path)
|
|
|
|
# Image metadata
|
|
width = height = None
|
|
filesize = None
|
|
created_ts = None
|
|
modified_ts = None
|
|
try:
|
|
img_file = full_path
|
|
with Image.open(img_file) as im:
|
|
width, height = im.size
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
stat = full_path.stat()
|
|
filesize = stat.st_size
|
|
created_ts = getattr(stat, "st_birthtime", stat.st_ctime)
|
|
modified_ts = stat.st_mtime
|
|
except Exception:
|
|
pass
|
|
|
|
def human_size(bytes_val):
|
|
if bytes_val is None:
|
|
return None
|
|
kb = 1024.0
|
|
if bytes_val < kb * 1024:
|
|
return f"{bytes_val / kb:.2f} KB"
|
|
return f"{bytes_val / (kb * kb):.2f} MB"
|
|
|
|
def fmt_ts(ts):
|
|
if ts is None:
|
|
return None
|
|
try:
|
|
return datetime.datetime.fromtimestamp(ts).strftime("%c")
|
|
except Exception:
|
|
return None
|
|
|
|
# Breadcrumbs: include directory breadcrumbs then append file name as label-only
|
|
# build_breadcrumbs expects a path_text representing directories only
|
|
dir_path_text = dir_text if dir_text != "" else ""
|
|
breadcrumbs = build_breadcrumbs(dir_path_text, query_state)
|
|
breadcrumbs.append({"label": full_path.name, "path": None})
|
|
|
|
next_theme = "light" if theme == "dark" else "dark"
|
|
theme_query = {"sort": sort_key, "theme": next_theme}
|
|
if search_text != "":
|
|
theme_query["search"] = search_text
|
|
|
|
context = {
|
|
"image_path": image,
|
|
# keep legacy prev/next names for tests
|
|
"prev": prev_path.name if prev_path is not None else None,
|
|
"next": next_path.name if next_path is not None else None,
|
|
# new richer values
|
|
"prev_url": prev_url,
|
|
"next_url": next_url,
|
|
"prev_thumb": prev_thumb,
|
|
"next_thumb": next_thumb,
|
|
"back_url": back_url,
|
|
"back_thumb": back_thumb,
|
|
"home_url": home_url,
|
|
"home_thumb": home_thumb,
|
|
"image_meta": {
|
|
"filename": full_path.name,
|
|
"width": width,
|
|
"height": height,
|
|
"filesize": human_size(filesize),
|
|
"created": fmt_ts(created_ts),
|
|
"modified": fmt_ts(modified_ts),
|
|
"visits": img.visits,
|
|
"visited": fmt_ts(img.last_visited.timestamp()),
|
|
"favorite": img.favorite
|
|
},
|
|
"breadcrumbs": breadcrumbs,
|
|
"theme": theme,
|
|
"sort_key": sort_key,
|
|
"sort_label": "",
|
|
"sort_options": build_sort_options(request, search_text, sort_key, theme),
|
|
"theme_toggle_url": gallery_url(
|
|
Path(dir_path_text) if dir_path_text != "" else None, True, theme_query
|
|
),
|
|
"path": path_text,
|
|
}
|
|
|
|
from .common import SORT_LABELS
|
|
|
|
context["sort_label"] = SORT_LABELS.get(sort_key, "")
|
|
|
|
return render(request, "image_view.html", context)
|