# -*- coding: utf-8 -*- import datetime import logging import os import re import unicodedata from babel.dates import format_date from django.conf import settings from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse from django.db import models from django.template.loader import get_template from django.utils import timezone from django.utils.translation import get_language, ugettext_lazy as _ from django_countries.fields import CountryField from .. import choices from .. import config from .. import signals from ..utils import get_ghost_user, get_system_user from .eventstatus import EventStatus, get_event_status logger = logging.getLogger(__name__) def get_system_user_id(): return get_system_user().id class Event(models.Model): # Metadata owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET(get_ghost_user), related_name='events', verbose_name=_(u'Ersteller')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_(u'Erstellt')) accepted = models.BooleanField(default=False, verbose_name=_(u'Freigegeben')) accepted_at = models.DateTimeField(blank=True, null=True, verbose_name=_(u'Freigegeben am')) accepted_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET(get_ghost_user), related_name='+', verbose_name=_(u'Freigegeben durch')) number = models.CharField(unique=True, max_length=config.NUMBER_MAX_LENGTH, blank=True, null=True, default=None, verbose_name=_(u'Programmnummer')) publication_confirmed = models.BooleanField(default=False, verbose_name=_(u'Veröffentlichung bestätigt')) publication_confirmed_at = models.DateTimeField(blank=True, null=True, verbose_name=_(u'Veröffentlichung bestätigt am')) publication_confirmed_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET(get_ghost_user), related_name='+', verbose_name=_(u'Veröffentlichung bestätigt durch')) # DescriptionForm title = models.CharField(max_length=config.TITLE_MAX_LENGTH, verbose_name=_(u'Titel')) description = models.TextField(verbose_name=_(u'Beschreibung')) # ModeForm mode = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH, choices=choices.MODE_CHOICES, verbose_name=_(u'Veranstaltungsart')) sport = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH, choices=choices.SPORT_CHOICES, verbose_name=_(u'Spielart')) ski_lift = models.BooleanField(default=False, verbose_name=_(u'Skiliftbenutzung')) level = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH, choices=choices.LEVEL_CHOICES, verbose_name=_(u'Schwierigkeitsnivau')) first_day = models.DateField(verbose_name=_(u'Erster Tag')) alt_first_day = models.DateField(blank=True, null=True, verbose_name=u'%s - %s' % (_(u'Ersatztermin'), _(u'Erster Tag'))) last_day = models.DateField(blank=True, null=True, verbose_name=_(u'Letzter Tag')) alt_last_day = models.DateField(blank=True, null=True, verbose_name=u'%s - %s' % (_(u'Ersatztermin'), _(u'Letzter Tag'))) # LocationForm country = CountryField(countries=choices.CountryChoiceSet, verbose_name=_(u'Land')) terrain = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH, choices=choices.TERRAIN_CHOICES, verbose_name=_(u'Gelände')) location = models.CharField(max_length=config.LOCATION_MAX_LENGTH, blank=True, verbose_name=_(u'Ort oder Gebiet')) transport = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH, choices=choices.TRANSPORT_CHOICES, verbose_name=_(u'Verkehrsmittel')) transport_other = models.CharField(max_length=config.TRANSPORT_OTHER_MAX_LENGTH, blank=True, verbose_name=_(u'Anderes Verkehrsmittel')) # JourneyForm meeting_point = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH, choices=choices.MEETING_POINT_CHOICES, verbose_name=_(u'Treffpunkt')) meeting_point_other = models.CharField(max_length=config.MEETING_POINT_OTHER_MAX_LENGTH, blank=True, verbose_name=_(u'Anderer Treffpunkt')) meeting_time = models.TimeField(blank=True, null=True, verbose_name=_(u'Uhrzeit am Treffpunkt')) departure_time = models.TimeField(blank=True, null=True, verbose_name=_(u'Uhrzeit Abfahrt')) departure_ride = models.CharField(max_length=config.DEPARTURE_RIDE_MAX_LENGTH, blank=True, verbose_name=_(u'Bahn-/Bus-Linie')) return_departure_time = models.TimeField(blank=True, null=True, verbose_name=_(u'Uhrzeit Rückfahrt')) return_arrival_time = models.TimeField(blank=True, null=True, verbose_name=_(u'Uhrzeit Rückkunft')) arrival_previous_day = models.BooleanField(default=False, verbose_name=_(u'Anreise des Trainers am Vortag')) # AccommodationForm basecamp = models.CharField(max_length=config.BASECAMP_MAX_LENGTH, blank=True, verbose_name=_(u'Stützpunkt')) accommodation = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH, choices=choices.ACCOMMODATION_CHOICES, verbose_name=_(u'Unterkunft')) accommodation_other = models.CharField(max_length=config.ACCOMMODATION_OTHER_MAX_LENGTH, blank=True, verbose_name=_(u'Andere Unterkunft')) meals = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH, choices=choices.MEALS_CHOICES, verbose_name=_(u'Verpflegung')) meals_other = models.CharField(max_length=config.MEALS_OTHER_MAX_LENGTH, blank=True, verbose_name=_(u'Andere Verpflegung')) # RequirementsForm requirements = models.TextField(blank=True, verbose_name=_(u'Anforderungen')) equipment = models.TextField(blank=True, verbose_name=_(u'Ausrüstung')) pre_meeting_1 = models.DateTimeField(blank=True, null=True, verbose_name=u'1. %s' % _(u'Vortreffen')) pre_meeting_2 = models.DateTimeField(blank=True, null=True, verbose_name=u'2. %s' % _(u'Vortreffen')) # TrainerForm trainer_firstname = models.CharField(max_length=config.TRAINER_NAME_MAX_LENGTH, blank=True, verbose_name=u'1. %s %s' % (_(u'Trainer'), _(u'Vorname'))) trainer_familyname = models.CharField(max_length=config.TRAINER_NAME_MAX_LENGTH, blank=True, verbose_name=u'1. %s %s' % (_(u'Trainer'), _(u'Familienname'))) trainer_email = models.EmailField(blank=True, verbose_name=u'1. %s %s' % (_(u'Trainer'), _(u'E-Mail'))) trainer_phone = models.CharField(max_length=config.PHONE_NUMBER_MAX_LENGTH, blank=True, verbose_name=u'1. %s %s' % (_(u'Trainer'), _(u'Telefon'))) trainer_2_fullname = models.CharField(max_length=config.TRAINER_NAME_MAX_LENGTH, blank=True, verbose_name=u'2. %s %s' % (_(u'Trainer'), _(u'Name'))) trainer_2_email = models.EmailField(blank=True, verbose_name=u'2. %s %s' % (_(u'Trainer'), _(u'E-Mail'))) trainer_2_phone = models.CharField(max_length=config.PHONE_NUMBER_MAX_LENGTH, blank=True, verbose_name=u'2. %s %s' % (_(u'Trainer'), _(u'Telefon'))) trainer_3_fullname = models.CharField(max_length=config.TRAINER_NAME_MAX_LENGTH, blank=True, verbose_name=u'3. %s %s' % (_(u'Trainer'), _(u'Name'))) trainer_3_email = models.EmailField(blank=True, verbose_name=u'3. %s %s' % (_(u'Trainer'), _(u'E-Mail'))) trainer_3_phone = models.CharField(max_length=config.PHONE_NUMBER_MAX_LENGTH, blank=True, verbose_name=u'3. %s %s' % (_(u'Trainer'), _(u'Telefon'))) # RegistrationForm min_participants = models.IntegerField(default=0, verbose_name=_(u'Min. Teilnehmer')) max_participants = models.IntegerField(default=0, verbose_name=_(u'Max. Teilnehmer')) registration_required = models.BooleanField(default=False, verbose_name=_(u'Anmeldung notwendig')) deadline = models.DateField(blank=True, null=True, verbose_name=_(u'Anmeldeschluss')) registration_howto = models.TextField(blank=True, verbose_name=_(u'Anmeldungshinweis')) # ChargesForm charge = models.FloatField(default=0, verbose_name=_(u'Teilnahmegebühr')) additional_costs = models.CharField(max_length=config.ADDITIONAL_COSTS_MAX_LENGTH, blank=True, verbose_name=_(u'Zusätzliche Kosten')) # TrainingForm course_topic_1 = models.TextField(blank=True, verbose_name=u'%s - %s 1' % (_(u'Kursinhalt'), _(u'Absatz'))) course_topic_2 = models.TextField(blank=True, verbose_name=u'%s - %s 2' % (_(u'Kursinhalt'), _(u'Absatz'))) course_topic_3 = models.TextField(blank=True, verbose_name=u'%s - %s 3' % (_(u'Kursinhalt'), _(u'Absatz'))) course_topic_4 = models.TextField(blank=True, verbose_name=u'%s - %s 4' % (_(u'Kursinhalt'), _(u'Absatz'))) course_topic_5 = models.TextField(blank=True, verbose_name=u'%s - %s 5' % (_(u'Kursinhalt'), _(u'Absatz'))) course_topic_6 = models.TextField(blank=True, verbose_name=u'%s - %s 6' % (_(u'Kursinhalt'), _(u'Absatz'))) course_goal_1 = models.TextField(blank=True, verbose_name=u'%s - %s 1' % (_(u'Kursziele'), _(u'Absatz'))) course_goal_2 = models.TextField(blank=True, verbose_name=u'%s - %s 2' % (_(u'Kursziele'), _(u'Absatz'))) course_goal_3 = models.TextField(blank=True, verbose_name=u'%s - %s 3' % (_(u'Kursziele'), _(u'Absatz'))) course_goal_4 = models.TextField(blank=True, verbose_name=u'%s - %s 4' % (_(u'Kursziele'), _(u'Absatz'))) course_goal_5 = models.TextField(blank=True, verbose_name=u'%s - %s 5' % (_(u'Kursziele'), _(u'Absatz'))) course_goal_6 = models.TextField(blank=True, verbose_name=u'%s - %s 6' % (_(u'Kursziele'), _(u'Absatz'))) # SummaryForm planned_publication_date = models.DateField(blank=True, null=True, verbose_name=_(u'Veröffentlichung am')) internal_note = models.TextField(blank=True, verbose_name=_(u'Bearbeitungshinweis')) class Meta: verbose_name = _(u'Veranstaltung') verbose_name_plural = _(u'Veranstaltungen') ordering = ['first_day'] default_permissions = ('view', 'accept', 'edit', 'delete') def __unicode__(self): return u'{number} - {title} ({date})'.format(number=self.get_number(), title=self.title, date=self.get_formated_date()) def get_absolute_url(self): return reverse('dav_events:event_detail', kwargs={'pk': self.pk}) def save(self, **kwargs): creating = False if not self.id: user_model = get_user_model() username = self.trainer_email.lower() if not username: s = self.trainer_firstname.replace(u'ß', u'ss') s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore') s = re.sub(r'[^a-z-]', '', s.lower()) username = s s = self.trainer_familyname.replace(u'ß', u'ss') s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore') s = re.sub(r'[^a-z-]', '', s.lower()) username += '.' + s s = re.sub(r'[^0-9]', '', str(self.trainer_phone)) username += '@' + s try: owner = user_model.objects.get(username=username) except user_model.DoesNotExist: owner = user_model(username=username, first_name=self.trainer_firstname, last_name=self.trainer_familyname, email=self.trainer_email, ) owner.save() logger.info('Owner created: %s', owner.username) self.owner = owner creating = True super(Event, self).save(**kwargs) if creating: self.confirm_status('draft', self.owner) logger.info('Event created: %s', self) def update_flags(self): if not self.id: return today = datetime.date.today() midnight = datetime.time(00, 00, 00) if not self.flags.filter(status__code='draft').exists(): new_flag = EventFlag(event=self, status=get_event_status('draft'), timestamp=self.created_at) new_flag.save() logger.info('Detected draft state of Event %s', self) if (self.flags.filter(status__code='publishing').exists() and not self.flags.filter(status__code='published').exists()): if not self.planned_publication_date: flag = self.flags.filter(status__code='publishing').last() new_status = get_event_status('published') new_flag = EventFlag(event=self, status=new_status, timestamp=flag.timestamp) new_flag.save() logger.info('Detected published state of Event %s', self) elif self.planned_publication_date <= today: new_status = get_event_status('published') new_timestamp = timezone.make_aware(datetime.datetime.combine(self.planned_publication_date, midnight)) new_flag = EventFlag(event=self, status=new_status, timestamp=new_timestamp) new_flag.save() logger.info('Detected published state of Event %s', self) if not self.flags.filter(status__code='expired').exists(): expired_at = None if self.alt_last_day: if self.alt_last_day < today: expired_at = self.alt_last_day elif self.last_day: if self.last_day < today: expired_at = self.last_day elif self.alt_first_day: if self.alt_first_day < today: expired_at = self.alt_first_day elif self.first_day and self.first_day < today: expired_at = self.first_day if expired_at: new_timestamp = timezone.make_aware(datetime.datetime.combine(expired_at, midnight)) new_flag = EventFlag(event=self, status=get_event_status('expired'), timestamp=new_timestamp) new_flag.save() logger.info('Detected expired state of Event %s', self) def is_flagged(self, status): self.update_flags() if isinstance(status, EventStatus): code = status.code else: code = status if self.flags.filter(status__code=code).exists(): return True return False def get_status(self): self.update_flags() last_flag = self.flags.last() if last_flag: return last_flag.status return get_event_status('void') def get_status_codes(self): self.update_flags() return [flag.status.code for flag in self.flags.all()] def confirm_status(self, status, user): if isinstance(status, EventStatus): code = status.code else: code = status flag = self.flags.filter(status__code=code).last() if flag: return flag if code == 'accepted': if not self.is_flagged('submitted'): logger.warning('Event.confirm_status(): yet not submitted event got accepted! (Event: %s)', self) if not self.number: self.number = self.get_next_number() self.save() elif code == 'publishing' or code == 'published': if not self.is_flagged('accepted'): logger.warning('Event.confirm_status(): yet not accepted event got published! (Event: %s)', self) status_obj = get_event_status(code) flag = EventFlag(event=self, status=status_obj, user=user) flag.save() logger.info('Flagging status \'%s\' for %s', code, self) if code == 'submitted': signals.event_submitted.send(sender=self.__class__, event=self) elif code == 'accepted': signals.event_accepted.send(sender=self.__class__, event=self) elif code == 'publishing': signals.event_publishing.send(sender=self.__class__, event=self) elif code == 'published': signals.event_published.send(sender=self.__class__, event=self) elif code == 'expired': signals.event_expired.send(sender=self.__class__, event=self) return flag def get_next_number(self): counter = 0 year = self.first_day.year year_begin = datetime.date(year, 1, 1) year_end = datetime.date(year, 12, 31) qs = Event.objects.filter(number__isnull=False, sport=self.sport, first_day__gte=year_begin, first_day__lte=year_end).order_by('-number') last = qs.first() if last: match = re.match(r'^(?P[A-Z])(?P[0-9][0-9]*)/(?P[0-9][0-9]*)', last.number) if match: gd = match.groupdict() counter = int(gd['count']) counter += 1 n = '%s%02d/%d' % (self.sport, counter, year % 100) return n def get_number(self): if self.accepted and self.number: return self.number else: return '%s**/%d' % (self.sport, self.first_day.year % 100) def get_formated_date(self, begin_date=None, end_date=None, format='normalized_long'): if begin_date is None: begin_date = self.first_day if end_date is None: end_date = self.last_day if format.endswith('numeric'): weekday_fmt = '' day_fmt = 'dd.' month_fmt = 'MM.' year_fmt = 'yyyy' elif format.endswith('short'): weekday_fmt = 'EEE ' day_fmt = 'd.' month_fmt = ' MMM' if begin_date.year == datetime.date.today().year: year_fmt = '' else: year_fmt = ' yy' else: weekday_fmt = 'EEEE, ' day_fmt = 'dd.' month_fmt = ' MMMM' year_fmt = ' yyyy' lang = get_language()[0:2] if not end_date: fmt = '{weekday}{day}{month}{year}'.format(weekday=weekday_fmt, day=day_fmt, month=month_fmt, year=year_fmt) r = format_date(begin_date, fmt, locale=lang) else: end_format = '{weekday}{day}{month}{year}'.format(weekday=weekday_fmt, day=day_fmt, month=month_fmt, year=year_fmt) if format.startswith('normalized'): begin_format = '{weekday}{day}'.format(weekday=weekday_fmt, day=day_fmt) if begin_date.month != end_date.month: begin_format += month_fmt if begin_date.year != end_date.year: begin_format += year_fmt else: begin_format = '{weekday}{day}{month}{year}'.format(weekday=weekday_fmt, day=day_fmt, month=month_fmt, year=year_fmt) begin = format_date(begin_date, begin_format, locale=lang) end = format_date(end_date, end_format, locale=lang) r = u'{begin} - {end}'.format(begin=begin, end=end) return r def get_alt_formated_date(self, format='normalized_long'): if self.alt_first_day: return self.get_formated_date(begin_date=self.alt_first_day, end_date=self.alt_last_day, format=format) else: return None def get_numeric_date(self, begin_date=None, end_date=None): return self.get_formated_date(begin_date=begin_date, end_date=end_date, format='numeric') def get_trainer_full_name(self): return u'%s %s' % (self.trainer_firstname, self.trainer_familyname) def get_template_context(self, context=None): if self.alt_last_day: day_after = self.alt_last_day + datetime.timedelta(1) elif self.last_day: day_after = self.last_day + datetime.timedelta(1) elif self.alt_first_day: day_after = self.alt_first_day + datetime.timedelta(1) else: day_after = self.first_day + datetime.timedelta(1) r = { 'event': self, 'status': self.get_status(), 'number': self.get_number(), 'title': self.title, 'description': self.description, 'mode': self.mode, 'sport': self.sport, 'first_day': self.first_day, 'last_day': self.last_day, 'normalized_date': self.get_formated_date(format='normalized'), 'normalized_long_date': self.get_formated_date(format='normalized_long'), 'normalized_short_date': self.get_formated_date(format='normalized_short'), 'alt_first_day': self.alt_first_day, 'alt_last_day': self.alt_last_day, 'alt_normalized_date': self.get_alt_formated_date(format='normalized'), 'alt_normalized_long_date': self.get_alt_formated_date(format='normalized_long'), 'alt_normalized_short_date': self.get_alt_formated_date(format='normalized_short'), 'day_after': day_after, 'country': self.country, 'location': self.location, 'transport': self.transport, 'transport_other': self.transport_other, 'meeting_point': self.meeting_point, 'meeting_point_other': self.meeting_point_other, 'meeting_time': self.meeting_time, 'departure_time': self.departure_time, 'departure_ride': self.departure_ride, 'return_departure_time': self.return_departure_time, 'return_arrival_time': self.return_arrival_time, 'basecamp': self.basecamp, 'accommodation': self.accommodation, 'accommodation_other': self.accommodation_other, 'meals': self.meals, 'meals_other': self.meals_other, 'requirements': self.requirements, 'equipment': self.equipment, 'pre_meeting_1': self.pre_meeting_1, 'pre_meeting_2': self.pre_meeting_2, 'trainer_firstname': self.trainer_firstname, 'trainer_familyname': self.trainer_familyname, 'trainer_fullname': self.get_trainer_full_name(), 'trainer_email': self.trainer_email, 'trainer_phone': self.trainer_phone, 'trainer_2_fullname': self.trainer_2_fullname, 'trainer_2_email': self.trainer_2_email, 'trainer_2_phone': self.trainer_2_phone, 'trainer_3_fullname': self.trainer_3_fullname, 'trainer_3_email': self.trainer_3_email, 'trainer_3_phone': self.trainer_3_phone, 'min_participants': self.min_participants, 'max_participants': self.max_participants, 'registration_required': self.registration_required, 'deadline': self.deadline, 'registration_howto': self.registration_howto, 'charge': self.charge, 'additional_costs': self.additional_costs, 'course_topic_1': self.course_topic_1, 'course_topic_2': self.course_topic_2, 'course_topic_3': self.course_topic_3, 'course_topic_4': self.course_topic_4, 'course_topic_5': self.course_topic_5, 'course_topic_6': self.course_topic_6, 'course_goal_1': self.course_goal_1, 'course_goal_2': self.course_goal_2, 'course_goal_3': self.course_goal_3, 'course_goal_4': self.course_goal_4, 'course_goal_5': self.course_goal_5, 'course_goal_6': self.course_goal_6, 'planned_publication_date': self.planned_publication_date, 'internal_note': self.internal_note, } if context is not None: r.update(context) return r def render_as_text(self, format=None): if format == 'ka-alpin': template_name = 'ka-alpin.txt' else: template_name = 'default.txt' template_path = os.path.join('dav_events', 'event', template_name) template = get_template(template_path) return template.render(self.get_template_context()) def render_as_html(self): template_name = os.path.join('dav_events', 'event', 'default.html') template = get_template(template_name) return template.render(self.get_template_context()) class EventFlag(models.Model): event = models.ForeignKey(Event, related_name='flags') status = models.ForeignKey(EventStatus, on_delete=models.PROTECT, related_name='+') timestamp = models.DateTimeField(default=timezone.now) user = models.ForeignKey(settings.AUTH_USER_MODEL, default=get_system_user_id, on_delete=models.SET(get_ghost_user), related_name='+') class Meta: ordering = ['event', 'status', 'timestamp'] def __unicode__(self): s = u'{status} - {timestamp}' if self.user: s += u' by user {user}' return s.format(status=self.status, timestamp=self.timestamp.strftime('%d.%m.%Y %H:%M:%S'), user=self.user)