Anonymous avatar Anonymous committed 716f4c2

Comments (0)

Files changed (4)

+Copyright (c) 2007, Justin C. Driscoll
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+    3. Neither the name of django-photologue nor the names of its contributors may be used
+       to endorse or promote products derived from this software without
+       specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Empty file added.

+import os
+import zipfile
+import StringIO
+
+from datetime import datetime
+import Image
+
+from django.db import models
+from django.db.models import signals
+from django.conf import settings
+from django.utils.functional import curry
+from django.core.validators import ValidationError
+from django.core.urlresolvers import reverse
+from django.dispatch import dispatcher
+from django.template.defaultfilters import slugify
+
+
+# define our path roots
+PHOTOLOGUE_DIR = 'photologue'
+PHOTOLOGUE_URL = '/photologue'
+
+
+class Gallery(models.Model):
+    pub_date = models.DateTimeField("Date published", default=datetime.now)
+    title = models.CharField(maxlength=200)
+    slug = models.SlugField(prepopulate_from=('title',),
+                            help_text='A "Slug" is a unique URL-friendly title for an object.')
+    description = models.TextField()
+    photos = models.ManyToManyField('Photo', related_name='galleries')
+
+    class Meta:
+        ordering = ['-pub_date']
+        get_latest_by = 'pub_date'
+        verbose_name_plural = "galleries"
+
+    class Admin:
+        list_display = ('title', 'pub_date', 'photo_count')
+        list_filter = ['pub_date']
+        date_hierarchy = 'pub_date'
+
+    def __str__(self):
+        return self.title
+
+    def get_absolute_url(self):
+        args = self.pub_date.strftime("%Y/%b/%d").lower().split("/") + [self.slug]
+        return reverse('pr-gallery-detail', args=args)
+
+    def latest(self, limit=5):
+        return self.photos.all()[:limit]
+
+    def photo_count(self):
+        return self.photos.all().count()
+    photo_count.short_description = 'Count'
+
+
+class GalleryUpload(models.Model):
+    id = models.IntegerField(default=1, editable=False, primary_key=True)
+    zip_file = models.FileField('Images file (.zip)',
+                                upload_to=PRESSROOM_DIR+"/temp",
+                                help_text="Select a .zip file of images to upload into a new Gallery.")
+    title_prefix = models.CharField(maxlength=75,
+                                    help_text="Photos will be titled using this prefix.")
+    caption = models.TextField(help_text="Caption will be added to all photos.")
+    description = models.TextField(blank=True,
+                                   help_text="A description of this Gallery.")
+    photographer = models.CharField(maxlength=100, blank=True)
+    info = models.TextField(blank=True,
+                            help_text="Additional information about the photograph such as date taken, equipment used etc..")
+
+    class Admin:
+        pass
+
+    def save(self):
+        super(GalleryUpload, self).save()
+        self.process_zipfile()
+        super(GalleryUpload, self).delete()
+
+    def process_zipfile(self):
+        if os.path.isfile(self.get_zip_file_filename()):
+            # TODO: implement try-except here
+            zip = zipfile.ZipFile(self.get_zip_file_filename())
+            bad_file = zip.testzip()
+            if bad_file:
+                raise forms.ValidationError('"%s" in the .zip archive is corrupt.' % bad_file)
+            count = 0
+            gallery = Gallery.objects.create(title=self.title_prefix,
+                                             slug=slugify(self.title_prefix),
+                                             description=self.description)
+            for filename in zip.namelist():
+                data = zip.read(filename)
+                title = ' '.join([self.title_prefix, str(count)])
+                slug = slugify(title)
+                photo = Photo(title=title, slug=slug,
+                              caption=self.caption,
+                              photographer=self.photographer,
+                              info=self.info)
+                photo.save_image_file(filename, data)
+                gallery.photos.add(photo)
+                count = count + 1
+            zip.close()
+
+
+class Photo(models.Model):
+    image = models.ImageField("Photograph", upload_to=PRESSROOM_DIR+"/photos/%Y/%b/%d")
+    pub_date = models.DateTimeField("Date published", default=datetime.now)
+    title = models.CharField(maxlength=80)
+    slug = models.SlugField(prepopulate_from=('title',),
+                            help_text='A "Slug" is a unique URL-friendly title for an object.')
+    caption = models.TextField()
+    photographer = models.CharField(maxlength=100)
+    info = models.TextField(blank=True,
+                            help_text="Additional information about the photograph such as date taken, equipment used etc..")
+
+    class Meta:
+        ordering = ['-pub_date']
+        get_latest_by = 'pub_date'
+
+    class Admin:
+        list_display = ('title', 'photographer', 'pub_date', 'admin_thumbnail')
+        list_filter = ['pub_date']
+        list_per_page = 10
+
+    def admin_thumbnail(self):
+        if 'thumbnail' in [photosize.name for photosize in PhotoSize.objects.all()]:
+            return '<a href="%s"><img src="%s"></a>' % \
+                            (self.get_absolute_url(), self._get_SIZE_url('thumbnail'))
+        else:
+            return 'A "thumbnail" photo size has not been defined.'
+    admin_thumbnail.short_description = 'Thumbnail'
+    admin_thumbnail.allow_tags = True
+
+    def __str__(self):
+        return self.title
+
+    def get_absolute_url(self):
+        args = self.pub_date.strftime("%Y/%b/%d").lower().split("/") + [self.slug]
+        return reverse('pr-photo-detail', args=args)
+
+
+    def cache_path(self):
+        return os.path.join(os.path.dirname(self.get_image_filename()), "cache")
+
+    def image_url(self):
+        return '/'.join([settings.MEDIA_URL, self.image.replace('\\', '/')])
+
+    def cache_url(self):
+        return '/'.join([os.path.dirname(self.image_url()), "cache"])
+
+    def image_filename(self):
+        return os.path.basename(self.image)
+
+    def _get_filename_for_size(self, size):
+        base, ext = os.path.splitext(self.image_filename())
+        return ''.join([base, '_', size, ext])
+
+    def _get_SIZE_size(self, size):
+        return PhotoSize.objects.get(name__exact=size)
+
+    def _get_SIZE_url(self, size):
+        try:
+            photosize = PhotoSize.objects.get(name=size)
+        except PhotoSize.DoesNotExist:
+            return ''
+        if not self.size_exists(photosize):
+            self.create_size(photosize)
+        return '/'.join([self.cache_url(), self._get_filename_for_size(photosize.name)])
+
+    def _get_SIZE_path(self, size):
+        try:
+            photosize = PhotoSize.objects.get(name=size)
+        except PhotoSize.DoesNotExist:
+            return ''
+        return os.path.join(self.cache_path(), self._get_filename_for_size(photosize.name))
+
+    def add_accessor_methods(self, *args, **kwargs):
+        for photosize in PhotoSize.objects.all():
+            setattr(self, 'get_%s_size' % photosize.name, curry(self._get_SIZE_size, size=photosize.name))
+            setattr(self, 'get_%s_url' % photosize.name, curry(self._get_SIZE_url, size=photosize.name))
+            setattr(self, 'get_%s_path' % photosize.name, curry(self._get_SIZE_path, size=photosize.name))
+
+    def size_exists(self, photosize):
+        func = getattr(self, "get_%s_path" % photosize.name, None)
+        if func is not None:
+            if os.path.isfile(func()):
+                return True
+        return False
+
+    def create_size(self, photosize):
+        if self.size_exists(photosize):
+            return
+        if not os.path.isdir(self.cache_path()):
+            os.makedirs(self.cache_path())
+        try:
+            im = Image.open(self.get_image_filename())
+        except IOError:
+            return
+        cur_width, cur_height = im.size
+        new_width, new_height = photosize.size()
+        if photosize.crop:
+            ratio = float(new_width)/cur_width if cur_width < cur_height else \
+                    float(new_height)/cur_height
+            x = (cur_width * ratio)
+            y = (cur_height * ratio)
+            x_diff = abs((new_width - x) / 2)
+            y_diff = abs((new_height - y) / 2)
+            box = (x_diff, y_diff, (x-x_diff), (y-y_diff))
+            resized = im.resize((x, y), Image.ANTIALIAS).crop(box)
+        else:
+            if not new_width == 0 and not new_height == 0:
+                ratio = float(new_width)/cur_width if cur_width > cur_height else \
+                        float(new_height)/cur_height
+            else:
+                if new_width == 0:
+                    ratio = float(new_height)/cur_height
+                else:
+                    ratio = float(new_width)/cur_width
+            resized = im.resize((int(cur_width*ratio), int(cur_height*ratio)), Image.ANTIALIAS)
+        resized.save(getattr(self, "get_%s_path" % photosize.name)())
+
+    def remove_size(self, photosize, remove_dirs=True):
+        if not self.size_exists(photosize):
+            return
+        filename = getattr(self, "get_%s_path" % photosize.name)()
+        if os.path.isfile(filename):
+            os.remove(filename)
+        if remove_dirs:
+            try:
+                os.removedirs(self.cache_path())
+            except:
+                pass
+
+    def remove_set(self):
+        for photosize in PhotoSize.objects.all():
+            self.remove_size(photosize, False)
+            try:
+                os.removedirs(self.cache_path())
+            except:
+                pass
+
+    def save(self):
+        super(Photo, self).save()
+
+    def delete(self):
+        super(Photo, self).delete()
+        self.remove_set()
+
+
+class PhotoSize(models.Model):
+    name = models.CharField(maxlength=20, unique=True, help_text='Examples: "thumbnail", "display", "small"')
+    width = models.PositiveIntegerField(default=0,
+                                        help_text='Leave to size the image to the set height')
+    height = models.PositiveIntegerField(default=0,
+                                         help_text='Leave to size the image to the set width')
+    crop = models.BooleanField("Crop photo to fit?", default=False,
+                               help_text="If selected the image will be scaled \
+                                         and cropped to fit the supplied dimensions.")
+
+    class Meta:
+        ordering = ['width', 'height']
+
+    class Admin:
+        list_display = ('name', 'width', 'height', 'crop')
+
+    def __str__(self):
+        return self.name
+
+    def save(self):
+        for photo in Photo.objects.all():
+            photo.remove_size(self)
+        super(PhotoSize, self).save()
+
+    def delete(self):
+        for photo in Photo.objects.all():
+            photo.remove_size(self)
+        super(PhotoSize, self).delete()
+
+    def size(self):
+        return (self.width, self.height)
+
+
+# Just a little instrospection...
+# The following MUST come after the model definition.
+def add_methods(sender, instance, signal, *args, **kwargs):
+    """ Adds methods to access sized images (urls, paths)
+
+    after the photo models __init__ function completes,
+    this method calls "add_accessor_methods" on each instance.
+    """
+    instance.add_accessor_methods()
+
+# connect the add_accessor_methods function to the post_init signal
+dispatcher.connect(add_methods, signal=signals.post_init, sender=Photo)
+from django.conf.urls.defaults import *
+from django_apps.photologue.models import *
+
+
+# galleries
+gallery_args = {'date_field': 'pub_date', 'allow_empty': True, 'queryset': Gallery.objects.all()}
+urlpatterns = patterns('django.views.generic.date_based',
+    url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'date_field': 'pub_date', 'slug_field': 'slug', 'queryset': Gallery.objects.all()}, name='pl-gallery-detail'),
+    url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', gallery_args, name='pl-gallery-archive-day'),
+    url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', gallery_args, name='pl-gallery-archive-month'),
+    url(r'^gallery/(?P<year>\d{4})/$', 'archive_year', gallery_args, name='pl-gallery-archive-year'),
+    url(r'^gallery/?$', 'archive_index', gallery_args, name='pl-gallery-archive'),
+)
+urlpatterns += patterns('django.views.generic.list_detail',
+    url(r'^gallery/page/(?P<page>[0-9]+)/$', 'object_list', {'queryset': Gallery.objects.all(), 'allow_empty': True, 'paginate_by': 5}, name='pl-gallery-list'),
+)
+
+# photographs
+photo_args = {'date_field': 'pub_date', 'allow_empty': True, 'queryset': Photo.objects.all()}
+urlpatterns += patterns('django.views.generic.date_based',
+    url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'date_field': 'pub_date', 'slug_field': 'slug', 'queryset': Photo.objects.all()}, name='pl-photo-detail'),
+    url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', photo_args, name='pl-photo-archive-day'),
+    url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', photo_args, name='pl-photo-archive-month'),
+    url(r'^photo/(?P<year>\d{4})/$', 'archive_year', photo_args, name='pl-photo-archive-year'),
+    url(r'^photo/$', 'archive_index', photo_args, name='pl-photo-archive'),
+)
+urlpatterns += patterns('django.views.generic.list_detail',
+    url(r'^photo/page/(?P<page>[0-9]+)/$', 'object_list', {'queryset': Photo.objects.all(), 'allow_empty': True, 'paginate_by': 20}, name='pl-photo-list'),
+)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.