aboutsummaryrefslogtreecommitdiffstats
path: root/bn_django/photos
diff options
context:
space:
mode:
Diffstat (limited to 'bn_django/photos')
-rw-r--r--bn_django/photos/__init__.py1
-rw-r--r--bn_django/photos/models.py283
-rw-r--r--bn_django/photos/templates/photos/base.html1
-rw-r--r--bn_django/photos/templates/photos/gallery_detail.html61
-rw-r--r--bn_django/photos/templates/photos/gallery_list.html56
-rw-r--r--bn_django/photos/templates/photos/import_form.html59
-rw-r--r--bn_django/photos/templates/photos/photo_detail.html96
-rw-r--r--bn_django/photos/urls.py21
-rw-r--r--bn_django/photos/views.py156
9 files changed, 734 insertions, 0 deletions
diff --git a/bn_django/photos/__init__.py b/bn_django/photos/__init__.py
new file mode 100644
index 0000000..52af441
--- /dev/null
+++ b/bn_django/photos/__init__.py
@@ -0,0 +1 @@
+"""Strongly based on code from the stockphoto project (google it?)"""
diff --git a/bn_django/photos/models.py b/bn_django/photos/models.py
new file mode 100644
index 0000000..33b2e0a
--- /dev/null
+++ b/bn_django/photos/models.py
@@ -0,0 +1,283 @@
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+import django.contrib.auth.models as auth
+from django.conf import settings
+from django.dispatch import dispatcher
+from django.db.models import signals
+
+import os, os.path
+import Image
+
+# Handle settings here
+try:
+ STOCKPHOTO_BASE = settings.STOCKPHOTO_BASE.strip('/')
+except AttributeError:
+ STOCKPHOTO_BASE='photos'
+
+try:
+ STOCKPHOTO_URL = settings.STOCKPHOTO_URL
+except AttributeError:
+ STOCKPHOTO_URL='/photos'
+if STOCKPHOTO_URL[-1] == '/':
+ STOCKPHOTO_URL=STOCKPHOTO_URL[:-1]
+
+try:
+ ADMIN_URL = settings.ADMIN_URL
+except AttributeError:
+ ADMIN_URL='/admin'
+if ADMIN_URL[-1] == '/':
+ ADMIN_URL=ADMIN_URL[:-1]
+
+# Create your models here.
+class Gallery(models.Model):
+ title = models.CharField(_("title"), maxlength=80)
+ slug = models.SlugField(prepopulate_from=("title",))
+ date = models.DateField(_("publication date"), auto_now_add=True)
+ created = models.ForeignKey(auth.User,
+ verbose_name=_("gallery created by")
+ )
+ display_width = models.IntegerField(
+ _("width to display full images"),
+ default=640)
+ display_height = models.IntegerField(
+ _("height to display full images"),
+ default=480)
+ thumbnail_width = models.IntegerField(
+ _("width to display thumbnails"),
+ default=150)
+ thumbnail_height = models.IntegerField(
+ _("height to display thumbnails"),
+ default=100)
+
+ class Meta:
+ get_latest_by = 'date'
+
+ class Admin:
+ ordering = ['date']
+
+ def __str__(self):
+ return self.title
+ def get_absolute_url(self):
+ return "%s/%d/" % (STOCKPHOTO_URL, self.id)
+ def get_admin_url(self):
+ return "%s/photos/gallery/%d/" % (ADMIN_URL, self.id)
+ def was_published_today(self):
+ return self.date.date() == datetime.date.today()
+
+ def first(self):
+ try:
+ return self.photo_set.all()[0]
+ except IndexError:
+ return None
+ def update_thumbs(self):
+ for photo in self.photo_set.all():
+ photo.create_disp()
+ photo.create_thumb()
+
+class Photo(models.Model):
+ # import os, os.path, Image
+ image = models.ImageField(_("Photograph"),
+ upload_to= STOCKPHOTO_BASE + "/%Y/%m/%d/")
+ title = models.CharField(_("title"), maxlength=80)
+ desc = models.TextField(_("description"), blank=True)
+ gallery = models.ForeignKey(Gallery)
+ photographer = models.CharField(_("photographer"), maxlength=80,
+ blank=True)
+ date = models.DateField(_("date photographed"), blank=True, null=True)
+ extra = models.TextField(_("any extra information about the photo"),
+ blank=True)
+ class META:
+ get_latest_by = ['date']
+
+ class Admin:
+ ordering = ['date']
+
+ def __str__(self):
+ return self.title
+
+ def delete_thumbnails(self):
+ """Remove thumbnail and display-sized images when deleting.
+
+ This may fail if, for example, they don't exist, so it should
+ fail silently. It may not be a good idea to delete the
+ original, as the user may not understand that deleting it from
+ the gallery deletes it from the filesystem, so currently we
+ don't do that.
+
+ """
+ try:
+ os.unlink(self.thumbpath())
+ except (IOError, OSError):
+ pass
+ try:
+ os.unlink(self.disppath())
+ except (IOError, OSError):
+ pass
+ # Deleting the original might be a bad thing.
+ #os.unlink(self.fullpath())
+
+ def get_absolute_url(self):
+ return "%s/detail/%d/" % (STOCKPHOTO_URL, self.id)
+
+ def get_admin_url(self):
+ return "%s/photos/photos/%d/" % (ADMIN_URL, self.id)
+
+ def thumbpath(self):
+ """Path to the thumbnail
+ """
+ photobase = self.image[len(STOCKPHOTO_BASE)+1:]
+ return os.path.join( settings.MEDIA_ROOT, STOCKPHOTO_BASE,
+ "cache", "thumbs", photobase)
+
+ def thumburl(self):
+ """URL to the thumbnail
+ """
+ photobase = self.image[len(STOCKPHOTO_BASE)+1:]
+ if settings.MEDIA_URL.endswith('/'):
+ return settings.MEDIA_URL + STOCKPHOTO_BASE + \
+ "/cache/thumbs/" + photobase
+ return settings.MEDIA_URL + '/' + STOCKPHOTO_BASE + \
+ "/cache/thumbs/" + photobase
+
+
+ def disppath(self):
+ photobase = self.image[len(STOCKPHOTO_BASE)+1:]
+ return os.path.join( settings.MEDIA_ROOT, STOCKPHOTO_BASE,
+ "cache", photobase)
+
+ def dispurl(self):
+ photobase = self.image[len(STOCKPHOTO_BASE)+1:]
+ if settings.MEDIA_URL.endswith('/'):
+ return settings.MEDIA_URL + STOCKPHOTO_BASE + "/cache/" \
+ + photobase
+ return settings.MEDIA_URL + '/' + STOCKPHOTO_BASE + \
+ "/cache/" + photobase
+
+ def fullpath(self):
+ if self.image.startswith(os.path.sep):
+ return self.image
+ return os.path.join(settings.MEDIA_ROOT, self.image)
+
+ def fullurl(self):
+ if self.image.startswith(os.path.sep):
+ # Shouldn't happen anymore
+ return (settings.MEDIA_URL +
+ self.image[len(settings.MEDIA_ROOT):])
+ else:
+ if settings.MEDIA_URL.endswith('/'):
+ return settings.MEDIA_URL + self.image
+ return settings.MEDIA_URL + '/' + self.image
+
+
+ def next(self):
+ '''Return id of 'next' photo in the same gallery or None if at
+ the end.'''
+ # we could probably be more clever here by using the new nifty
+ # db access filters and queries, but for now, this is good enough
+ photo_list = [x for x in self.gallery.photo_set.all()]
+ ind = photo_list.index(self)
+ if (ind +1) == len(photo_list):
+ return None
+ else:
+ return photo_list[ind + 1]
+
+ def prev(self):
+ """Return id of 'previous' photo in the same gallery or None
+ if at the beginning
+ """
+ photo_list = [x for x in self.gallery.photo_set.all()]
+ ind = photo_list.index(self)
+ if ind == 0:
+ return False
+ else:
+ return photo_list[ind - 1]
+
+ def full_exists(self):
+ return os.path.exists( self.fullpath() )
+
+ def disp_exists(self):
+ return os.path.exists( self.disppath() )
+
+ def thumb_exists(self):
+ return os.path.exists( self.thumbpath() )
+
+ def create_disp(self):
+ im = Image.open( self.fullpath() )
+ format = im.format
+ # create the path for the display image
+ disp_path = self.disppath()
+ disp_dir = os.path.dirname(disp_path)
+ if not os.path.exists(disp_dir):
+ os.makedirs(disp_dir, 0775)
+
+ # Make a copy of the image, scaled, if needed.
+ maxwidth = self.gallery.display_width
+ maxheight = self.gallery.display_height
+ width, height = im.size
+ if (width > maxwidth) and width > height:
+ scale = float(maxwidth)/width
+ width = int(width * scale)
+ height = int(height * scale)
+ newim = im.resize( (width, height), Image.ANTIALIAS )
+ elif (height > maxheight) and height >= width:
+ scale = float(maxheight)/height
+ width = int(width * scale)
+ height = int(height * scale)
+ newim = im.resize( (width, height), Image.ANTIALIAS )
+ else:
+ newim = im
+ newim.save(disp_path, format)
+
+ def create_thumb(self):
+ im = Image.open( self.fullpath() )
+ format = im.format
+ # create the path for the thumbnail image
+ thumb_path = self.thumbpath()
+ thumb_dir = os.path.dirname(thumb_path)
+ if not os.path.exists(thumb_dir):
+ os.makedirs(thumb_dir, 0775)
+
+ # Make a copy of the image, scaled, if needed.
+ maxwidth = self.gallery.thumbnail_width
+ maxheight = self.gallery.thumbnail_height
+ width, height = im.size
+ if (width > maxwidth) and (width > height):
+ scale = float(maxwidth)/width
+ width = int(width * scale)
+ height = int(height * scale)
+ newim = im.resize( (width, height), Image.ANTIALIAS )
+ elif (height > maxheight):
+ scale = float(maxheight)/height
+ width = int(width * scale)
+ height = int(height * scale)
+ newim = im.resize( (width, height), Image.ANTIALIAS )
+ else:
+ newim = im
+ newim.save(thumb_path, format)
+
+ def build_display_images(self):
+ """Make thumbnail and display-sized images after saving.
+
+ For some reason, this may fail on a first pass (self.image may
+ be empty when this is called), but if we just let it fail
+ silently, it will apparently get called again and succeed.
+ """
+ if self.image:
+ if not self.thumb_exists():
+ self.create_thumb()
+ if not self.disp_exists():
+ self.create_disp()
+
+def build_display_images(sender, instance, signal, *args, **kwargs):
+ """Simple hook for save-after trigger
+ """
+ instance.build_display_images()
+def delete_thumbnails(sender, instance, signal, *args, **kwargs):
+ """Simple hook for pre-delete trigger.
+ """
+ instance.delete_thumbnails()
+
+dispatcher.connect(build_display_images, signal=signals.post_save,
+ sender=Photo)
+dispatcher.connect(delete_thumbnails, signal=signals.pre_delete,
+ sender=Photo)
diff --git a/bn_django/photos/templates/photos/base.html b/bn_django/photos/templates/photos/base.html
new file mode 100644
index 0000000..94d9808
--- /dev/null
+++ b/bn_django/photos/templates/photos/base.html
@@ -0,0 +1 @@
+{% extends "base.html" %}
diff --git a/bn_django/photos/templates/photos/gallery_detail.html b/bn_django/photos/templates/photos/gallery_detail.html
new file mode 100644
index 0000000..f9d1bd6
--- /dev/null
+++ b/bn_django/photos/templates/photos/gallery_detail.html
@@ -0,0 +1,61 @@
+{% extends "photos/base.html" %}
+{# {% load markup %} #}
+
+{% block path %}
+ <a href="../">photos</a> &raquo;
+{% if object %}
+ <a href="../{{ object.id }}">{{ object.title }}</a>
+{% endif %}
+{% endblock %}
+
+{% block title %}
+{% if object %}
+Gallery: {{ object.title }}
+{% endif %}
+{% endblock %}
+
+{% block content %}
+{% if object %}
+<br />
+{% if object.photo_set.count %}
+
+{% for item in object.photo_set.all %}
+
+<span class="photo_thumb">
+ <a href="../detail/{{ item.id }}">
+ <img src="{{ item.thumburl }}"
+ alt="{{ item.title }}" />
+ <!--<a href="../detail/{{ item.id }}" class="caption">{{ item.title }}</a>
+</a> -->
+
+</span>
+{% endfor %}
+
+{% else %}
+<p>There are no photos in this gallery. If you just uploaded a batch
+of photos, try hitting your browser's reload button to see if they
+show up.</p>
+{% endif %}
+ &nbsp;
+<!--
+{% if not user.is_anonymous %}
+<p>
+ <a href="{{ object.get_admin_url }}">Edit this gallery.</a><br/>
+ <a href="{{ admin_url }}/photos/photo/add/">Add a photo to
+ this gallery.</a><br/>
+ <a href="{{photos_url}}/import/{{ object.id }}/">Add a bunch
+ of photos to this gallery.</a>
+</p>
+{% else %}
+<p>
+ <a href="/accounts/login/?next={{ request.path }}">
+ Login</a> to add or edit photos
+</p>
+{% endif %}
+-->
+
+{% else %}
+<p>This is not the gallery you are looking for.</p>
+{% endif %}
+
+{% endblock %}
diff --git a/bn_django/photos/templates/photos/gallery_list.html b/bn_django/photos/templates/photos/gallery_list.html
new file mode 100644
index 0000000..61225c4
--- /dev/null
+++ b/bn_django/photos/templates/photos/gallery_list.html
@@ -0,0 +1,56 @@
+{% extends "photos/base.html" %}
+{# {% load markup %} #}
+
+{% block path %}photos{% endblock %}
+{% block title %}Photo Galleries{% endblock %}
+
+{% block content %}
+{% if object_list %}
+<div id="thumbnail-box">
+ <div id="thumbnails">
+ {% for item in object_list %}
+ <div class="lefty">
+ &nbsp; &nbsp;&nbsp;
+ <a href="{{ item.id }}/">
+ {% if item.first %}
+ <img src="{{ item.first.thumburl }}"
+ alt="{{ item.first.title }}" />
+ {% else %}
+ No photo available
+ {% endif %}
+ </a>
+ </div>
+ <br /> <br /><a href="{{ item.id }}/"><h3> &nbsp;&nbsp;{{ item.title }}</h3></a>
+ <br /> <br /> <br /> <br />
+ {% endfor %}
+ </div>
+</div>
+{% else %}
+<p>No galleries have been set up yet.</p>
+{% endif %}
+
+
+{% if is_paginated %}
+
+{% if has_previous %}
+<a href="./?page={{ previous }}">&laquo; previous</a> |
+{% endif %}
+{% if has_next %}
+<a href="./?page={{ next }}">next &raquo;</a>
+{% endif %}
+{% endif %}
+
+<!--
+{% if not user.is_anonymous %}
+<p>
+ <a href="{{admin_url}}/photos/gallery/add/">Create a new gallery.</a>
+</p>
+{% else %}
+<p>
+ <a href="/accounts/login/?next={{ request.path }}">
+ Login</a> to create a new gallery.
+</p>
+{% endif %}
+-->
+
+{% endblock %}
diff --git a/bn_django/photos/templates/photos/import_form.html b/bn_django/photos/templates/photos/import_form.html
new file mode 100644
index 0000000..0d4f19e
--- /dev/null
+++ b/bn_django/photos/templates/photos/import_form.html
@@ -0,0 +1,59 @@
+{% extends "photos/base.html" %}
+{# {% load markup %} #}
+{% block title %}Import photos into a gallery
+{% if gallery%} ({{gallery.title}}){% endif %}{% endblock %}
+
+{% block content %}
+{% if gallery %}
+
+<form action="../../import/{{ gallery.id }}/" method="post"
+ enctype="multipart/form-data">
+ <div>
+ {%if form.zipfile.errors %}
+ <span style="color: red;">
+ {{ form.zipfile.errors|join:", " }}
+ </span><br/>
+ {% endif %}
+
+ <label class="fortextinput" for="id_zipfile">
+ ZIP archive to upload:
+ </label>
+ {{ form.zipfile }}<br/><br/>
+
+ {%if form.photographer.errors %}
+ <span style="color: red;">
+ {{ form.photographer.errors|join:", " }}
+ </span><br/>
+ {% endif %}
+
+ <label class="fortextinput" for="id_photographer">
+ Name of photographer:
+ </label>
+ {{ form.photographer }}<br/><br/>
+
+ {%if form.date.errors %}
+ <span style="color: red;">
+ {{ form.date.errors|join:", " }}
+ </span><br/>
+ {% endif %}
+
+ <label class="fortextinput" for="id_date">
+ Date photos were taken:
+ </label>
+ {{ form.date }}<br/><br/>
+ <input type="submit" value="Upload"/>
+ </div>
+</form>
+<p>
+ When you upload a batch of photos in a zipfile, it will give each of
+ them a title based on its filename, and assigns them all the same
+ photographer and date. That's probably not always what you want, so
+ you can change any of these settings on a per-photo basis
+ <em>after</em> you upload the images.
+</p>
+
+{% else %}
+<p>Oops! No gallery here!</p>
+{% endif %}
+
+{% endblock %}
diff --git a/bn_django/photos/templates/photos/photo_detail.html b/bn_django/photos/templates/photos/photo_detail.html
new file mode 100644
index 0000000..d9c2177
--- /dev/null
+++ b/bn_django/photos/templates/photos/photo_detail.html
@@ -0,0 +1,96 @@
+{% extends "photos/base.html" %}
+{# {% load markup %} #}
+
+{% block path %}
+ <a href="../..">photos</a> &raquo;
+ <a href="../../{{ object.gallery.id }}">{{ object.gallery.title }}</a>
+{% endblock %}
+
+{% block title %}
+{% if object %}
+{{ object.title }}
+{% endif %}
+{% endblock %}
+
+{% block content %}
+
+{% if object %}
+{% if object.next %}
+<div class="right_stuff">
+<div class="small-image-box">
+<br /> <br /> <br /> <br /> <br /> <br /> <br /> <br /> <br />
+ <a href="../{{ object.next.id }}/">
+ Next:<br/>
+ <img src="{{ object.next.thumburl}}" alt="{{ object.next.title }}"/>
+ </a> <!--
+ <a href="../{{ object.next.id }}/">
+ <span>{{ object.next.title }} &raquo;</span></a> -->
+</div></div>
+
+{% endif %}
+
+{% if object.prev %}
+<div class="right_stuff">
+<div class="small-image-box">
+ <a href="../{{ object.prev.id }}/">
+ Previous:<br />
+ <img src="{{ object.prev.thumburl}}" alt="{{ object.prev.title }}"/>
+ </a> <!--
+ <a href="../{{ object.prev.id }}/">
+ <span>&laquo; {{ object.prev.title }} </span></a> -->
+</div>
+</div>
+{% endif %}
+<center>
+<div id="centerize">
+ <a href="{{ object.fullurl }}">
+ <img src="{{ object.dispurl }}"
+ alt="{{ object.title }}" />
+ </a>
+ <div>
+ <p class="photodesc"> {{ object.desc }} </p>
+ {% if object.photographer %}
+ <p class="photographer">Photo by {{ object.photographer }}.</p>
+ {% endif %}
+ {% if object.date %}
+ <p class="photodate">Photo taken {{ object.date }}.</p>
+ {% endif %}
+ {% if object.extra %}
+ <h3 class="photoextra">More information</h3>
+ <p class="photoextra">{{ object.extra }}</p>
+ {% endif %}
+ </div>
+</div>
+</center>
+{% if object.prev %}
+ <a href="../{{ object.prev.id }}/" class="lefty">
+ &laquo; previous
+ </a>
+{% endif %}
+{% if object.next %}
+ <a href="../{{ object.next.id }}/" class="righty">
+ next &raquo;
+ </a>
+{% endif %}
+<br />
+
+<!--
+{% if not user.is_anonymous %}
+<p>
+ <a href="{{ admin_url }}/photos/photo/{{ object.id }}/">
+ Edit this image.
+ </a>
+</p>
+{%else %}
+<p>
+ <a href="/accounts/login/?next={{ request.path }}">
+ Login</a> to edit this image.
+</p>
+{% endif %}
+-->
+{% else %}
+<p>This is not the photo you are looking for.</p>
+{% endif %}
+
+
+{% endblock %}
diff --git a/bn_django/photos/urls.py b/bn_django/photos/urls.py
new file mode 100644
index 0000000..a413c3f
--- /dev/null
+++ b/bn_django/photos/urls.py
@@ -0,0 +1,21 @@
+from django.conf.urls.defaults import *
+from django.conf import settings
+
+from models import Gallery, Photo, ADMIN_URL, STOCKPHOTO_URL
+
+info_dict = { 'extra_context': { 'admin_url': ADMIN_URL,
+ 'stockphoto_url': STOCKPHOTO_URL} }
+
+urlpatterns = patterns('django.views.generic.list_detail',
+ (r'^$', 'object_list',
+ dict(info_dict, queryset=Gallery.objects.all(),
+ paginate_by= 10, allow_empty= True)),
+ (r'^(?P<object_id>\d+)/$', 'object_detail',
+ dict(info_dict, queryset=Gallery.objects.all())),
+ (r'^detail/(?P<object_id>\d+)/$', 'object_detail',
+ dict(info_dict, queryset=Photo.objects.all())),
+)
+urlpatterns += patterns('bn_django.photos.views',
+ (r'^import/(\d+)/$', 'import_photos'),
+ (r'^export/(\d+)/$', 'export'),
+)
diff --git a/bn_django/photos/views.py b/bn_django/photos/views.py
new file mode 100644
index 0000000..7cc5332
--- /dev/null
+++ b/bn_django/photos/views.py
@@ -0,0 +1,156 @@
+# Create your views here.
+
+# django imports
+from django.conf import settings
+from django import forms, http, template
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import get_object_or_404, render_to_response
+from django.http import HttpResponse
+
+# other imports
+import zipfile
+import os
+import stat
+import shutil
+from datetime import datetime
+from tempfile import NamedTemporaryFile, mkdtemp
+import Image
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+# Handling settings here
+try:
+ STOCKPHOTO_BASE = settings.STOCKPHOTO_BASE.strip('/')
+except AttributeError:
+ STOCKPHOTO_BASE = 'photos'
+
+# models
+from bn_django.photos.models import Gallery, Photo
+
+# views
+
+class ImportManipulator(forms.Manipulator):
+ def __init__(self):
+ self.fields = (
+ forms.FileUploadField(field_name="zipfile",
+ is_required=True,
+ validator_list=[self.valid_zipfile,]),
+ forms.TextField(field_name="photographer"),
+ forms.DateField(field_name="date"),
+ )
+ def valid_zipfile(self, field_data, all_data):
+ zip_file = StringIO(field_data['content'])
+ zip = zipfile.ZipFile(zip_file)
+ return not zip.testzip()
+
+
+
+@login_required
+def import_photos(request, thegallery):
+ """Import a batch of photographs uploaded by the user.
+
+ Import a batch of photographs uploaded by the user, all with
+ the same information for gallery, photographer and date. The
+ title will be set from the filename, and the description will be
+ blank. Self-respecting photographers will edit the fields for
+ each photograph; this is just a way to get a bunch of photographs
+ uploaded quickly.
+
+ The photographs should be wrapped up in a zip archive. The
+ archive will be unpacked (and flattened) in a temporary directory,
+ and all image files will be identified and imported into the
+ gallery. Other files in the archive will be silently ignored.
+
+ After importing the images, the view will display a page which may
+ contain the number of images imported, and a link to the gallery
+ into which the images were imported.
+ """
+ # Check if the gallery is valid
+ gallery = get_object_or_404(Gallery, pk=thegallery)
+ # And that the user has permission to add photos
+ if not request.user.has_perm('gallery.add_photo'):
+ return http.HttpResponseForbidden("No permission to add photos")
+
+ manipulator = ImportManipulator()
+ if request.POST:
+ new_data = request.POST.copy()
+ new_data.update(request.FILES)
+ errors = manipulator.get_validation_errors(new_data)
+ if not errors:
+ # So now everything is okay
+ f = StringIO(new_data['zipfile']['content']) # the zip"file"
+ zip = zipfile.ZipFile(f)
+ manipulator.do_html2python(new_data)
+ date = new_data['date']
+ if not date:
+ date = datetime.date(datetime.now())
+
+ destdir= os.path.join(settings.MEDIA_ROOT, STOCKPHOTO_BASE,
+ datetime.strftime(datetime.now(),
+ "%Y/%m/%d/"))
+ if not os.path.isdir(destdir):
+ os.makedirs(destdir, 0775)
+ for filename in zip.namelist():
+ photopath = os.path.join(destdir, os.path.basename(filename))
+ data = zip.read(filename)
+ file_data = StringIO(data)
+ try:
+ Image.open(file_data)
+ except:
+ # don't save and process non Image files
+ continue
+ photo = file(photopath, "wb")
+ photo.write(data)
+
+ # Create the object
+ if photopath.startswith(os.path.sep):
+ photopath = photopath[len(settings.MEDIA_ROOT):]
+ photo = Photo(image=photopath, date=date,
+ photographer=new_data['photographer'],
+ title = os.path.basename(filename),
+ gallery_id = thegallery)
+ # Save it -- the thumbnails etc. get created.
+ photo.save()
+
+ # And jump to the directory for this gallery
+ response = http.HttpResponseRedirect(gallery.get_absolute_url())
+ response['Pragma'] = 'no cache'
+ response['Cache-Control'] = 'no-cache'
+ return response
+ else:
+ errors = new_data = {}
+ form = forms.FormWrapper(manipulator, new_data, errors)
+ return render_to_response('photos/import_form.html',
+ dict(form=form, gallery=gallery))
+ # request,
+
+@login_required
+def export(request, thegallery):
+ """Export a gallery to a zip file and send it to the user.
+ """
+ # Check if the gallery is valid
+ gallery = get_object_or_404(Gallery, pk=thegallery)
+
+ # gather up the photos into a new directory
+ tmpdir = mkdtemp()
+ for photo in gallery.photo_set.all():
+ shutil.copy(photo.get_image_filename(),
+ tmpdir)
+ files = [ os.path.join(tmpdir, ff) for ff in os.listdir(tmpdir) ]
+ outfile = NamedTemporaryFile()
+ zf = zipfile.ZipFile(outfile, "w",
+ compression=zipfile.ZIP_DEFLATED)
+ for filename in files:
+ zf.write(filename, arcname=os.path.basename(filename))
+ zf.close()
+ outfile.flush()
+ outfile.seek(0)
+ shutil.rmtree(tmpdir)
+ response = HttpResponse(outfile)
+ response['Content-Type'] = "application/zip"
+ response['Content-Length'] = str(os.stat(outfile.name)[stat.ST_SIZE])
+ response['Content-Disposition'] = "attachment; filename=photos.zip"
+ return response
+