487 lines
24 KiB
Python
487 lines
24 KiB
Python
# -*- coding: utf-8 -*-
|
|
from __future__ import unicode_literals
|
|
import datetime
|
|
import difflib
|
|
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.encoding import python_2_unicode_compatible
|
|
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
|
|
from ..workflow import DefaultWorkflow
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
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=_('Ersteller'))
|
|
created_at = models.DateTimeField(auto_now_add=True,
|
|
verbose_name=_('Erstellt'))
|
|
number = models.CharField(unique=True,
|
|
max_length=config.NUMBER_MAX_LENGTH,
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
verbose_name=_('Programmnummer'))
|
|
|
|
# DescriptionForm
|
|
title = models.CharField(max_length=config.TITLE_MAX_LENGTH, verbose_name=_('Titel'))
|
|
description = models.TextField(verbose_name=_('Beschreibung'))
|
|
|
|
# ModeForm
|
|
mode = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH,
|
|
choices=choices.MODE_CHOICES,
|
|
verbose_name=_('Veranstaltungsart'))
|
|
sport = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH,
|
|
choices=choices.SPORT_CHOICES,
|
|
verbose_name=_('Spielart'))
|
|
ski_lift = models.BooleanField(default=False,
|
|
verbose_name=_('Skiliftbenutzung'))
|
|
level = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH,
|
|
choices=choices.LEVEL_CHOICES,
|
|
verbose_name=_('Schwierigkeitsnivau'))
|
|
first_day = models.DateField(verbose_name=_('Erster Tag'))
|
|
alt_first_day = models.DateField(blank=True,
|
|
null=True,
|
|
verbose_name='%s - %s' % (_('Ersatztermin'), _('Erster Tag')))
|
|
last_day = models.DateField(blank=True,
|
|
null=True,
|
|
verbose_name=_('Letzter Tag'))
|
|
alt_last_day = models.DateField(blank=True,
|
|
null=True,
|
|
verbose_name='%s - %s' % (_('Ersatztermin'), _('Letzter Tag')))
|
|
|
|
# LocationForm
|
|
country = CountryField(countries=choices.CountryChoiceSet,
|
|
verbose_name=_('Land'))
|
|
terrain = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH,
|
|
choices=choices.TERRAIN_CHOICES,
|
|
default=choices.TERRAIN_CHOICES[0][0],
|
|
verbose_name=_('Gelände'))
|
|
location = models.CharField(max_length=config.LOCATION_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name=_('Ort oder Gebiet'))
|
|
transport = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH,
|
|
choices=choices.TRANSPORT_CHOICES,
|
|
default=choices.TRANSPORT_CHOICES[0][0],
|
|
verbose_name=_('Verkehrsmittel'))
|
|
transport_other = models.CharField(max_length=config.TRANSPORT_OTHER_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name=_('Anderes Verkehrsmittel'))
|
|
|
|
# JourneyForm
|
|
meeting_point = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH,
|
|
choices=choices.MEETING_POINT_CHOICES,
|
|
default=choices.MEETING_POINT_CHOICES[0][0],
|
|
verbose_name=_('Treffpunkt'))
|
|
meeting_point_other = models.CharField(max_length=config.MEETING_POINT_OTHER_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name=_('Anderer Treffpunkt'))
|
|
meeting_time = models.TimeField(blank=True,
|
|
null=True,
|
|
verbose_name=_('Uhrzeit am Treffpunkt'))
|
|
departure_time = models.TimeField(blank=True,
|
|
null=True,
|
|
verbose_name=_('Uhrzeit Abfahrt'))
|
|
departure_ride = models.CharField(max_length=config.DEPARTURE_RIDE_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name=_('Bahn-/Bus-Linie'))
|
|
return_departure_time = models.TimeField(blank=True,
|
|
null=True,
|
|
verbose_name=_('Uhrzeit Rückfahrt'))
|
|
return_arrival_time = models.TimeField(blank=True,
|
|
null=True,
|
|
verbose_name=_('Uhrzeit Rückkunft'))
|
|
arrival_previous_day = models.BooleanField(default=False,
|
|
verbose_name=_('Anreise des Trainers am Vortag'))
|
|
|
|
# AccommodationForm
|
|
basecamp = models.CharField(max_length=config.BASECAMP_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name=_('Stützpunkt'))
|
|
|
|
accommodation = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH,
|
|
choices=choices.ACCOMMODATION_CHOICES,
|
|
default=choices.ACCOMMODATION_CHOICES[0][0],
|
|
verbose_name=_('Unterkunft'))
|
|
accommodation_other = models.CharField(max_length=config.ACCOMMODATION_OTHER_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name=_('Andere Unterkunft'))
|
|
meals = models.CharField(max_length=choices.CHOICE_FIELD_MAX_LENGTH,
|
|
choices=choices.MEALS_CHOICES,
|
|
default=choices.MEALS_CHOICES[0][0],
|
|
verbose_name=_('Verpflegung'))
|
|
meals_other = models.CharField(max_length=config.MEALS_OTHER_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name=_('Andere Verpflegung'))
|
|
|
|
# RequirementsForm
|
|
requirements = models.TextField(blank=True,
|
|
verbose_name=_('Anforderungen'))
|
|
equipment = models.TextField(blank=True,
|
|
verbose_name=_('Ausrüstung'))
|
|
pre_meeting_1 = models.DateTimeField(blank=True,
|
|
null=True,
|
|
verbose_name='1. %s' % _('Vortreffen'))
|
|
pre_meeting_2 = models.DateTimeField(blank=True,
|
|
null=True,
|
|
verbose_name='2. %s' % _('Vortreffen'))
|
|
|
|
# TrainerForm
|
|
trainer_firstname = models.CharField(max_length=config.TRAINER_NAME_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name='1. %s %s' % (_('Trainer'), _('Vorname')))
|
|
trainer_familyname = models.CharField(max_length=config.TRAINER_NAME_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name='1. %s %s' % (_('Trainer'), _('Familienname')))
|
|
trainer_email = models.EmailField(blank=True,
|
|
verbose_name='1. %s %s' % (_('Trainer'), _('E-Mail')))
|
|
trainer_phone = models.CharField(max_length=config.PHONE_NUMBER_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name='1. %s %s' % (_('Trainer'), _('Telefon')))
|
|
trainer_2_fullname = models.CharField(max_length=config.TRAINER_NAME_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name='2. %s %s' % (_('Trainer'), _('Name')))
|
|
trainer_2_email = models.EmailField(blank=True,
|
|
verbose_name='2. %s %s' % (_('Trainer'), _('E-Mail')))
|
|
trainer_2_phone = models.CharField(max_length=config.PHONE_NUMBER_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name='2. %s %s' % (_('Trainer'), _('Telefon')))
|
|
trainer_3_fullname = models.CharField(max_length=config.TRAINER_NAME_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name='3. %s %s' % (_('Trainer'), _('Name')))
|
|
trainer_3_email = models.EmailField(blank=True,
|
|
verbose_name='3. %s %s' % (_('Trainer'), _('E-Mail')))
|
|
trainer_3_phone = models.CharField(max_length=config.PHONE_NUMBER_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name='3. %s %s' % (_('Trainer'), _('Telefon')))
|
|
|
|
# RegistrationForm
|
|
min_participants = models.IntegerField(default=0,
|
|
verbose_name=_('Min. Teilnehmer'))
|
|
max_participants = models.IntegerField(default=0,
|
|
verbose_name=_('Max. Teilnehmer'))
|
|
registration_required = models.BooleanField(default=False,
|
|
verbose_name=_('Anmeldung notwendig'))
|
|
deadline = models.DateField(blank=True,
|
|
null=True,
|
|
verbose_name=_('Anmeldeschluss'))
|
|
registration_howto = models.TextField(blank=True,
|
|
verbose_name=_('Anmeldungshinweis'))
|
|
|
|
# ChargesForm
|
|
charge = models.FloatField(default=0,
|
|
verbose_name=_('Teilnahmegebühr'))
|
|
additional_costs = models.CharField(max_length=config.ADDITIONAL_COSTS_MAX_LENGTH,
|
|
blank=True,
|
|
verbose_name=_('Zusätzliche Kosten'))
|
|
|
|
# TrainingForm
|
|
course_topic_1 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 1' % (_('Kursinhalt'), _('Absatz')))
|
|
course_topic_2 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 2' % (_('Kursinhalt'), _('Absatz')))
|
|
course_topic_3 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 3' % (_('Kursinhalt'), _('Absatz')))
|
|
course_topic_4 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 4' % (_('Kursinhalt'), _('Absatz')))
|
|
course_topic_5 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 5' % (_('Kursinhalt'), _('Absatz')))
|
|
course_topic_6 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 6' % (_('Kursinhalt'), _('Absatz')))
|
|
course_goal_1 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 1' % (_('Kursziele'), _('Absatz')))
|
|
course_goal_2 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 2' % (_('Kursziele'), _('Absatz')))
|
|
course_goal_3 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 3' % (_('Kursziele'), _('Absatz')))
|
|
course_goal_4 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 4' % (_('Kursziele'), _('Absatz')))
|
|
course_goal_5 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 5' % (_('Kursziele'), _('Absatz')))
|
|
course_goal_6 = models.TextField(blank=True,
|
|
verbose_name='%s - %s 6' % (_('Kursziele'), _('Absatz')))
|
|
|
|
# SummaryForm
|
|
planned_publication_date = models.DateField(blank=True,
|
|
null=True,
|
|
verbose_name=_('Veröffentlichung am'))
|
|
internal_note = models.TextField(blank=True,
|
|
verbose_name=_('Bearbeitungshinweis'))
|
|
|
|
@property
|
|
def workflow(self):
|
|
return DefaultWorkflow(self)
|
|
|
|
@property
|
|
def editor(self):
|
|
if not hasattr(self, '_editor'):
|
|
self._editor = None
|
|
return self._editor
|
|
|
|
@editor.setter
|
|
def editor(self, editor):
|
|
self._editor = editor
|
|
|
|
class Meta:
|
|
verbose_name = _('Veranstaltung')
|
|
verbose_name_plural = _('Veranstaltungen')
|
|
ordering = ['first_day']
|
|
|
|
def __str__(self):
|
|
return '{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:detail', kwargs={'pk': self.pk})
|
|
|
|
def save(self, implicit_update=False, **kwargs):
|
|
creating = False
|
|
original_text = ''
|
|
|
|
if not self.id:
|
|
user_model = get_user_model()
|
|
username = self.trainer_email.lower()
|
|
if not username:
|
|
s = self.trainer_firstname.replace('ß', 'ss')
|
|
s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore')
|
|
s = re.sub(r'[^a-z-]', '', s.lower())
|
|
username = s
|
|
s = self.trainer_familyname.replace('ß', '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)
|
|
|
|
if self.editor and self.editor.is_authenticated and self.editor != owner:
|
|
logger.warning('Event is not created by its owner (Current user: %s, Owner: %s)!', self.editor, owner)
|
|
self.owner = owner
|
|
creating = True
|
|
elif not implicit_update:
|
|
original = Event.objects.get(id=self.id)
|
|
original_text = original.render_as_text(show_internal_fields=True)
|
|
|
|
if not self.editor or not self.editor.is_authenticated:
|
|
self.editor = self.owner
|
|
|
|
super(Event, self).save(**kwargs)
|
|
|
|
if creating:
|
|
logger.info('Event created: %s', self)
|
|
signals.event_created.send(sender=self.__class__, event=self)
|
|
self.workflow.update_status('draft', self.editor)
|
|
elif not implicit_update:
|
|
modified_text = self.render_as_text(show_internal_fields=True)
|
|
o_lines = original_text.split('\n')
|
|
m_lines = modified_text.split('\n')
|
|
diff_lines = list(difflib.unified_diff(o_lines, m_lines, n=len(m_lines), lineterm=''))
|
|
logger.info('Event updated: %s', self)
|
|
signals.event_updated.send(sender=self.__class__, event=self, diff=diff_lines, user=self.editor)
|
|
|
|
def is_deadline_expired(self):
|
|
today = datetime.date.today()
|
|
return self.deadline and self.deadline < today
|
|
|
|
def get_number(self):
|
|
return self.workflow.get_number()
|
|
|
|
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 = '{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 '%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,
|
|
'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, show_internal_fields=False):
|
|
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, using='PLAINTEXT')
|
|
return template.render(self.get_template_context({'show_internal_fields': show_internal_fields}))
|
|
|
|
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())
|