Files
django-dav-events/dav_events/models/event.py

626 lines
30 KiB
Python

# -*- 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<sport>[A-Z])(?P<count>[0-9][0-9]*)/(?P<year>[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)