from pathlib import Path import tempfile from django.test import TestCase, override_settings from django.contrib.auth.models import User from django.utils import timezone from viewer.models import Image as Im class SpecialGalleriesTests(TestCase): def setUp(self): # prepare a temporary gallery root and files self.tmp_gallery = tempfile.TemporaryDirectory() self.gallery_root = Path(self.tmp_gallery.name) self.settings_override = override_settings(GALLERY_ROOT=self.gallery_root) self.settings_override.enable() self.user = User.objects.create_user("specuser", "s@example.com", "pw") # create files under gallery root now = timezone.now() a = self.gallery_root / "a.jpg" b = self.gallery_root / "b.jpg" c = self.gallery_root / "c.jpg" for p in (a, b, c): p.write_bytes(b"x") Im.objects.create( user=self.user, path=str(a), favorite=True, visits=5, last_visited=now ) Im.objects.create( user=self.user, path=str(b), favorite=False, visits=10, last_visited=now ) Im.objects.create( user=self.user, path=str(c), favorite=True, visits=2, last_visited=now ) # client for view tests from django.test import Client self.client = Client() self.client.force_login(self.user) def tearDown(self): self.settings_override.disable() self.tmp_gallery.cleanup() def test_get_special_paths_filters_and_orders(self): from viewer.specials import get_special_paths favs = get_special_paths(self.user, "favorites") # favorites should include only those marked favorite (a and c) fav_names = sorted([p.name for p in favs]) self.assertEqual(fav_names, ["a.jpg", "c.jpg"]) most = get_special_paths(self.user, "most-visited") # most-visited should be ordered descending by visits, expect b.jpg first self.assertGreaterEqual(len(most), 1) self.assertEqual(most[0].name, "b.jpg") def test_favorites_directory_view_and_links(self): # Directory listing for favorites should show only favorite images resp = self.client.get("/gallery/favorites/") self.assertEqual(resp.status_code, 200) ctx = resp.context self.assertTrue(ctx.get("is_special")) names = [img["name"] for img in ctx.get("images")] self.assertEqual(sorted(names), ["a.jpg", "c.jpg"]) # Back should be hidden (None) and home present self.assertIsNone(ctx.get("back_url")) self.assertIsNotNone(ctx.get("home_url")) self.assertTrue(str(ctx.get("home_url")).startswith("/gallery/")) # Breadcrumb should point to the special root breadcrumbs = ctx.get("breadcrumbs") self.assertEqual(breadcrumbs[0]["label"], "Favorites") self.assertTrue(breadcrumbs[0]["path"].startswith("/gallery/favorites/")) def test_favorites_image_view_prev_next_and_breadcrumbs(self): # Image view under special gallery should scope prev/next to favorites only resp = self.client.get("/gallery/favorites/a.jpg/") self.assertEqual(resp.status_code, 200) ctx = resp.context self.assertTrue(ctx.get("is_special")) # Breadcrumbs: first is special root (clickable), last is filename non-clickable crumbs = ctx.get("breadcrumbs") self.assertEqual(crumbs[0]["label"], "Favorites") self.assertIsNotNone(crumbs[0]["path"]) self.assertEqual(crumbs[-1]["label"], "a.jpg") self.assertIsNone(crumbs[-1]["path"]) # Prev should be None for first in alphabetical order among favorites self.assertIsNone(ctx.get("prev")) # Next should be c.jpg (since favorites are a.jpg and c.jpg sorted) self.assertIn("c.jpg", ctx.get("next")) def test_favorites_image_view_prev_next_urls_and_back_link(self): # Image view under favorites should produce prev/next URLs scoped to favorites resp = self.client.get("/gallery/favorites/a.jpg/") self.assertEqual(resp.status_code, 200) ctx = resp.context self.assertTrue(ctx.get("is_special")) crumbs = ctx.get("breadcrumbs") # back link for template should point to the special root self.assertTrue(crumbs[0]["path"].startswith("/gallery/favorites/")) # prev_url should be None for first favorite; next_url should be inside favorites self.assertIsNone(ctx.get("prev_url")) next_url = ctx.get("next_url") self.assertIsNotNone(next_url) self.assertTrue(next_url.startswith("/gallery/favorites/")) self.assertIn("c.jpg", next_url) def test_most_visited_image_view_prev_next_urls(self): # most-visited image view should scope prev/next URLs to the most-visited gallery resp = self.client.get("/gallery/most-visited/b.jpg/") self.assertEqual(resp.status_code, 200) ctx = resp.context self.assertTrue(ctx.get("is_special")) crumbs = ctx.get("breadcrumbs") self.assertTrue(crumbs[0]["path"].startswith("/gallery/most-visited/")) # b.jpg is the top most-visited image in fixtures => prev_url None, next_url points to a.jpg self.assertIsNone(ctx.get("prev_url")) next_url = ctx.get("next_url") self.assertIsNotNone(next_url) self.assertTrue(next_url.startswith("/gallery/most-visited/")) self.assertIn("a.jpg", next_url) def test_most_visited_and_recent_directory_views(self): # most-visited should list images ordered by visits desc resp = self.client.get("/gallery/most-visited/") self.assertEqual(resp.status_code, 200) names = [img["name"] for img in resp.context.get("images")] # b.jpg had visits=10, should be first self.assertEqual(names[0], "b.jpg") # recent: modify last_visited so that c is newest from viewer.models import Image as ImModel now = timezone.now() # update c to be most recent ImModel.objects.filter(path=str(self.gallery_root / "c.jpg")).update( last_visited=now ) resp2 = self.client.get("/gallery/recent/") self.assertEqual(resp2.status_code, 200) recent_names = [img["name"] for img in resp2.context.get("images")] # c.jpg should be first in recent self.assertEqual(recent_names[0], "c.jpg") def test_missing_and_outside_paths_are_skipped(self): # Create an Image row pointing to a missing file inside gallery from viewer.models import Image as ImModel missing_path = self.gallery_root / "missing.jpg" ImModel.objects.create( user=self.user, path=str(missing_path), favorite=True, visits=1 ) # Create a file outside gallery root and an Image row pointing to it outside_tmp = tempfile.NamedTemporaryFile(delete=False) outside_tmp.write(b"x") outside_tmp.flush() outside_tmp.close() ImModel.objects.create( user=self.user, path=str(Path(outside_tmp.name)), favorite=True, visits=1 ) # favorites listing should still only include existing gallery files (a.jpg and c.jpg) resp = self.client.get("/gallery/favorites/") self.assertEqual(resp.status_code, 200) names = sorted([img["name"] for img in resp.context.get("images")]) self.assertEqual(names, ["a.jpg", "c.jpg"])