summaryrefslogtreecommitdiff
path: root/app/photos/forms.py
diff options
context:
space:
mode:
authorluxagraf <sng@luxagraf.net>2015-10-25 08:44:23 -0400
committerluxagraf <sng@luxagraf.net>2015-10-25 08:44:23 -0400
commitb1b467ca78585d1b875d41e8374164bbb4952ad6 (patch)
treedf5ba563161164a8876ae9c5505c5eb2378454d8 /app/photos/forms.py
parent280ccc3920b9c761c3911cf7e4d5c40555c37ce9 (diff)
redid photo app to support uploading a zip folder of images and reading photo info from exif metadata
Diffstat (limited to 'app/photos/forms.py')
-rw-r--r--app/photos/forms.py174
1 files changed, 174 insertions, 0 deletions
diff --git a/app/photos/forms.py b/app/photos/forms.py
new file mode 100644
index 0000000..eae0e7e
--- /dev/null
+++ b/app/photos/forms.py
@@ -0,0 +1,174 @@
+import zipfile
+from zipfile import BadZipFile
+import logging
+import datetime
+import time
+import os
+from io import BytesIO
+from fractions import Fraction
+try:
+ import Image
+except ImportError:
+ from PIL import Image
+
+
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+from django.contrib import messages
+from django.contrib.sites.models import Site
+from django.conf import settings
+from django.utils.encoding import force_text
+from django.template.defaultfilters import slugify
+from django.core.files.base import ContentFile
+from django.contrib.admin import widgets
+from django.contrib.gis.geos import Point
+
+import exiftool
+from photos.models import LuxImage, Gallery
+from locations.models import Location
+
+logger = logging.getLogger('photos.forms')
+
+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 Gallery.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 = Gallery.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']
+ )
+ 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
+
+ # 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)
+ image.save()
+ gallery.image.add(image)
+
+ with exiftool.ExifTool() as et:
+ meta = et.get_metadata(image.image.path)
+ et.terminate()
+ image.exif_raw = meta
+ try:
+ image.title = meta["EXIF:ImageDescription"]
+ except:
+ pass
+ try:
+ image.caption = meta["EXIF:UserComment"]
+ except:
+ pass
+ try:
+ image.exif_lens = meta["MakerNotes:LensType"]
+ except:
+ pass
+ try:
+ image.point = Point(meta["XMP:GPSLongitude"], meta["XMP:GPSLatitude"], srid=4326)
+ try:
+ image.location = Location.objects.filter(geometry__contains=image.point).get()
+ except Location.DoesNotExist:
+ pass
+ except KeyError:
+ pass
+ image.exif_aperture = meta["EXIF:FNumber"]
+ image.exif_make = meta["EXIF:Make"]
+ image.exif_model = meta["EXIF:Model"]
+ image.exif_exposure = str(Fraction(float(meta["EXIF:ExposureTime"])).limit_denominator())
+ image.exif_iso = meta["EXIF:ISO"]
+ image.exif_focal_length = meta["EXIF:FocalLength"]
+ fmt_date = time.strptime(meta["EXIF:DateTimeOriginal"], "%Y:%m:%d %H:%M:%S")
+ image.exif_date = time.strftime("%Y-%m-%d %H:%M:%S", fmt_date)
+ image.height = meta["File:ImageHeight"]
+ image.width = meta["File:ImageWidth"]
+ image.save()
+ count += 1
+
+ zipper.close()
+
+ if request:
+ messages.success(request, _('The photos have been uploaded'))