# -'- Encoding: utf-8 -*- import os from django.conf import settings from django.core.urlresolvers import reverse from django.db import models from django.template.defaultfilters import slugify from django.utils.timezone import now from django.utils.translation import ugettext as _ from imagekit import ImageSpec import imagekit from imagekit.models import ImageSpecField from pilkit import processors import pyexiv2 from utils import COUNTRIES, OverwriteStorage CHOICES_HORIZONTAL = ( (0.00000001, _('left')), (0.5, _('center')), (1, _('right')) ) CHOICES_VERTICAL = ( (0.00000001, _('top')), (0.5, _('middle')), (1, _('bottom')) ) 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, Event): if instance.id: return "events/%s.%s" % (instance.id, extension) else: return "events/%s.%s" % (slugify(instance.name), extension) elif isinstance(instance, Location): if instance.id: return "events/location/%s.%s" % (instance.id, extension) else: return "events/location/%s.%s" % (instance.id, extension) elif isinstance(instance, Photo): return "events/%s/%s" % (instance.event.id, filename) def post_save_image(sender, instance=None, created=False, raw=False, **kwargs): """ Reganerate the images. """ os.remove(instance.display.path) os.remove(instance.callout.path) os.remove(instance.thumbnail.path) """ instance.callout.generate(force=True) instance.display.generate(force=True) instance.thumbnail.generate(force=True) """ class CalloutImage(ImageSpec): format = 'PNG' width = 940 height = 300 @property def processors(self): model, field_name = imagekit.utils.get_field_info(self.source) # @UnusedVariable @IgnorePep8 anchor = model.get_anchor() if anchor: return [processors.Transpose(), processors.ResizeToFill( width=self.width, height=self.height, anchor=anchor )] else: return [processors.Transpose(), processors.SmartResize( width=self.width, height=self.height )] class DisplayImage(ImageSpec): format = 'PNG' processors = [processors.Transpose(), processors.ResizeToFit(width=940, height=940, upscale=False)] class ThumbnailImage(CalloutImage): format = 'PNG' width = 140 height = 140 imagekit.register.generator('kasu:image:callout', CalloutImage) imagekit.register.generator('kasu:image:display', DisplayImage) imagekit.register.generator('kasu:image:thumbnail', ThumbnailImage) class ImageModel(models.Model): callout = ImageSpecField(source='image', id='kasu:image:callout') display = ImageSpecField(source='image', id='kasu:image:display') thumbnail = ImageSpecField(source='image', id='kasu:image:thumbnail') def get_anchor(self): try: anchor_horizontal = getattr(self, 'anchor_horizontal') anchor_vertical = getattr(self, 'anchor_vertical') except AttributeError: return None if anchor_horizontal and anchor_vertical: return self.anchor_horizontal, self.anchor_vertical else: return None class Meta: abstract = True class EventManager(models.Manager): def current_event(self): try: current = self.filter(start__lte=now()) current = current.filter(end__gte=now()) return current.order_by('start', 'end')[0] except: return None def next_event(self): try: return self.filter(start__gt=now()).order_by('start', 'end')[0] except: return None def archive(self): return self.filter(start__lt=now()) def upcoming(self, limit=3): result = self.filter(start__gt=now()).order_by('start', 'end') if limit: return result[1:(limit + 1)] else: return result class Event(ImageModel): name = models.CharField(_('Name'), max_length=255) description = models.TextField(_("Description"), blank=True) location = models.ForeignKey('Location') start = models.DateTimeField(_('Start')) end = models.DateTimeField(_('End'), blank=True, null=True) url = models.URLField(_('Homepage'), blank=True) image = models.ImageField(_("Image"), upload_to=get_upload_path, storage=OverwriteStorage(), blank=True, null=True) is_tournament = models.BooleanField(_('Tournament'), default=False, help_text=_(u'This event is a tournament, different rules apply for \ the kyu ranking.')) photo_count = models.PositiveIntegerField(default=0, editable=False) event_series = models.ForeignKey('Event', blank=True, null=True, on_delete=models.SET_NULL, editable=False, verbose_name=_('Event Series'), help_text=_(u'Wenn dieser Event zu einer Veranstaltungsreihe gehört \ werden Ort, Beschreibung, Bild und Homepage von dem hier angegebenen \ Event übernommen.')) objects = EventManager() class Meta(object): verbose_name = _('Event') verbose_name_plural = _('Events') ordering = ('-start', '-end',) def __unicode__(self): try: return "%(name)s (%(date)s)" % {'name': self.name, 'date': self.start.date()} except: return "New Event Model" def get_absolute_url(self): kwargs = { 'pk': self.id, 'year': self.start.strftime('%Y'), 'month': self.start.strftime('%m') } return reverse('event-detail', kwargs=kwargs) def get_edit_url(self): kwargs = { 'pk': self.id, 'year': self.start.strftime('%Y'), 'month': self.start.strftime('%m') } return reverse('event-form', kwargs=kwargs) def get_callout(self): if self.image: return self.callout elif self.photo_set.count(): return self.photo_set.all().order_by('?')[0].callout elif self.location.image: return self.location.callout else: return None def get_thumbnail(self): if self.image: return self.thumbnail elif self.photo_set.count(): return self.photo_set.all().order_by('?')[0].thumbnail elif self.location.image: return self.location.thumbnail else: return None def save(self, **kwargs): if self.event_series: master_event = self.event_series self.description = master_event.description self.location = master_event.location self.url = master_event.url self.image = master_event.image self.photo_count = self.photo_set.count() models.Model.save(self, **kwargs) # Update the rest of the event series: for sub_event in Event.objects.filter(event_series=self): sub_event.save() # Update the Hanchans if necesery: for hanchan in self.hanchan_set.all(): hanchan.save() class Location(ImageModel): name = models.CharField(_("Name"), max_length=200) description = models.TextField(_("Description"), blank=True) image = models.ImageField(_("Image"), upload_to=get_upload_path, storage=OverwriteStorage(), blank=True, null=True) url = models.URLField(_('Homepage'), blank=True) postal_code = models.CharField(_('Postal Code'), max_length=6) street_address = models.CharField(_('Street Address'), max_length=127) locality = models.CharField(_('Locality'), max_length=127) country = models.CharField(_('Country'), max_length=2, choices=COUNTRIES) class Meta(object): verbose_name = _('Venue') verbose_name_plural = _('Venues') def __unicode__(self): return self.name @property def address(self): address = (self.street_address, self.locality, self.country,) return ','.join(address) 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(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=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=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(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: verbose_name = _('Event Image') verbose_name_plural = _('Event Images') ordering = ["created_date"] get_latest_by = "created_date" 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. """ ImageModel.save(self, **kwargs) self.save_metadata() self.event.save() models.signals.post_save.connect(post_save_image, sender=Photo)