# -'- 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)