172 lines
5.5 KiB
Python
172 lines
5.5 KiB
Python
# -'- Encoding: utf-8 -*-
|
|
|
|
import os
|
|
|
|
from django.conf import settings
|
|
from django.core.urlresolvers import reverse
|
|
from django.db import models
|
|
from django.utils.translation import ugettext as _
|
|
import pyexiv2
|
|
|
|
from utils import OverwriteStorage
|
|
from kasu import image_models
|
|
|
|
|
|
def get_upload_path(instance, filename):
|
|
"""
|
|
Generates the desired file path and filename for an uploaded Image.
|
|
With this function Django can save the uploaded images to subfolders that
|
|
also have a meaning for humans.
|
|
|
|
@param instance: an Django Object for which the Image has been uploaded.
|
|
@type instance: a instace of an models.Model sub-class.
|
|
@param filename: The filename of the uploaded image.
|
|
@type filename: String
|
|
"""
|
|
extension = filename[filename.rfind('.') + 1:]
|
|
if isinstance(instance, Photo):
|
|
return "events/%s/%s" % (instance.event.id, filename)
|
|
|
|
|
|
class PhotoManager(models.Manager):
|
|
def get_random(self, startpage=True):
|
|
if startpage:
|
|
queryset = self.filter(on_startpage=True)
|
|
else:
|
|
queryset = self.all().order_by('?')[0]
|
|
try:
|
|
return queryset.order_by('?')[0]
|
|
except IndexError:
|
|
return Photo()
|
|
|
|
|
|
class Photo(image_models.ImageModel):
|
|
name = models.CharField(_("Name"), max_length=100, blank=True)
|
|
image = models.ImageField(_("Image"), upload_to=get_upload_path,
|
|
storage=OverwriteStorage())
|
|
anchor_horizontal = models.FloatField(
|
|
_('horizontal Anchorpoint'),
|
|
choices=image_models.CHOICES_HORIZONTAL,
|
|
blank=True, null=True,
|
|
help_text='Der Ankerpunkt ist der interessante Teil des Bildes,\
|
|
welcher nie abgeschnitten werden darf'
|
|
)
|
|
anchor_vertical = models.FloatField(
|
|
_('vertical Anchorpoint'),
|
|
choices=image_models.CHOICES_VERTICAL,
|
|
blank=True, null=True,
|
|
help_text='Wenn kein Ankerpunkt von Hand (horizontal und vertikal)\
|
|
festgelegt wird, versucht die Software diesen selbst zu erraten.'
|
|
)
|
|
|
|
event = models.ForeignKey('events.Event')
|
|
description = models.TextField(
|
|
_("Description"),
|
|
max_length=300,
|
|
blank=True
|
|
)
|
|
photographer = models.ForeignKey(settings.AUTH_USER_MODEL)
|
|
on_startpage = models.BooleanField(
|
|
_("Startpage"),
|
|
default=False,
|
|
help_text=_('Display this Photo on the Startpage Teaser')
|
|
)
|
|
created_date = models.DateTimeField(_("Published on"))
|
|
views = models.PositiveIntegerField(
|
|
_("Number of views"),
|
|
editable=False,
|
|
default=0
|
|
)
|
|
objects = PhotoManager()
|
|
metadata = None
|
|
orientation = 1
|
|
|
|
class Meta:
|
|
get_latest_by = "created_date"
|
|
ordering = ["created_date"]
|
|
db_table = 'events_photo'
|
|
verbose_name = _('Event Image')
|
|
verbose_name_plural = _('Event Images')
|
|
|
|
|
|
def __unicode__(self):
|
|
return os.path.basename(self.image.name)
|
|
|
|
def read_metadata(self):
|
|
image_path = os.path.join(settings.MEDIA_ROOT, self.image.name)
|
|
self.metadata = pyexiv2.ImageMetadata(image_path)
|
|
self.metadata.read()
|
|
try:
|
|
self.orientation = self.metadata['Exif.Image.Orientation'].value
|
|
except:
|
|
self.orientation = 1
|
|
|
|
def save_metadata(self):
|
|
if not self.metadata:
|
|
self.read_metadata()
|
|
self.metadata['Exif.Image.DateTime'] = self.created_date
|
|
self.metadata['Exif.Image.ImageDescription'] = self.description
|
|
self.metadata['Exif.Image.Artist'] = self.photographer.username
|
|
self.metadata['Exif.Image.Orientation'] = self.orientation or 1
|
|
self.metadata.write()
|
|
|
|
def rotate(self, rotate):
|
|
"""
|
|
Sets an the Exif tag in an image to set the right direction.
|
|
This provides lossless image rotation.
|
|
@param rotate: 'clockwise' or 'counter-clockwise' the direction in
|
|
which we should rotate the image in 90° steps.
|
|
"""
|
|
if not self.metadata:
|
|
self.read_metadata()
|
|
if rotate == 'clockwise':
|
|
if self.orientation == 1:
|
|
self.orientation = 6
|
|
elif self.orientation == 6:
|
|
self.orientation = 3
|
|
elif self.orientation == 3:
|
|
self.orientation = 8
|
|
else:
|
|
self.orientation = 1
|
|
elif rotate == 'counter-clockwise':
|
|
if self.orientation == 1:
|
|
self.orientation = 8
|
|
elif self.orientation == 8:
|
|
self.orientation = 3
|
|
elif self.orientation == 3:
|
|
self.orientation = 6
|
|
else:
|
|
self.orientation = 1
|
|
self.save()
|
|
|
|
def get_absolute_url(self):
|
|
return reverse(
|
|
'event-photo',
|
|
kwargs={'event': self.event.id, 'pk': self.id}
|
|
)
|
|
|
|
@property
|
|
def next_photo(self):
|
|
return self.get_next_by_created_date(event=self.event)
|
|
|
|
@property
|
|
def previous_photo(self):
|
|
return self.get_previous_by_created_date(event=self.event)
|
|
|
|
def save(self, **kwargs):
|
|
"""
|
|
Triggers to save related Event to save. This should force an update for
|
|
the denormalized Photo count.
|
|
"""
|
|
super(Photo, self).save()
|
|
self.save_metadata()
|
|
|
|
|
|
def update_event(sender, instance=None, created=False, raw=False, **kwargs):
|
|
image_models.regenerate_image_cache(sender, instance=instance)
|
|
instance.event.save()
|
|
|
|
|
|
models.signals.post_save.connect(update_event, sender=Photo)
|
|
models.signals.post_delete.connect(update_event, sender=Photo)
|