""" 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, append_query, 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() # Search remains a GET parameter. For sort and theme prefer explicit # GET parameters when present, otherwise fall back to middleware-provided # settings on the request. search_text = request.GET.get("search", "").strip() sort_key = normalize_sort( request.GET.get("sort") or getattr(request, "sort", None) or "abc" ) theme = normalize_theme( request.GET.get("theme") or getattr(request, "theme", None) or "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 = { "next": request.get_full_path(), "sort": sort_key, "theme": next_theme, } if search_text != "": theme_query["next"] = request.get_full_path() 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": append_query("/gallery/toggle-settings/", 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)