# 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): model = Gallery 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): model = Photo def get_queryset(self): """Non-staff members only see public photos""" qs = super().get_queryset() if self.request.user.is_staff: return qs else: return qs.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(tags__slug=current_tag) \ .order_by('-date_start') return context class GalleryDetailView(LoginRequiredMixin, DetailView): """ Gallery detail view to filter on photo owner """ model = Gallery def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Non-staff members only see public photos if self.request.user.is_staff: context['photos'] = self.object.photos.all() else: context['photos'] = self.object.photos.filter(is_public=True) # 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.photos.filter(is_public=True): 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)