refactor: split directory and image rendering into viewer.directory, viewer.image; add viewer.common for shared helpers
This commit is contained in:
221
viewer/common.py
Normal file
221
viewer/common.py
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
# Standard library imports.
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
from functools import cmp_to_key
|
||||||
|
|
||||||
|
# Django imports.
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
# Project imports.
|
||||||
|
from .utils import make_thumbnail, is_image_file
|
||||||
|
|
||||||
|
###########################################################################################
|
||||||
|
# Constants. #
|
||||||
|
###########################################################################################
|
||||||
|
|
||||||
|
SORT_OPTIONS = [
|
||||||
|
("abc", "Alphabetical A-Z"),
|
||||||
|
("cba", "Alphabetical Z-A"),
|
||||||
|
("old", "Creation date old to new"),
|
||||||
|
("new", "Creation date new to old"),
|
||||||
|
("recent", "Modification date most recent"),
|
||||||
|
("tnecer", "Modification date least recent"),
|
||||||
|
]
|
||||||
|
|
||||||
|
SORT_LABELS = dict(SORT_OPTIONS)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_sort(sort_value):
|
||||||
|
return sort_value if sort_value in SORT_LABELS else "abc"
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_theme(theme_value):
|
||||||
|
return theme_value if theme_value in ("dark", "light") else "dark"
|
||||||
|
|
||||||
|
|
||||||
|
def get_creation_timestamp(path_obj):
|
||||||
|
try:
|
||||||
|
stat_data = path_obj.stat()
|
||||||
|
return getattr(stat_data, "st_birthtime", stat_data.st_ctime)
|
||||||
|
except OSError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_modification_timestamp(path_obj):
|
||||||
|
try:
|
||||||
|
return path_obj.stat().st_mtime
|
||||||
|
except OSError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def build_query(search_text, sort_key, theme):
|
||||||
|
query = {"sort": sort_key, "theme": theme}
|
||||||
|
|
||||||
|
if search_text != "":
|
||||||
|
query["search"] = search_text
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
def append_query(url, query_dict):
|
||||||
|
if len(query_dict) == 0:
|
||||||
|
return url
|
||||||
|
|
||||||
|
return url + "?" + urlencode(query_dict)
|
||||||
|
|
||||||
|
|
||||||
|
def gallery_url(path_obj=None, is_dir=False, query_dict=None):
|
||||||
|
if query_dict is None:
|
||||||
|
query_dict = {}
|
||||||
|
|
||||||
|
if path_obj is None:
|
||||||
|
base_url = "/gallery/"
|
||||||
|
else:
|
||||||
|
path_text = str(path_obj).replace("\\", "/")
|
||||||
|
base_url = "/gallery/" + path_text
|
||||||
|
|
||||||
|
if is_dir and not base_url.endswith("/"):
|
||||||
|
base_url += "/"
|
||||||
|
|
||||||
|
return append_query(base_url, query_dict)
|
||||||
|
|
||||||
|
|
||||||
|
def sort_images(images, sort_key):
|
||||||
|
def compare(img_a, img_b):
|
||||||
|
name_a = img_a.name.lower()
|
||||||
|
name_b = img_b.name.lower()
|
||||||
|
rel_a = str(img_a.relative_to(settings.GALLERY_ROOT)).lower()
|
||||||
|
rel_b = str(img_b.relative_to(settings.GALLERY_ROOT)).lower()
|
||||||
|
|
||||||
|
if sort_key == "abc":
|
||||||
|
if name_a < name_b:
|
||||||
|
return -1
|
||||||
|
if name_a > name_b:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
elif sort_key == "cba":
|
||||||
|
if name_a > name_b:
|
||||||
|
return -1
|
||||||
|
if name_a < name_b:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
elif sort_key == "old":
|
||||||
|
created_a = get_creation_timestamp(img_a)
|
||||||
|
created_b = get_creation_timestamp(img_b)
|
||||||
|
|
||||||
|
if created_a < created_b:
|
||||||
|
return -1
|
||||||
|
if created_a > created_b:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
elif sort_key == "new":
|
||||||
|
created_a = get_creation_timestamp(img_a)
|
||||||
|
created_b = get_creation_timestamp(img_b)
|
||||||
|
|
||||||
|
if created_a > created_b:
|
||||||
|
return -1
|
||||||
|
if created_a < created_b:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
elif sort_key == "recent":
|
||||||
|
modified_a = get_modification_timestamp(img_a)
|
||||||
|
modified_b = get_modification_timestamp(img_b)
|
||||||
|
|
||||||
|
if modified_a > modified_b:
|
||||||
|
return -1
|
||||||
|
if modified_a < modified_b:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
elif sort_key == "tnecer":
|
||||||
|
modified_a = get_modification_timestamp(img_a)
|
||||||
|
modified_b = get_modification_timestamp(img_b)
|
||||||
|
|
||||||
|
if modified_a < modified_b:
|
||||||
|
return -1
|
||||||
|
if modified_a > modified_b:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if name_a < name_b:
|
||||||
|
return -1
|
||||||
|
if name_a > name_b:
|
||||||
|
return 1
|
||||||
|
if rel_a < rel_b:
|
||||||
|
return -1
|
||||||
|
if rel_a > rel_b:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return sorted(images, key=cmp_to_key(compare))
|
||||||
|
|
||||||
|
|
||||||
|
def build_breadcrumbs(path_text, query_dict):
|
||||||
|
breadcrumbs = [{"label": "Gallery", "path": gallery_url(None, True, query_dict)}]
|
||||||
|
|
||||||
|
if path_text == "":
|
||||||
|
return breadcrumbs
|
||||||
|
|
||||||
|
segments = Path(path_text).parts
|
||||||
|
current = Path("")
|
||||||
|
|
||||||
|
for segment in segments:
|
||||||
|
current = current.joinpath(segment)
|
||||||
|
breadcrumbs.append(
|
||||||
|
{"label": segment, "path": gallery_url(current, True, query_dict)}
|
||||||
|
)
|
||||||
|
|
||||||
|
return breadcrumbs
|
||||||
|
|
||||||
|
|
||||||
|
def build_sort_options(request, search_text, sort_key, theme):
|
||||||
|
options = []
|
||||||
|
|
||||||
|
for option_key, label in SORT_OPTIONS:
|
||||||
|
query = {"sort": option_key, "theme": theme}
|
||||||
|
|
||||||
|
if search_text != "":
|
||||||
|
query["search"] = search_text
|
||||||
|
|
||||||
|
options.append(
|
||||||
|
{
|
||||||
|
"key": option_key,
|
||||||
|
"label": label,
|
||||||
|
"url": append_query(request.path, query),
|
||||||
|
"is_active": option_key == sort_key,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
def get_first_image_thumbnail_url(subdir):
|
||||||
|
try:
|
||||||
|
images = sorted(
|
||||||
|
[
|
||||||
|
entry
|
||||||
|
for entry in subdir.iterdir()
|
||||||
|
if entry.is_file() and not entry.is_symlink() and is_image_file(entry)
|
||||||
|
],
|
||||||
|
key=lambda item: (
|
||||||
|
item.name.lower(),
|
||||||
|
str(item.relative_to(settings.GALLERY_ROOT)).lower(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except OSError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(images) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
first_image = images[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
make_thumbnail(first_image)
|
||||||
|
rel_path = first_image.relative_to(settings.GALLERY_ROOT)
|
||||||
|
thumb_path = settings.THUMBNAILS_ROOT.joinpath(rel_path)
|
||||||
|
|
||||||
|
if thumb_path.exists():
|
||||||
|
return "/thumbs/" + str(rel_path).replace("\\", "/")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
163
viewer/directory.py
Normal file
163
viewer/directory.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
"""
|
||||||
|
Directory-related rendering helpers for the gallery view.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Standard library imports.
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Django imports.
|
||||||
|
from django.http import HttpResponseNotFound
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
# Project imports.
|
||||||
|
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 do_recursive_search(start_path, query):
|
||||||
|
"""
|
||||||
|
Gets all images and sub-directories inside the start_path whose name matches the given query,
|
||||||
|
and then joins the results recursively by iterating in each sub-directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
entries = [entry for entry in start_path.iterdir() if not entry.is_symlink()]
|
||||||
|
except OSError:
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
all_subdirs = sorted(
|
||||||
|
[entry for entry in entries if entry.is_dir()],
|
||||||
|
key=lambda item: (item.name.lower(), str(item).lower()),
|
||||||
|
)
|
||||||
|
|
||||||
|
subdirs = [entry for entry in all_subdirs if query.lower() in entry.name.lower()]
|
||||||
|
|
||||||
|
images = sorted(
|
||||||
|
[
|
||||||
|
entry
|
||||||
|
for entry in entries
|
||||||
|
if entry.is_file()
|
||||||
|
and is_image_file(entry)
|
||||||
|
and query.lower() in entry.name.lower()
|
||||||
|
],
|
||||||
|
key=lambda item: (item.name.lower(), str(item).lower()),
|
||||||
|
)
|
||||||
|
|
||||||
|
for subdir in all_subdirs:
|
||||||
|
rec_subdirs, rec_images = do_recursive_search(subdir, query.lower())
|
||||||
|
subdirs.extend(rec_subdirs)
|
||||||
|
images.extend(rec_images)
|
||||||
|
|
||||||
|
return subdirs, images
|
||||||
|
|
||||||
|
|
||||||
|
def render_directory(request, path_text, full_path):
|
||||||
|
"""
|
||||||
|
Renders the gallery view related to directories, be it the contents of an actual directory
|
||||||
|
in the file system, or logical gallery directories like search result pages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_entries = [
|
||||||
|
entry for entry in full_path.iterdir() if not entry.is_symlink()
|
||||||
|
]
|
||||||
|
except OSError:
|
||||||
|
return HttpResponseNotFound("Not found")
|
||||||
|
|
||||||
|
current_subdirs = sorted(
|
||||||
|
[entry for entry in current_entries if entry.is_dir()],
|
||||||
|
key=lambda item: (
|
||||||
|
item.name.lower(),
|
||||||
|
str(item.relative_to(settings.GALLERY_ROOT)).lower(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if search_text == "":
|
||||||
|
images = [
|
||||||
|
entry
|
||||||
|
for entry in current_entries
|
||||||
|
if entry.is_file() and is_image_file(entry)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
_, images = do_recursive_search(full_path, search_text)
|
||||||
|
|
||||||
|
images = sort_images(images, sort_key)
|
||||||
|
|
||||||
|
image_data = []
|
||||||
|
for image in images:
|
||||||
|
rel_path = image.relative_to(settings.GALLERY_ROOT)
|
||||||
|
image_url = gallery_url(rel_path, False, query_state)
|
||||||
|
|
||||||
|
thumbnail = None
|
||||||
|
try:
|
||||||
|
# use shared make_thumbnail so tests can patch viewer.common.make_thumbnail
|
||||||
|
make_thumbnail(image)
|
||||||
|
thumb_path = settings.THUMBNAILS_ROOT.joinpath(rel_path)
|
||||||
|
if thumb_path.exists():
|
||||||
|
thumbnail = "/thumbs/" + str(rel_path).replace("\\", "/")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
image_data.append(
|
||||||
|
{"path": image_url, "name": image.name, "thumbnail": thumbnail}
|
||||||
|
)
|
||||||
|
|
||||||
|
subdir_data = []
|
||||||
|
for subdir in current_subdirs:
|
||||||
|
rel_path = subdir.relative_to(settings.GALLERY_ROOT)
|
||||||
|
subdir_data.append(
|
||||||
|
{
|
||||||
|
"path": gallery_url(rel_path, True, query_state),
|
||||||
|
"name": subdir.name,
|
||||||
|
"thumbnail": get_first_image_thumbnail_url(subdir),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
next_theme = "light" if theme == "dark" else "dark"
|
||||||
|
theme_query = {"sort": sort_key, "theme": next_theme}
|
||||||
|
|
||||||
|
if search_text != "":
|
||||||
|
theme_query["search"] = search_text
|
||||||
|
|
||||||
|
search_action_query = {"sort": sort_key, "theme": theme}
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"path": path_text,
|
||||||
|
"search_text": search_text,
|
||||||
|
"theme": theme,
|
||||||
|
"sort_key": sort_key,
|
||||||
|
"sort_label": "",
|
||||||
|
"sort_options": build_sort_options(request, search_text, sort_key, theme),
|
||||||
|
"breadcrumbs": build_breadcrumbs(path_text, query_state),
|
||||||
|
"images": image_data,
|
||||||
|
"subdirs": subdir_data,
|
||||||
|
"theme_toggle_url": gallery_url(
|
||||||
|
Path(path_text) if path_text != "" else None, True, theme_query
|
||||||
|
),
|
||||||
|
"search_action_url": gallery_url(
|
||||||
|
Path(path_text) if path_text != "" else None, True, search_action_query
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
# sort_label depends on SORT_LABELS in common; import lazily to avoid circulars
|
||||||
|
from .common import SORT_LABELS
|
||||||
|
|
||||||
|
context["sort_label"] = SORT_LABELS.get(sort_key, "")
|
||||||
|
|
||||||
|
return render(request, "gallery_view.html", context)
|
||||||
195
viewer/image.py
Normal file
195
viewer/image.py
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
"""
|
||||||
|
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
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
# Third-party
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# Project imports.
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 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),
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
from .common import SORT_LABELS
|
||||||
|
|
||||||
|
context["sort_label"] = SORT_LABELS.get(sort_key, "")
|
||||||
|
|
||||||
|
return render(request, "image_view.html", context)
|
||||||
@@ -12,7 +12,9 @@ from django.test import Client, RequestFactory, TestCase, override_settings
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
# Project imports.
|
# Project imports.
|
||||||
from viewer.views import SORT_LABELS, do_recursive_search, gallery_view
|
from viewer.common import SORT_LABELS
|
||||||
|
from viewer.directory import do_recursive_search
|
||||||
|
from viewer.views import gallery_view
|
||||||
|
|
||||||
|
|
||||||
class GalleryBaseTests(TestCase):
|
class GalleryBaseTests(TestCase):
|
||||||
@@ -280,7 +282,7 @@ class GalleryViewTests(GalleryBaseTests):
|
|||||||
self.assertNotIn("symlink_sub", names)
|
self.assertNotIn("symlink_sub", names)
|
||||||
|
|
||||||
def test_thumbnail_failure_does_not_break_render(self):
|
def test_thumbnail_failure_does_not_break_render(self):
|
||||||
with patch("viewer.views.make_thumbnail", side_effect=Exception("fail")):
|
with patch("viewer.common.make_thumbnail", side_effect=Exception("fail")):
|
||||||
response = self.client.get("/gallery/")
|
response = self.client.get("/gallery/")
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
@@ -383,7 +385,7 @@ class GalleryViewTests(GalleryBaseTests):
|
|||||||
def fake_creation(path_obj):
|
def fake_creation(path_obj):
|
||||||
return creation_values.get(path_obj.name, 0)
|
return creation_values.get(path_obj.name, 0)
|
||||||
|
|
||||||
with patch("viewer.views.get_creation_timestamp", side_effect=fake_creation):
|
with patch("viewer.common.get_creation_timestamp", side_effect=fake_creation):
|
||||||
old = self.client.get("/gallery/?sort=old")
|
old = self.client.get("/gallery/?sort=old")
|
||||||
new = self.client.get("/gallery/?sort=new")
|
new = self.client.get("/gallery/?sort=new")
|
||||||
|
|
||||||
|
|||||||
530
viewer/views.py
530
viewer/views.py
@@ -1,275 +1,18 @@
|
|||||||
# Standard library imports.
|
"""Top-level views module.
|
||||||
from pathlib import Path
|
|
||||||
from urllib.parse import urlencode
|
After refactor this file only keeps the minimal public view entry points and imports
|
||||||
from functools import cmp_to_key
|
helpers from the new `directory` and `image` modules.
|
||||||
|
"""
|
||||||
|
|
||||||
# Django imports.
|
# Django imports.
|
||||||
from django.http import HttpResponseNotFound
|
from django.http import HttpResponseNotFound
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
# Project imports.
|
# Local helpers split into modules
|
||||||
from .utils import make_thumbnail, is_image_file
|
from .directory import render_directory
|
||||||
from PIL import Image
|
from .image import render_image
|
||||||
import datetime
|
|
||||||
|
|
||||||
###########################################################################################
|
|
||||||
# Constants. #
|
|
||||||
###########################################################################################
|
|
||||||
|
|
||||||
SORT_OPTIONS = [
|
|
||||||
("abc", "Alphabetical A-Z"),
|
|
||||||
("cba", "Alphabetical Z-A"),
|
|
||||||
("old", "Creation date old to new"),
|
|
||||||
("new", "Creation date new to old"),
|
|
||||||
("recent", "Modification date most recent"),
|
|
||||||
("tnecer", "Modification date least recent"),
|
|
||||||
]
|
|
||||||
|
|
||||||
SORT_LABELS = dict(SORT_OPTIONS)
|
|
||||||
|
|
||||||
###########################################################################################
|
|
||||||
# Helper functions. #
|
|
||||||
###########################################################################################
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_sort(sort_value):
|
|
||||||
return sort_value if sort_value in SORT_LABELS else "abc"
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_theme(theme_value):
|
|
||||||
return theme_value if theme_value in ("dark", "light") else "dark"
|
|
||||||
|
|
||||||
|
|
||||||
def get_creation_timestamp(path_obj):
|
|
||||||
try:
|
|
||||||
stat_data = path_obj.stat()
|
|
||||||
return getattr(stat_data, "st_birthtime", stat_data.st_ctime)
|
|
||||||
except OSError:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def get_modification_timestamp(path_obj):
|
|
||||||
try:
|
|
||||||
return path_obj.stat().st_mtime
|
|
||||||
except OSError:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def build_query(search_text, sort_key, theme):
|
|
||||||
query = {"sort": sort_key, "theme": theme}
|
|
||||||
|
|
||||||
if search_text != "":
|
|
||||||
query["search"] = search_text
|
|
||||||
|
|
||||||
return query
|
|
||||||
|
|
||||||
|
|
||||||
def append_query(url, query_dict):
|
|
||||||
if len(query_dict) == 0:
|
|
||||||
return url
|
|
||||||
|
|
||||||
return url + "?" + urlencode(query_dict)
|
|
||||||
|
|
||||||
|
|
||||||
def gallery_url(path_obj=None, is_dir=False, query_dict=None):
|
|
||||||
if query_dict is None:
|
|
||||||
query_dict = {}
|
|
||||||
|
|
||||||
if path_obj is None:
|
|
||||||
base_url = "/gallery/"
|
|
||||||
else:
|
|
||||||
path_text = str(path_obj).replace("\\", "/")
|
|
||||||
base_url = "/gallery/" + path_text
|
|
||||||
|
|
||||||
if is_dir and not base_url.endswith("/"):
|
|
||||||
base_url += "/"
|
|
||||||
|
|
||||||
return append_query(base_url, query_dict)
|
|
||||||
|
|
||||||
|
|
||||||
def do_recursive_search(start_path, query):
|
|
||||||
"""
|
|
||||||
Gets all images and sub-directories inside the start_path whose name matches the given query,
|
|
||||||
and then joins the results recursively by iterating in each sub-directory.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
entries = [entry for entry in start_path.iterdir() if not entry.is_symlink()]
|
|
||||||
except OSError:
|
|
||||||
return [], []
|
|
||||||
|
|
||||||
all_subdirs = sorted(
|
|
||||||
[entry for entry in entries if entry.is_dir()],
|
|
||||||
key=lambda item: (item.name.lower(), str(item).lower()),
|
|
||||||
)
|
|
||||||
|
|
||||||
subdirs = [entry for entry in all_subdirs if query.lower() in entry.name.lower()]
|
|
||||||
|
|
||||||
images = sorted(
|
|
||||||
[
|
|
||||||
entry
|
|
||||||
for entry in entries
|
|
||||||
if entry.is_file()
|
|
||||||
and is_image_file(entry)
|
|
||||||
and query.lower() in entry.name.lower()
|
|
||||||
],
|
|
||||||
key=lambda item: (item.name.lower(), str(item).lower()),
|
|
||||||
)
|
|
||||||
|
|
||||||
for subdir in all_subdirs:
|
|
||||||
rec_subdirs, rec_images = do_recursive_search(subdir, query.lower())
|
|
||||||
subdirs.extend(rec_subdirs)
|
|
||||||
images.extend(rec_images)
|
|
||||||
|
|
||||||
return subdirs, images
|
|
||||||
|
|
||||||
|
|
||||||
def sort_images(images, sort_key):
|
|
||||||
def compare(img_a, img_b):
|
|
||||||
name_a = img_a.name.lower()
|
|
||||||
name_b = img_b.name.lower()
|
|
||||||
rel_a = str(img_a.relative_to(settings.GALLERY_ROOT)).lower()
|
|
||||||
rel_b = str(img_b.relative_to(settings.GALLERY_ROOT)).lower()
|
|
||||||
|
|
||||||
if sort_key == "abc":
|
|
||||||
if name_a < name_b:
|
|
||||||
return -1
|
|
||||||
if name_a > name_b:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
elif sort_key == "cba":
|
|
||||||
if name_a > name_b:
|
|
||||||
return -1
|
|
||||||
if name_a < name_b:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
elif sort_key == "old":
|
|
||||||
created_a = get_creation_timestamp(img_a)
|
|
||||||
created_b = get_creation_timestamp(img_b)
|
|
||||||
|
|
||||||
if created_a < created_b:
|
|
||||||
return -1
|
|
||||||
if created_a > created_b:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
elif sort_key == "new":
|
|
||||||
created_a = get_creation_timestamp(img_a)
|
|
||||||
created_b = get_creation_timestamp(img_b)
|
|
||||||
|
|
||||||
if created_a > created_b:
|
|
||||||
return -1
|
|
||||||
if created_a < created_b:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
elif sort_key == "recent":
|
|
||||||
modified_a = get_modification_timestamp(img_a)
|
|
||||||
modified_b = get_modification_timestamp(img_b)
|
|
||||||
|
|
||||||
if modified_a > modified_b:
|
|
||||||
return -1
|
|
||||||
if modified_a < modified_b:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
elif sort_key == "tnecer":
|
|
||||||
modified_a = get_modification_timestamp(img_a)
|
|
||||||
modified_b = get_modification_timestamp(img_b)
|
|
||||||
|
|
||||||
if modified_a < modified_b:
|
|
||||||
return -1
|
|
||||||
if modified_a > modified_b:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if name_a < name_b:
|
|
||||||
return -1
|
|
||||||
if name_a > name_b:
|
|
||||||
return 1
|
|
||||||
if rel_a < rel_b:
|
|
||||||
return -1
|
|
||||||
if rel_a > rel_b:
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
return sorted(images, key=cmp_to_key(compare))
|
|
||||||
|
|
||||||
|
|
||||||
def build_breadcrumbs(path_text, query_dict):
|
|
||||||
breadcrumbs = [{"label": "Gallery", "path": gallery_url(None, True, query_dict)}]
|
|
||||||
|
|
||||||
if path_text == "":
|
|
||||||
return breadcrumbs
|
|
||||||
|
|
||||||
segments = Path(path_text).parts
|
|
||||||
current = Path("")
|
|
||||||
|
|
||||||
for segment in segments:
|
|
||||||
current = current.joinpath(segment)
|
|
||||||
breadcrumbs.append(
|
|
||||||
{"label": segment, "path": gallery_url(current, True, query_dict)}
|
|
||||||
)
|
|
||||||
|
|
||||||
return breadcrumbs
|
|
||||||
|
|
||||||
|
|
||||||
def build_sort_options(request, search_text, sort_key, theme):
|
|
||||||
options = []
|
|
||||||
|
|
||||||
for option_key, label in SORT_OPTIONS:
|
|
||||||
query = {"sort": option_key, "theme": theme}
|
|
||||||
|
|
||||||
if search_text != "":
|
|
||||||
query["search"] = search_text
|
|
||||||
|
|
||||||
options.append(
|
|
||||||
{
|
|
||||||
"key": option_key,
|
|
||||||
"label": label,
|
|
||||||
"url": append_query(request.path, query),
|
|
||||||
"is_active": option_key == sort_key,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return options
|
|
||||||
|
|
||||||
|
|
||||||
def get_first_image_thumbnail_url(subdir):
|
|
||||||
try:
|
|
||||||
images = sorted(
|
|
||||||
[
|
|
||||||
entry
|
|
||||||
for entry in subdir.iterdir()
|
|
||||||
if entry.is_file() and not entry.is_symlink() and is_image_file(entry)
|
|
||||||
],
|
|
||||||
key=lambda item: (
|
|
||||||
item.name.lower(),
|
|
||||||
str(item.relative_to(settings.GALLERY_ROOT)).lower(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
except OSError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if len(images) == 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
first_image = images[0]
|
|
||||||
|
|
||||||
try:
|
|
||||||
make_thumbnail(first_image)
|
|
||||||
rel_path = first_image.relative_to(settings.GALLERY_ROOT)
|
|
||||||
thumb_path = settings.THUMBNAILS_ROOT.joinpath(rel_path)
|
|
||||||
|
|
||||||
if thumb_path.exists():
|
|
||||||
return "/thumbs/" + str(rel_path).replace("\\", "/")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################
|
|
||||||
# View functions. #
|
|
||||||
###########################################################################################
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@@ -277,257 +20,6 @@ def index(request):
|
|||||||
return redirect("gallery_view_root")
|
return redirect("gallery_view_root")
|
||||||
|
|
||||||
|
|
||||||
def _render_directory(request, path_text, full_path):
|
|
||||||
"""
|
|
||||||
Renders the gallery view related to directories, be it the contents of an actual directory
|
|
||||||
in the file system, or logical gallery directories like search result pages.
|
|
||||||
"""
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
try:
|
|
||||||
current_entries = [
|
|
||||||
entry for entry in full_path.iterdir() if not entry.is_symlink()
|
|
||||||
]
|
|
||||||
except OSError:
|
|
||||||
return HttpResponseNotFound("Not found")
|
|
||||||
|
|
||||||
current_subdirs = sorted(
|
|
||||||
[entry for entry in current_entries if entry.is_dir()],
|
|
||||||
key=lambda item: (
|
|
||||||
item.name.lower(),
|
|
||||||
str(item.relative_to(settings.GALLERY_ROOT)).lower(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
if search_text == "":
|
|
||||||
images = [
|
|
||||||
entry
|
|
||||||
for entry in current_entries
|
|
||||||
if entry.is_file() and is_image_file(entry)
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
_, images = do_recursive_search(full_path, search_text)
|
|
||||||
|
|
||||||
images = sort_images(images, sort_key)
|
|
||||||
|
|
||||||
image_data = []
|
|
||||||
for image in images:
|
|
||||||
rel_path = image.relative_to(settings.GALLERY_ROOT)
|
|
||||||
image_url = gallery_url(rel_path, False, query_state)
|
|
||||||
|
|
||||||
thumbnail = None
|
|
||||||
try:
|
|
||||||
make_thumbnail(image)
|
|
||||||
thumb_path = settings.THUMBNAILS_ROOT.joinpath(rel_path)
|
|
||||||
if thumb_path.exists():
|
|
||||||
thumbnail = "/thumbs/" + str(rel_path).replace("\\", "/")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
image_data.append(
|
|
||||||
{"path": image_url, "name": image.name, "thumbnail": thumbnail}
|
|
||||||
)
|
|
||||||
|
|
||||||
subdir_data = []
|
|
||||||
for subdir in current_subdirs:
|
|
||||||
rel_path = subdir.relative_to(settings.GALLERY_ROOT)
|
|
||||||
subdir_data.append(
|
|
||||||
{
|
|
||||||
"path": gallery_url(rel_path, True, query_state),
|
|
||||||
"name": subdir.name,
|
|
||||||
"thumbnail": get_first_image_thumbnail_url(subdir),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
next_theme = "light" if theme == "dark" else "dark"
|
|
||||||
theme_query = {"sort": sort_key, "theme": next_theme}
|
|
||||||
|
|
||||||
if search_text != "":
|
|
||||||
theme_query["search"] = search_text
|
|
||||||
|
|
||||||
search_action_query = {"sort": sort_key, "theme": theme}
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"path": path_text,
|
|
||||||
"search_text": search_text,
|
|
||||||
"theme": theme,
|
|
||||||
"sort_key": sort_key,
|
|
||||||
"sort_label": SORT_LABELS[sort_key],
|
|
||||||
"sort_options": build_sort_options(request, search_text, sort_key, theme),
|
|
||||||
"breadcrumbs": build_breadcrumbs(path_text, query_state),
|
|
||||||
"images": image_data,
|
|
||||||
"subdirs": subdir_data,
|
|
||||||
"theme_toggle_url": append_query(request.path, theme_query),
|
|
||||||
"search_action_url": append_query(request.path, search_action_query),
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, "gallery_view.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
def _render_image(request, path_text, full_path):
|
|
||||||
"""
|
|
||||||
Renders the view corresponding to an image file.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 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),
|
|
||||||
},
|
|
||||||
"breadcrumbs": breadcrumbs,
|
|
||||||
"theme": theme,
|
|
||||||
"sort_key": sort_key,
|
|
||||||
"sort_label": SORT_LABELS[sort_key],
|
|
||||||
"sort_options": build_sort_options(request, search_text, sort_key, theme),
|
|
||||||
"theme_toggle_url": append_query(request.path, theme_query),
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, "image_view.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def gallery_view(request, path=None):
|
def gallery_view(request, path=None):
|
||||||
"""
|
"""
|
||||||
@@ -549,6 +41,6 @@ def gallery_view(request, path=None):
|
|||||||
path_text = path if path is not None else ""
|
path_text = path if path is not None else ""
|
||||||
|
|
||||||
if candidate.is_dir():
|
if candidate.is_dir():
|
||||||
return _render_directory(request, path_text, candidate)
|
return render_directory(request, path_text, candidate)
|
||||||
|
|
||||||
return _render_image(request, path_text, candidate)
|
return render_image(request, path_text, candidate)
|
||||||
|
|||||||
Reference in New Issue
Block a user