website/photos/views.py
import os
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404
from django.http.request import HttpRequest as HttpRequest
from django.shortcuts import get_object_or_404, redirect
from django.views.generic import TemplateView
from facedetection.models import ReferenceFace
from photos.models import Album, Photo
from photos.services import (
check_shared_album_token,
get_annotated_accessible_albums,
is_album_accessible,
)
from thaliawebsite.views import PagedView
from utils.media.services import fetch_thumbnails, get_media_url
COVER_FILENAME = "cover.jpg"
class IndexView(LoginRequiredMixin, PagedView):
model = Album
paginate_by = 16
template_name = "photos/index.html"
context_object_name = "albums"
keywords = None
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
self.keywords = request.GET.get("keywords", "").split()
def get_queryset(self):
albums = Album.objects.filter(hidden=False, is_processing=False).select_related(
"_cover"
)
for key in self.keywords:
albums = albums.filter(title__icontains=key)
albums = get_annotated_accessible_albums(self.request, albums)
albums = albums.order_by("-date")
return albums
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["keywords"] = self.keywords
fetch_thumbnails([x.cover.file for x in context["object_list"] if x.cover])
context[
"has_processing_reference_faces"
] = self.request.member.reference_faces.filter(
status=ReferenceFace.Status.PROCESSING,
marked_for_deletion_at__isnull=True,
).exists()
context[
"has_rejected_reference_faces"
] = self.request.member.reference_faces.filter(
status=ReferenceFace.Status.REJECTED,
marked_for_deletion_at__isnull=True,
).exists()
context["has_reference_faces"] = self.request.member.reference_faces.filter(
marked_for_deletion_at__isnull=True
).exists()
return context
class _BaseAlbumView(TemplateView):
template_name = "photos/album.html"
def get_album(self, **kwargs):
raise NotImplementedError
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
album = self.get_album(**kwargs)
context["album"] = album
photos = album.photo_set.select_properties("num_likes")
# Fix select_properties dropping the default ordering.
photos = photos.order_by("pk")
# Prefetch thumbnails for efficiency
fetch_thumbnails([p.file for p in photos])
context["photos"] = photos
return context
class AlbumDetailView(LoginRequiredMixin, _BaseAlbumView):
"""Render an album, if it is accessible by the user."""
def get_album(self, **kwargs):
slug = kwargs.get("slug")
album = get_object_or_404(
Album.objects.filter(hidden=False, is_processing=False), slug=slug
)
if not is_album_accessible(self.request, album):
raise Http404("Sorry, you're not allowed to view this album")
return album
class SharedAlbumView(_BaseAlbumView):
"""Render a shared album if the correct token is provided."""
def get_album(self, **kwargs):
slug = kwargs.get("slug")
token = kwargs.get("token")
album = get_object_or_404(
Album.objects.filter(hidden=False, is_processing=False), slug=slug
)
check_shared_album_token(album, token)
return album
def _photo_path(obj, filename):
"""Return the path to a Photo."""
photoname = os.path.basename(filename)
albumpath = os.path.join(obj.photosdir, obj.dirname)
photopath = os.path.join(albumpath, photoname)
get_object_or_404(Photo.objects.filter(album=obj, file=photopath))
return photopath
def _download(request, obj, filename):
"""Download a photo.
This function provides a layer of indirection for shared albums.
"""
photopath = _photo_path(obj, filename)
photo = get_object_or_404(Photo.objects.filter(album=obj, file=photopath))
return redirect(get_media_url(photo.file, attachment=f"{obj.slug}-{filename}"))
@login_required
def download(request, slug, filename):
"""Download a photo if the album of the photo is accessible by the user."""
obj = get_object_or_404(Album, slug=slug)
if is_album_accessible(request, obj):
return _download(request, obj, filename)
raise Http404("Sorry, you're not allowed to view this album")
def shared_download(request, slug, token, filename):
"""Download a photo from a shared album if the album token is provided."""
obj = get_object_or_404(Album, slug=slug)
check_shared_album_token(obj, token)
return _download(request, obj, filename)
class LikedPhotoView(LoginRequiredMixin, PagedView):
model = Photo
paginate_by = 16
template_name = "photos/liked-photos.html"
context_object_name = "photos"
def get_queryset(self):
photos = (
Photo.objects.filter(
likes__member=self.request.member,
album__hidden=False,
album__is_processing=False,
)
.select_related("album")
.select_properties("num_likes")
.order_by("-album__date")
)
return photos
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
fetch_thumbnails([p.file for p in context["photos"]])
return context