191 lines
6.1 KiB
Python
191 lines
6.1 KiB
Python
from django.db import models
|
|
from utils.models import UUIDModel, CreatedUpdatedModel
|
|
from program.models import EventType, EventLocation
|
|
from django.contrib.postgres.fields import DateTimeRangeField
|
|
from psycopg2.extras import DateTimeTZRange
|
|
from django.core.exceptions import ValidationError
|
|
from datetime import timedelta
|
|
from django.utils import timezone
|
|
from django.urls import reverse
|
|
import logging
|
|
logger = logging.getLogger("bornhack.%s" % __name__)
|
|
|
|
|
|
class Camp(CreatedUpdatedModel, UUIDModel):
|
|
class Meta:
|
|
verbose_name = 'Camp'
|
|
verbose_name_plural = 'Camps'
|
|
ordering = ['-title']
|
|
|
|
title = models.CharField(
|
|
verbose_name='Title',
|
|
help_text='Title of the camp, ie. Bornhack 2016.',
|
|
max_length=255,
|
|
)
|
|
|
|
tagline = models.CharField(
|
|
verbose_name='Tagline',
|
|
help_text='Tagline of the camp, ie. "Initial Commit"',
|
|
max_length=255,
|
|
)
|
|
|
|
slug = models.SlugField(
|
|
verbose_name='Url Slug',
|
|
help_text='The url slug to use for this camp'
|
|
)
|
|
|
|
buildup = DateTimeRangeField(
|
|
verbose_name='Buildup Period',
|
|
help_text='The camp buildup period.',
|
|
)
|
|
|
|
camp = DateTimeRangeField(
|
|
verbose_name='Camp Period',
|
|
help_text='The camp period.',
|
|
)
|
|
|
|
teardown = DateTimeRangeField(
|
|
verbose_name='Teardown period',
|
|
help_text='The camp teardown period.',
|
|
)
|
|
|
|
read_only = models.BooleanField(
|
|
help_text='Whether the camp is read only (i.e. in the past)',
|
|
default=False
|
|
)
|
|
|
|
colour = models.CharField(
|
|
verbose_name='Colour',
|
|
help_text='The primary colour for the camp in hex',
|
|
max_length=7
|
|
)
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('camp_detail', kwargs={'camp_slug': self.slug})
|
|
|
|
def clean(self):
|
|
''' Make sure the dates make sense - meaning no overlaps and buildup before camp before teardown '''
|
|
errors = []
|
|
# check for overlaps buildup vs. camp
|
|
if self.buildup.upper > self.camp.lower:
|
|
msg = "End of buildup must not be after camp start"
|
|
errors.append(ValidationError({'buildup', msg}))
|
|
errors.append(ValidationError({'camp', msg}))
|
|
|
|
# check for overlaps camp vs. teardown
|
|
if self.camp.upper > self.teardown.lower:
|
|
msg = "End of camp must not be after teardown start"
|
|
errors.append(ValidationError({'camp', msg}))
|
|
errors.append(ValidationError({'teardown', msg}))
|
|
|
|
if errors:
|
|
raise ValidationError(errors)
|
|
|
|
def __str__(self):
|
|
return "%s - %s" % (self.title, self.tagline)
|
|
|
|
@property
|
|
def event_types(self):
|
|
# return all event types with at least one event in this camp
|
|
return EventType.objects.filter(event__instances__isnull=False, event__camp=self).distinct()
|
|
|
|
@property
|
|
def event_locations(self):
|
|
''' Return all event locations with at least one event in this camp'''
|
|
return EventLocation.objects.filter(eventinstances__isnull=False, camp=self).distinct()
|
|
|
|
@property
|
|
def logo_small(self):
|
|
return 'img/%(slug)s/logo/%(slug)s-logo-s.png' % {'slug': self.slug}
|
|
|
|
@property
|
|
def logo_small_svg(self):
|
|
return 'img/%(slug)s/logo/%(slug)s-logo-small.svg' % {'slug': self.slug}
|
|
|
|
@property
|
|
def logo_large(self):
|
|
return 'img/%(slug)s/logo/%(slug)s-logo-l.png' % {'slug': self.slug}
|
|
|
|
@property
|
|
def logo_large_svg(self):
|
|
return 'img/%(slug)s/logo/%(slug)s-logo-large.svg' % {'slug': self.slug}
|
|
|
|
def get_days(self, camppart):
|
|
'''
|
|
Returns a list of DateTimeTZRanges representing the days during the specified part of the camp.
|
|
'''
|
|
if not hasattr(self, camppart):
|
|
logger.error("nonexistant field/attribute")
|
|
return False
|
|
|
|
field = getattr(self, camppart)
|
|
|
|
if not hasattr(field, '__class__') or not hasattr(field.__class__, '__name__') or not field.__class__.__name__ == 'DateTimeTZRange':
|
|
logger.error("this attribute is not a datetimetzrange field: %s" % field)
|
|
return False
|
|
|
|
daycount = (field.upper - field.lower).days
|
|
days = []
|
|
for i in range(0, daycount):
|
|
if i == 0:
|
|
# on the first day use actual start time instead of midnight
|
|
days.append(
|
|
DateTimeTZRange(
|
|
field.lower,
|
|
(field.lower+timedelta(days=i+1)).replace(hour=0)
|
|
)
|
|
)
|
|
elif i == daycount-1:
|
|
# on the last day use actual end time instead of midnight
|
|
days.append(
|
|
DateTimeTZRange(
|
|
(field.lower+timedelta(days=i)).replace(hour=0),
|
|
field.lower+timedelta(days=i+1)
|
|
)
|
|
)
|
|
else:
|
|
# neither first nor last day, goes from midnight to midnight
|
|
days.append(
|
|
DateTimeTZRange(
|
|
(field.lower+timedelta(days=i)).replace(hour=0),
|
|
(field.lower+timedelta(days=i+1)).replace(hour=0)
|
|
)
|
|
)
|
|
return days
|
|
|
|
@property
|
|
def buildup_days(self):
|
|
'''
|
|
Returns a list of DateTimeTZRanges representing the days during the buildup.
|
|
'''
|
|
return self.get_days('buildup')
|
|
|
|
@property
|
|
def camp_days(self):
|
|
'''
|
|
Returns a list of DateTimeTZRanges representing the days during the camp.
|
|
'''
|
|
return self.get_days('camp')
|
|
|
|
@property
|
|
def teardown_days(self):
|
|
'''
|
|
Returns a list of DateTimeTZRanges representing the days during the buildup.
|
|
'''
|
|
return self.get_days('teardown')
|
|
|
|
@property
|
|
def call_for_speakers_open(self):
|
|
if self.camp.upper < timezone.now():
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
@property
|
|
def call_for_sponsors_open(self):
|
|
""" Keep call for sponsors open 30 days after camp end """
|
|
if self.camp.upper + timedelta(days=30) < timezone.now():
|
|
return False
|
|
else:
|
|
return True
|