"""Models to solitary events, events series with an location and photos.""" import os from ckeditor.fields import RichTextField from django.conf import settings from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q from django.template.defaultfilters import slugify from django.urls import reverse from django.utils.timezone import now from django.utils.translation import ugettext as _ from easy_thumbnails.fields import ThumbnailerImageField from utils import COUNTRIES, OverwriteStorage from .managers import EventManager 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 """ filename, extension = os.path.splitext(filename.lower()) if isinstance(instance, Event): return "events/{date:%Y-%m-%d}/{name}{ext}".format( date=instance.start, name=slugify(instance.name), ext=extension ) elif isinstance(instance, Location): return "events/locations/{name}{ext}".format( name=slugify(instance.name), ext=extension ) elif isinstance(instance, Photo): return "events/{date:%Y-%m-%d}/{name}{ext}".format( date=instance.event.start, name=filename, ext=extension ) class Event(models.Model): """An Event that could be a tournament, a game session, or an convention.""" name = models.CharField(_('Name'), max_length=255) description = RichTextField(_("Description"), blank=True) location = models.ForeignKey('Location', on_delete=models.PROTECT) start = models.DateTimeField(_('Start')) end = models.DateTimeField(_('End'), blank=True, null=True) url = models.URLField(_('Homepage'), blank=True) image = ThumbnailerImageField( _("Image"), upload_to=get_upload_path, storage=OverwriteStorage(), blank=True, null=True ) mahjong_tournament = models.BooleanField( _('Mahjong Tournament'), default=False, help_text=_(u'This event is a tournament, different rules apply for \ the kyu ranking.') ) mahjong_season = models.PositiveSmallIntegerField( _('Mahjong Season'), blank=True, null=True ) photo_count = models.PositiveIntegerField(default=0, editable=False) event_series = models.ForeignKey( 'Event', blank=True, null=True, on_delete=models.SET_NULL, editable=True, 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.') ) date_created = models.DateTimeField( auto_now_add=True, null=True, db_index=True, editable=False, verbose_name=_('first created at'), ) date_modified = models.DateTimeField( auto_now=True, editable=False, verbose_name=_('latest updated at'), ) objects = EventManager() class Meta(object): """order the events by start date.""" verbose_name = _('Event') verbose_name_plural = _('Events') ordering = ('start', 'end',) def __str__(self): try: return "%(name)s (%(date)s)" % {'name': self.name, 'date': self.start.date()} except: return "New Event Model" def clean(self): if self.end and self.end < self.start: raise ValidationError({ 'end': _("A event can't end before it had started") }) 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_eventseries_form_url(self): kwargs = { 'pk': self.id, 'year': self.start.strftime('%Y'), 'month': self.start.strftime('%m') } return reverse('eventseries-form', 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_image(self): if self.image: return self.image elif self.photo_count: return self.photo_set.all().order_by('?')[0].image elif self.location.image: return self.location.image else: return None @property def is_future_event(self): return self.start > now() def save(self, **kwargs): # Fülle fehlende Felder mit den Angaben der Hauptveranstaltung aus. if self.event_series: master_event = self.event_series self.name = self.name or master_event.name self.description = self.description or master_event.description self.location = master_event.location self.url = master_event.url self.image = self.image or master_event.image self.photo_count = self.photo_set.count() super(Event, self).save(**kwargs) # Update the Hanchans if necesery: for hanchan in self.hanchan_set.all(): hanchan.save() class Location(models.Model): name = models.CharField(_("Name"), max_length=200) description = RichTextField(_("Description"), blank=True) image = ThumbnailerImageField( _("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) date_created = models.DateTimeField( auto_now_add=True, db_index=True, editable=False, null=True, verbose_name=_('first created at'), ) date_modified = models.DateTimeField( auto_now=True, editable=False, verbose_name=_('latest updated at'), ) class Meta(object): verbose_name = _('Venue') verbose_name_plural = _('Venues') def __str__(self): return self.name @property def address(self): address = (self.street_address, self.locality, self.country,) return ','.join(address) class Photo(models.Model): name = models.CharField(_("Name"), max_length=100, blank=True) image = ThumbnailerImageField( _("Image"), upload_to=get_upload_path, storage=OverwriteStorage() ) event = models.ForeignKey('events.Event', on_delete=models.PROTECT, ) description = models.TextField( _("Description"), max_length=300, blank=True ) photographer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT) on_startpage = models.BooleanField( _("Startpage"), default=False, db_index=True, 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 ) date_created = models.DateTimeField( auto_now_add=True, db_index=True, editable=False, null=True, verbose_name=_('first created at'), ) date_modified = models.DateTimeField( auto_now=True, editable=False, verbose_name=_('latest updated at'), ) 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 __str__(self): return os.path.basename(self.image.name) def rotate(self, rotate): # TODO: Eine vernüftigte Methode ohne viele Abhängigkeiten finden um # die Bilder bei Bedarf zu drehen. if rotate == 'clockwise': pass elif rotate == 'counter-clockwise': pass self.save() def save(self, **kwargs): super(Photo, self).save(**kwargs) self.event.save() def get_absolute_url(self): return reverse( 'event-photo', kwargs={'event': self.event.id, 'pk': self.id} ) @property def next_photo(self): queryset = Photo.objects.filter(created_date__gt=self.created_date) queryset = queryset.order_by('created_date') try: if self.event.event_series: return queryset.filter( Q(event=self.event) | Q(event=self.event.event_series) | Q(event__event_series=self.event.event_series) )[0] else: return queryset.filter( Q(event=self.event) | Q(event__event_series=self.event) )[0] except IndexError: return None @property def previous_photo(self): queryset = Photo.objects.filter(created_date__lt=self.created_date) queryset = queryset.order_by('-created_date') try: if self.event.event_series: return queryset.filter( Q(event=self.event) | Q(event=self.event.event_series) | Q(event__event_series=self.event.event_series) )[0] else: return queryset.filter( Q(event=self.event) | Q(event__event_series=self.event) )[0] except IndexError: return None