""" 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, append_query, 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 remains a GET parameter. For sort and theme prefer explicit # GET parameters when present (so query-preserving links behave as # callers expect), otherwise fall back to middleware-provided settings. 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) 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" # The theme toggle button now calls the persistent settings endpoint. # Include `next` (for redirect back) and also surface `sort` and `search` # as top-level query parameters so templates/tests can inspect them easily. theme_toggle_query = { "next": request.get_full_path(), "theme": next_theme, "sort": sort_key, } if search_text != "": theme_toggle_query["search"] = search_text search_action_query = {"sort": sort_key, "theme": theme} # 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) 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, "back_url": back_url, "back_thumb": back_thumb, "home_url": home_url, "home_thumb": home_thumb, "theme_toggle_url": append_query( "/gallery/toggle-settings/", theme_toggle_query ), "search_action_url": gallery_url( Path(path_text) if path_text != "" else None, True, search_action_query ), "clear_search_url": gallery_url( Path(path_text) if path_text != "" else None, True, None ), } # 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)