164 lines
5.6 KiB
Python
164 lines
5.6 KiB
Python
# Copyright (C) 2021 by BDE ENS Paris-Saclay
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import os
|
|
import zipfile
|
|
from io import BytesIO
|
|
from pathlib import Path
|
|
|
|
from django.contrib import messages
|
|
from django.contrib.auth.mixins import (LoginRequiredMixin,
|
|
PermissionRequiredMixin)
|
|
from django.core.mail import mail_managers
|
|
from django.db import IntegrityError
|
|
from django.http import HttpResponse
|
|
from django.urls import reverse_lazy
|
|
from django.utils.text import slugify
|
|
from django.views.generic.dates import ArchiveIndexView, YearArchiveView
|
|
from django.views.generic.detail import DetailView
|
|
from django.views.generic.edit import FormView
|
|
from PIL import Image
|
|
|
|
from .forms import UploadForm
|
|
from .models import Gallery, Photo, Tag
|
|
|
|
|
|
class GalleryDateView(LoginRequiredMixin):
|
|
queryset = Gallery.objects.filter(is_public=True)
|
|
date_field = 'date_start'
|
|
uses_datetime_field = False # Fix related object access
|
|
allow_empty = True
|
|
|
|
|
|
class GalleryArchiveIndexView(GalleryDateView, ArchiveIndexView):
|
|
pass
|
|
|
|
|
|
class GalleryYearArchiveView(GalleryDateView, YearArchiveView):
|
|
make_object_list = True
|
|
|
|
|
|
class PhotoDetailView(LoginRequiredMixin, DetailView):
|
|
queryset = Photo.objects.filter(is_public=True)
|
|
|
|
|
|
class TagDetail(LoginRequiredMixin, DetailView):
|
|
model = Tag
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""
|
|
Insert the single object into the context dict.
|
|
"""
|
|
current_tag = self.get_object().slug
|
|
context = super().get_context_data(**kwargs)
|
|
context['galleries'] = Gallery.objects.filter(is_public=True) \
|
|
.filter(tags__slug=current_tag) \
|
|
.order_by('-date_start')
|
|
return context
|
|
|
|
|
|
class CustomGalleryDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Custom gallery detail view to filter on photo owner
|
|
"""
|
|
queryset = Gallery.objects.filter(is_public=True)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
# Query with owner to reduce database lag
|
|
context['photos'] = self.object.public().select_related('owner')
|
|
|
|
# List owners
|
|
context['owners'] = []
|
|
for photo in context['photos']:
|
|
if photo.owner not in context['owners']:
|
|
context['owners'].append(photo.owner)
|
|
|
|
# Filter on owner
|
|
if 'owner' in self.kwargs:
|
|
context['photos'] = context['photos'].filter(owner__id=self.kwargs['owner'])
|
|
|
|
return context
|
|
|
|
|
|
class GalleryDownload(LoginRequiredMixin, DetailView):
|
|
model = Gallery
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
"""
|
|
Download a zip file of the gallery on GET request.
|
|
"""
|
|
# Create zip file with pictures
|
|
gallery = self.get_object()
|
|
byte_data = BytesIO()
|
|
zip_file = zipfile.ZipFile(byte_data, "w")
|
|
for photo in gallery.public():
|
|
filename = os.path.basename(os.path.normpath(photo.image.path))
|
|
zip_file.write(photo.image.path, filename)
|
|
zip_file.close()
|
|
|
|
# Return zip file
|
|
response = HttpResponse(byte_data.getvalue(), content_type='application/x-zip-compressed')
|
|
response['Content-Disposition'] = f"attachment; filename={gallery.slug}.zip"
|
|
return response
|
|
|
|
|
|
class GalleryUpload(PermissionRequiredMixin, FormView):
|
|
"""
|
|
Form to upload new photos in a gallery
|
|
"""
|
|
form_class = UploadForm
|
|
template_name = "photologue/upload.html"
|
|
success_url = reverse_lazy("photologue:pl-gallery-upload")
|
|
permission_required = 'photologue.add_gallery'
|
|
|
|
def form_valid(self, form):
|
|
# Upload photos
|
|
# We take files from the request to support multiple upload
|
|
files = self.request.FILES.getlist('file_field')
|
|
gallery = form.get_or_create_gallery()
|
|
gallery_year = Path(str(gallery.date_start.year))
|
|
gallery_dir = gallery_year / gallery.slug
|
|
failed_upload = 0
|
|
for photo_file in files:
|
|
# Check that we have a valid image
|
|
try:
|
|
opened = Image.open(photo_file)
|
|
opened.verify()
|
|
except Exception:
|
|
# Pillow doesn't recognize it as an image, skip it
|
|
messages.error(self.request, f"{photo_file.name} was not recognized as an image")
|
|
failed_upload += 1
|
|
continue
|
|
|
|
title = f"{gallery.title} - {photo_file.name}"
|
|
try:
|
|
photo = Photo(
|
|
title=title,
|
|
slug=slugify(title),
|
|
owner=self.request.user,
|
|
)
|
|
photo_name = str(gallery_dir / photo_file.name)
|
|
photo.image.save(photo_name, photo_file)
|
|
photo.save()
|
|
photo.galleries.set([gallery])
|
|
except IntegrityError:
|
|
messages.error(self.request, f"{photo_file.name} was not uploaded. Maybe the photo was already uploaded.")
|
|
failed_upload += 1
|
|
|
|
# Notify user then managers
|
|
if not failed_upload:
|
|
messages.success(self.request, "All photos has been successfully uploaded.")
|
|
else:
|
|
n_success = len(files) - failed_upload
|
|
messages.warning(self.request, f"Only {n_success} photos were successfully uploaded !")
|
|
|
|
gallery_title = form.cleaned_data['gallery'] or form.cleaned_data.get('new_gallery_title', '')
|
|
photos = ", ".join(f.name for f in files)
|
|
mail_managers(
|
|
subject="New photos upload",
|
|
message=f"{self.request.user.username} has uploaded in `{gallery_title}`: {photos}",
|
|
)
|
|
|
|
return super().form_valid(form)
|