import os
from django.contrib.auth.decorators import login_required
from django.core.paginator import EmptyPage, Paginator
from django.http import Http404
from django.shortcuts import get_object_or_404, render
from django.utils.translation import get_language
from django_sendfile import sendfile
from photos.models import Album, Photo
from photos.services import (
check_shared_album_token,
get_annotated_accessible_albums,
is_album_accessible,
)
COVER_FILENAME = "cover.jpg"
[docs]@login_required
def index(request):
"""Render the index page showing multiple album cards."""
keywords = request.GET.get("keywords", "").split()
# Only show published albums
albums = Album.objects.filter(hidden=False)
for key in keywords:
albums = albums.filter(**{f"title_{get_language()}__icontains": key})
albums = get_annotated_accessible_albums(request, albums)
albums = albums.order_by("-date")
paginator = Paginator(albums, 16)
page = request.GET.get("page")
page = 1 if page is None or not page.isdigit() else int(page)
try:
albums = paginator.page(page)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
albums = paginator.page(paginator.num_pages)
page = paginator.num_pages
# Show the two pages before and after the current page
page_range_start = max(1, page - 2)
page_range_stop = min(page + 3, paginator.num_pages + 1)
# Add extra pages if we show less than 5 pages
page_range_start = min(page_range_start, page_range_stop - 5)
page_range_start = max(1, page_range_start)
# Add extra pages if we still show less than 5 pages
page_range_stop = max(page_range_stop, page_range_start + 5)
page_range_stop = min(page_range_stop, paginator.num_pages + 1)
page_range = range(page_range_start, page_range_stop)
return render(
request,
"photos/index.html",
{"albums": albums, "page_range": page_range, "keywords": keywords},
)
def _render_album_page(request, album):
"""Render album.html for a specified album."""
context = {"album": album, "photos": album.photo_set.filter(hidden=False)}
return render(request, "photos/album.html", context)
[docs]@login_required
def detail(request, slug):
"""Render an album, if it accessible by the user."""
obj = get_object_or_404(Album, slug=slug)
if is_album_accessible(request, obj):
return _render_album_page(request, obj)
raise Http404("Sorry, you're not allowed to view this album")
[docs]def shared_album(request, slug, token):
"""Render a shared album if the correct token is provided."""
obj = get_object_or_404(Album, slug=slug)
check_shared_album_token(obj, token)
return _render_album_page(request, obj)
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 sendfile(request, photo.file.path, attachment=True)
[docs]@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")
[docs]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)