import zipfile from zipfile import BadZipFile import logging import datetime import os from io import BytesIO try: import Image except ImportError: from PIL import Image from django import forms from django.utils.translation import ugettext_lazy as _ from django.utils.safestring import mark_safe from django.contrib import messages from django.core.files.base import ContentFile from django.contrib.admin import widgets from django.conf import settings from photos.models import LuxImage, LuxGallery, LuxImageSize from .utils import resize_image from .readexif import readexif logger = logging.getLogger('photos.forms') class GalleryForm(forms.ModelForm): class Meta: fields = '__all__' widgets = { 'images': forms.SelectMultiple, } def __init__(self, *args, **kwargs): super(GalleryForm, self).__init__(*args, **kwargs) images = LuxImage.objects.all()[:100] items = [] for image in images: items.append( (image.pk, mark_safe('%sqq%sqq%s' % (image.title, image.get_image_by_size('tn'), image.pk))) ) self.fields['images'].choices = items self.fields['images'].allow_tags = True class UploadZipForm(forms.Form): """ Handles the uploading of a gallery of photos packed in a .zip file Creates Gallery object, adds photos with all metadata that's available """ zip_file = forms.FileField() title = forms.CharField(label=_('Gallery Title'), max_length=250) slug = forms.SlugField(label=_('Gallery Slug')) desc = forms.CharField(label=_('Gallery Caption'), widget=forms.Textarea, required=False) date = forms.SplitDateTimeField(label=_('Date'), widget=widgets.AdminSplitDateTime) is_public = forms.BooleanField(label=_('Is public'), initial=True, required=False, help_text=_('Show on site')) def clean_zip_file(self): """Open the zip file a first time, to check that it is a valid zip archive. We'll open it again in a moment, so we have some duplication, but let's focus on keeping the code easier to read! """ zip_file = self.cleaned_data['zip_file'] try: zip = zipfile.ZipFile(zip_file) except BadZipFile as e: raise forms.ValidationError(str(e)) bad_file = zip.testzip() if bad_file: zip.close() raise forms.ValidationError('"%s" in the .zip archive is corrupt.' % bad_file) zip.close() # Close file in all cases. return zip_file def clean_title(self): title = self.cleaned_data['title'] if title and LuxGallery.objects.filter(title=title).exists(): raise forms.ValidationError(_('A gallery with that title already exists.')) return title def clean(self): cleaned_data = super(UploadZipForm, self).clean() if not self['title'].errors: # If there's already an error in the title, no need to add another # error related to the same field. if not cleaned_data.get('title', None) and not cleaned_data['gallery']: raise forms.ValidationError( _('Select an existing gallery, or enter a title for a new gallery.')) return cleaned_data def save(self, request=None, zip_file=None): if not zip_file: zip_file = self.cleaned_data['zip_file'] gallery, created = LuxGallery.objects.get_or_create( title=self.cleaned_data['title'], description=self.cleaned_data['desc'], slug=self.cleaned_data['slug'], pub_date=self.cleaned_data['date'], is_public=self.cleaned_data['is_public'] ) zipper = zipfile.ZipFile(zip_file) count = 1 for filename in sorted(zipper.namelist()): f, file_extension = os.path.splitext(filename) logger.debug('Reading file "{0}".'.format(filename)) if filename.startswith('__') or filename.startswith('.'): logger.debug('Ignoring file "{0}".'.format(filename)) continue if os.path.dirname(filename): logger.warning('Ignoring file "{0}" as it is in a subfolder; all images should be in the top ' 'folder of the zip.'.format(filename)) if request: messages.warning(request, _('Ignoring file "{filename}" as it is in a subfolder').format(filename=filename), fail_silently=True) continue data = zipper.read(filename) if not len(data): logger.debug('File "{0}" is empty.'.format(filename)) continue fn, file_extension = os.path.splitext(filename) if file_extension != ".mp4": # Basic check that we have a valid image. try: file = BytesIO(data) opened = Image.open(file) opened.verify() except Exception: # Pillow (or PIL) doesn't recognize it as an image. # If a "bad" file is found we just skip it. # But we do flag this both in the logs and to the user. logger.error('Could not process file "{0}" in the .zip archive.'.format(filename)) if request: messages.warning(request, _('Could not process file "{0}" in the .zip archive.').format( filename), fail_silently=True) continue image = LuxImage( pub_date=datetime.datetime.now() ) contentfile = ContentFile(data) image.image.save(filename, contentfile) if file_extension != ".mp4": img = Image.open(image.image.path) if img.size[0] > img.size[1]: image.sizes.add(LuxImageSize.objects.get(width=2560)) image.sizes.add(LuxImageSize.objects.get(width=1170)) image.sizes.add(LuxImageSize.objects.get(width=720)) if img.size[1] > img.size[0]: image.sizes.add(LuxImageSize.objects.get(height=1600)) image.sizes.add(LuxImageSize.objects.get(height=800)) image.sizes.add(LuxImageSize.objects.get(height=460)) image.save() gallery.images.add(image) zipper.close() if request: messages.success(request, _('The photos have been uploaded'))