feat(viewer): persist theme/sort via toggle view; use middleware-provided theme/sort in views and templates
This commit is contained in:
@@ -170,16 +170,22 @@ def build_sort_options(request, search_text, sort_key, theme):
|
||||
options = []
|
||||
|
||||
for option_key, label in SORT_OPTIONS:
|
||||
query = {"sort": option_key, "theme": theme}
|
||||
# Build a URL that points to the settings toggle endpoint which will
|
||||
# persist the chosen sort (and optionally theme) in the user's
|
||||
# UserSettings and then redirect back to the current view. Include
|
||||
# the current full path as the `next` parameter so the toggle view
|
||||
# can return to the same page.
|
||||
query = {"next": request.get_full_path(), "sort": option_key, "theme": theme}
|
||||
|
||||
if search_text != "":
|
||||
# Include search top-level so templates/tests can assert its presence
|
||||
query["search"] = search_text
|
||||
|
||||
options.append(
|
||||
{
|
||||
"key": option_key,
|
||||
"label": label,
|
||||
"url": append_query(request.path, query),
|
||||
"url": append_query("/gallery/toggle-settings/", query),
|
||||
"is_active": option_key == sort_key,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ from .common import (
|
||||
normalize_theme,
|
||||
build_query,
|
||||
gallery_url,
|
||||
append_query,
|
||||
sort_images,
|
||||
build_sort_options,
|
||||
build_breadcrumbs,
|
||||
@@ -68,9 +69,16 @@ def render_directory(request, path_text, full_path):
|
||||
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", "abc"))
|
||||
theme = normalize_theme(request.GET.get("theme", "dark"))
|
||||
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)
|
||||
|
||||
try:
|
||||
@@ -130,10 +138,18 @@ def render_directory(request, path_text, full_path):
|
||||
)
|
||||
|
||||
next_theme = "light" if theme == "dark" else "dark"
|
||||
theme_query = {"sort": sort_key, "theme": next_theme}
|
||||
|
||||
# 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_query["search"] = search_text
|
||||
theme_toggle_query["search"] = search_text
|
||||
|
||||
search_action_query = {"sort": sort_key, "theme": theme}
|
||||
|
||||
@@ -147,8 +163,8 @@ def render_directory(request, path_text, full_path):
|
||||
"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
|
||||
"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
|
||||
|
||||
@@ -23,6 +23,7 @@ from .common import (
|
||||
normalize_theme,
|
||||
build_query,
|
||||
gallery_url,
|
||||
append_query,
|
||||
sort_images,
|
||||
build_sort_options,
|
||||
build_breadcrumbs,
|
||||
@@ -40,7 +41,7 @@ def render_image(request, path_text, full_path):
|
||||
try:
|
||||
img = Im.objects.get(path=full_path, user=request.user)
|
||||
|
||||
if request.method == 'POST':
|
||||
if request.method == "POST":
|
||||
img.favorite = not img.favorite
|
||||
|
||||
except Im.DoesNotExist:
|
||||
@@ -51,10 +52,16 @@ def render_image(request, path_text, full_path):
|
||||
img.visits = img.visits + 1
|
||||
img.save()
|
||||
|
||||
# Preserve query state (sort, search, theme) similar to gallery view
|
||||
# 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", "abc"))
|
||||
theme = normalize_theme(request.GET.get("theme", "dark"))
|
||||
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)
|
||||
@@ -169,9 +176,13 @@ def render_image(request, path_text, full_path):
|
||||
breadcrumbs.append({"label": full_path.name, "path": None})
|
||||
|
||||
next_theme = "light" if theme == "dark" else "dark"
|
||||
theme_query = {"sort": sort_key, "theme": next_theme}
|
||||
theme_query = {
|
||||
"next": request.get_full_path(),
|
||||
"sort": sort_key,
|
||||
"theme": next_theme,
|
||||
}
|
||||
if search_text != "":
|
||||
theme_query["search"] = search_text
|
||||
theme_query["next"] = request.get_full_path()
|
||||
|
||||
context = {
|
||||
"image_path": image,
|
||||
@@ -196,16 +207,14 @@ def render_image(request, path_text, full_path):
|
||||
"modified": fmt_ts(modified_ts),
|
||||
"visits": img.visits,
|
||||
"visited": fmt_ts(img.last_visited.timestamp()),
|
||||
"favorite": img.favorite
|
||||
"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
|
||||
),
|
||||
"theme_toggle_url": append_query("/gallery/toggle-settings/", theme_query),
|
||||
"path": path_text,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
from django.urls import path
|
||||
|
||||
# Module imports
|
||||
from .views import (
|
||||
gallery_view
|
||||
)
|
||||
from .views import gallery_view, toggle_settings
|
||||
|
||||
###########################################################################################
|
||||
# URL Patterns. #
|
||||
@@ -13,6 +11,7 @@ from .views import (
|
||||
|
||||
urlpatterns = [
|
||||
# Views.
|
||||
path('', gallery_view, name = 'gallery_view_root'),
|
||||
path('<path:path>/', gallery_view, name = 'gallery_view_path'),
|
||||
path("", gallery_view, name="gallery_view_root"),
|
||||
path("toggle-settings/", toggle_settings, name="toggle_settings"),
|
||||
path("<path:path>/", gallery_view, name="gallery_view_path"),
|
||||
]
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
"""Top-level views module.
|
||||
|
||||
After refactor this file only keeps the minimal public view entry points and imports
|
||||
helpers from the new `directory` and `image` modules.
|
||||
helpers from the new `directory` and `image` modules. Also provides the
|
||||
`toggle_settings` view used by template buttons to persist theme/sort.
|
||||
"""
|
||||
|
||||
# Django imports.
|
||||
from urllib.parse import urlparse
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import redirect
|
||||
from django.core.exceptions import MultipleObjectsReturned
|
||||
|
||||
# Local helpers split into modules
|
||||
from .directory import render_directory
|
||||
from .image import render_image
|
||||
|
||||
from .models import UserSettings
|
||||
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
@@ -44,3 +49,51 @@ def gallery_view(request, path=None):
|
||||
return render_directory(request, path_text, candidate)
|
||||
|
||||
return render_image(request, path_text, candidate)
|
||||
|
||||
|
||||
@login_required
|
||||
def toggle_settings(request):
|
||||
"""Persist theme and/or sort for the current user and redirect back.
|
||||
|
||||
Expected query params:
|
||||
- next: the URL to redirect back to (optional)
|
||||
- theme: optional, 'light' or 'dark'
|
||||
- sort: optional, one of allowed sort keys
|
||||
|
||||
The view will obtain or create the UserSettings row for the user and set
|
||||
any provided values. If multiple UserSettings rows exist (shouldn't
|
||||
normally happen) the first is used.
|
||||
"""
|
||||
|
||||
next_url = request.GET.get("next") or "/gallery/"
|
||||
|
||||
# Only allow in-site redirects for safety
|
||||
parsed = urlparse(next_url)
|
||||
if parsed.netloc and parsed.netloc != "":
|
||||
next_url = "/gallery/"
|
||||
|
||||
user = getattr(request, "user", None)
|
||||
if not user or not getattr(user, "is_authenticated", False):
|
||||
return redirect(next_url)
|
||||
|
||||
# Obtain or create the settings row
|
||||
try:
|
||||
settings_obj = UserSettings.objects.get(user=user)
|
||||
except UserSettings.DoesNotExist:
|
||||
settings_obj = UserSettings(user=user)
|
||||
except MultipleObjectsReturned:
|
||||
settings_obj = UserSettings.objects.filter(user=user).first()
|
||||
|
||||
# Apply provided values
|
||||
theme = request.GET.get("theme")
|
||||
sort = request.GET.get("sort")
|
||||
|
||||
if theme in ("light", "dark"):
|
||||
settings_obj.theme = theme
|
||||
|
||||
if sort:
|
||||
settings_obj.sort = sort
|
||||
|
||||
settings_obj.save()
|
||||
|
||||
return redirect(next_url)
|
||||
|
||||
Reference in New Issue
Block a user