summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/photos/TODO5
-rw-r--r--app/photos/admin.py83
-rw-r--r--app/photos/forms.py174
-rw-r--r--app/photos/models.py62
-rw-r--r--app/photos/views.py1
5 files changed, 323 insertions, 2 deletions
diff --git a/app/photos/TODO b/app/photos/TODO
new file mode 100644
index 0000000..4584c63
--- /dev/null
+++ b/app/photos/TODO
@@ -0,0 +1,5 @@
+Okay we're uploading/creating galleries/adding images
+
+Then resize all images for responsive gallery.
+
+Then play with gallery templates and add a template selecter to form.
diff --git a/app/photos/admin.py b/app/photos/admin.py
index 3c70c35..2f71fcc 100644
--- a/app/photos/admin.py
+++ b/app/photos/admin.py
@@ -1,8 +1,60 @@
from django.contrib import admin
from django.contrib.gis.admin import OSMGeoAdmin
-from photos.models import Photo, PhotoGallery
+from django.conf.urls import patterns
+from django.conf.urls import url
+from django.utils.translation import ungettext, ugettext_lazy as _
+from photos.models import Photo, PhotoGallery, LuxImage, Gallery
+from django.shortcuts import render
+from django.contrib.admin import helpers
+from django.http import HttpResponseRedirect
+from .forms import UploadZipForm
+
+class LuxImageAdmin(OSMGeoAdmin):
+ list_display = ('pk','admin_thumbnail', 'pub_date',)
+ list_filter = ('pub_date',)
+ search_fields = ['title', 'caption']
+
+admin.site.register(LuxImage, LuxImageAdmin)
+
+class GalleryAdmin(OSMGeoAdmin):
+ list_display = ('title', 'location', 'pub_date')
+ list_filter = ('location',)
+
+ def get_urls(self):
+ urls = super(GalleryAdmin, self).get_urls()
+ custom_urls = patterns('',
+ url(r'^upload_zip/$',
+ self.admin_site.admin_view(self.upload_zip),
+ name='upload_zip')
+ )
+ return custom_urls + urls
+
+ def upload_zip(self, request):
+ context = {
+ 'title': _('Upload a zip archive of photos'),
+ 'app_label': self.model._meta.app_label,
+ 'opts': self.model._meta,
+ 'has_change_permission': self.has_change_permission(request)
+ }
+
+ # Handle form request
+ if request.method == 'POST':
+ form = UploadZipForm(request.POST, request.FILES)
+ if form.is_valid():
+ form.save(request=request)
+ return HttpResponseRedirect('..')
+ else:
+ form = UploadZipForm()
+ context['form'] = form
+ context['adminform'] = helpers.AdminForm(form,
+ list([(None, {'fields': form.base_fields})]),
+ {})
+ return render(request, 'admin/upload_zip.html', context)
+
+admin.site.register(Gallery, GalleryAdmin)
+
class PhotoAdmin(OSMGeoAdmin):
list_display = ('title', 'admin_thumbnail', 'flickr_id', 'pub_date',)
list_filter = ('pub_date',)
@@ -73,6 +125,35 @@ class PhotoGalleryAdmin(OSMGeoAdmin):
)
}),
)
+ def get_urls(self):
+ urls = super(PhotoGalleryAdmin, self).get_urls()
+ custom_urls = patterns('',
+ url(r'^upload_zip/$',
+ self.admin_site.admin_view(self.upload_zip),
+ name='upload_zip')
+ )
+ return custom_urls + urls
+
+ def upload_zip(self, request):
+ context = {
+ 'title': _('Upload a zip archive of photos'),
+ 'app_label': self.model._meta.app_label,
+ 'opts': self.model._meta,
+ 'has_change_permission': self.has_change_permission(request)
+ }
+ # Handle form request
+ if request.method == 'POST':
+ form = UploadZipForm(request.POST, request.FILES)
+ if form.is_valid():
+ form.save(request=request)
+ return HttpResponseRedirect('..')
+ else:
+ form = UploadZipForm()
+ context['form'] = form
+ context['adminform'] = helpers.AdminForm(form,
+ list([(None, {'fields': form.base_fields})]),
+ {})
+ return render(request, 'admin/upload_zip.html', context)
admin.site.register(PhotoGallery, PhotoGalleryAdmin)
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'))
diff --git a/app/photos/models.py b/app/photos/models.py
index dce655e..d3024d9 100644
--- a/app/photos/models.py
+++ b/app/photos/models.py
@@ -10,6 +10,44 @@ from django.conf import settings
from taggit.managers import TaggableManager
from locations.models import Location, Region
+def get_upload_path(self, filename):
+ return "images/galleries/originals/%s/%s" % (datetime.datetime.today().strftime("%Y"), filename)
+
+class LuxImage(models.Model):
+ image = models.FileField(blank=True, null=True, upload_to=get_upload_path)
+ title = models.CharField(null=True,blank=True, max_length=300)
+ caption = models.TextField(blank=True, null=True)
+ pub_date = models.DateTimeField()
+ exif_raw = models.TextField(blank=True, null=True)
+ exif_aperture = models.CharField(max_length=50, blank=True, null=True)
+ exif_make = models.CharField(max_length=50, blank=True, null=True)
+ exif_model = models.CharField(max_length=50, blank=True, null=True)
+ exif_exposure = models.CharField(max_length=50, blank=True, null=True)
+ exif_iso = models.CharField(max_length=50, blank=True, null=True)
+ exif_focal_length = models.CharField(max_length=50, blank=True, null=True)
+ exif_lens = models.CharField(max_length=50, blank=True, null=True)
+ exif_date = models.DateTimeField(blank=True, null=True)
+ height = models.CharField(max_length=6, blank=True, null=True)
+ width = models.CharField(max_length=6, blank=True, null=True)
+ point = models.PointField(null=True, blank=True)
+ location = models.ForeignKey(Location, null=True, blank=True)
+ is_public = models.BooleanField(default=True)
+ is_video = models.BooleanField(default=False)
+ flickr_id = models.CharField(null=True,blank=True, max_length=80)
+
+ class Meta:
+ ordering = ('-pub_date', 'id')
+ verbose_name_plural = 'Images'
+ get_latest_by = 'pub_date'
+
+ def __str__(self):
+ return "%s" % self.pk
+
+ def admin_thumbnail(self):
+ return force_text('<a href="%s"><img src="%s"></a>' %
+ (self.image.url, self.image.url))
+ admin_thumbnail.allow_tags = True
+ admin_thumbnail.short_description = 'Thumbnail'
class Photo(models.Model):
description = models.TextField(blank=True, null=True)
@@ -171,6 +209,30 @@ class Photo(models.Model):
def save(self, *args, **kwargs):
super(Photo, self).save()
+class Gallery(models.Model):
+ title = models.CharField(blank=True, max_length=300)
+ description = models.TextField(blank=True, null=True)
+ slug = models.CharField(blank=True, max_length=300)
+ thumb = models.CharField(blank=True, max_length=300)
+ image = models.ManyToManyField(LuxImage)
+ pub_date = models.DateTimeField(null=True)
+ point = models.PointField(null=True, blank=True)
+ location = models.ForeignKey(Location, null=True, blank=True)
+
+ class Meta:
+ ordering = ('-pub_date', 'id')
+ verbose_name_plural = 'Galleries'
+ get_latest_by = 'pub_date'
+
+ def __str__(self):
+ return self.title
+
+ def get_main_image(self):
+ return "%sgallery_thumbs/%s.jpg" % (settings.IMAGES_URL, self.id)
+
+ def get_absolute_url(self):
+ return "/photos/galleries/%s/" % (self.slug)
+
class PhotoGallery(models.Model):
set_id = models.CharField(blank=True, max_length=300)
diff --git a/app/photos/views.py b/app/photos/views.py
index 567f329..0def0e3 100644
--- a/app/photos/views.py
+++ b/app/photos/views.py
@@ -6,7 +6,6 @@ from django.core import serializers
from photos.models import Photo, PhotoGallery
from locations.models import Country, Region
-
def gallery_list(request, page):
request.page_url = '/photos/%d/'
request.page = int(page)