commit before start of work sunday, mostly fixing schedule stuff, including setting settings.TIME_ZONE properly, this means we will need to convert production data times to be UTC
This commit is contained in:
parent
125d433860
commit
518611534e
|
@ -30,4 +30,7 @@ BANKACCOUNT_IBAN='123'
|
||||||
BANKACCOUNT_SWIFTBIC='123'
|
BANKACCOUNT_SWIFTBIC='123'
|
||||||
BANKACCOUNT_REG='123'
|
BANKACCOUNT_REG='123'
|
||||||
BANKACCOUNT_ACCOUNT='123'
|
BANKACCOUNT_ACCOUNT='123'
|
||||||
|
TIME_ZONE='Europe/Copenhagen'
|
||||||
|
SCHEDULE_MIDNIGHT_OFFSET_HOURS=6
|
||||||
|
SCHEDULE_TIMESLOT_LENGTH_MINUTES=30
|
||||||
|
|
||||||
|
|
|
@ -54,10 +54,14 @@ STATIC_ROOT = local_dir('static')
|
||||||
STATICFILES_DIRS = [local_dir('static_src')]
|
STATICFILES_DIRS = [local_dir('static_src')]
|
||||||
MEDIA_ROOT = env('MEDIA_ROOT')
|
MEDIA_ROOT = env('MEDIA_ROOT')
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = env('TIME_ZONE')
|
||||||
USE_I18N = True
|
#USE_I18N = True
|
||||||
USE_L10N = True
|
#USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
SHORT_DATE_FORMAT = 'd/m-Y'
|
||||||
|
DATE_FORMAT = 'd/m-Y'
|
||||||
|
DATETIME_FORMAT = 'd/m-Y H:i'
|
||||||
|
TIME_FORMAT = 'H:i'
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
|
@ -167,3 +171,7 @@ LOGGING = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# schedule settings
|
||||||
|
SCHEDULE_MIDNIGHT_OFFSET_HOURS=int(env('SCHEDULE_MIDNIGHT_OFFSET_HOURS'))
|
||||||
|
SCHEDULE_TIMESLOT_LENGTH_MINUTES=int(env('SCHEDULE_TIMESLOT_LENGTH_MINUTES'))
|
||||||
|
|
||||||
|
|
|
@ -49,11 +49,6 @@ urlpatterns = [
|
||||||
TemplateView.as_view(template_name='sponsors.html'),
|
TemplateView.as_view(template_name='sponsors.html'),
|
||||||
name='call-for-sponsors'
|
name='call-for-sponsors'
|
||||||
),
|
),
|
||||||
url(
|
|
||||||
r'^speakers/',
|
|
||||||
TemplateView.as_view(template_name='speakers.html'),
|
|
||||||
name='call-for-speakers'
|
|
||||||
),
|
|
||||||
url(
|
url(
|
||||||
r'^login/$',
|
r'^login/$',
|
||||||
LoginView.as_view(),
|
LoginView.as_view(),
|
||||||
|
@ -123,7 +118,12 @@ urlpatterns = [
|
||||||
url(
|
url(
|
||||||
r'^(?P<slug>[-_\w+]+)/$',
|
r'^(?P<slug>[-_\w+]+)/$',
|
||||||
EventDetailView.as_view(),
|
EventDetailView.as_view(),
|
||||||
name='event'
|
name='event_detail'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^call-for-speakers/$',
|
||||||
|
CallForSpeakersView.as_view(),
|
||||||
|
name='call_for_speakers'
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,6 +3,9 @@ from django.db import models
|
||||||
from utils.models import UUIDModel, CreatedUpdatedModel
|
from utils.models import UUIDModel, CreatedUpdatedModel
|
||||||
from program.models import EventType
|
from program.models import EventType
|
||||||
from django.contrib.postgres.fields import DateTimeRangeField
|
from django.contrib.postgres.fields import DateTimeRangeField
|
||||||
|
from psycopg2.extras import DateTimeTZRange
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
|
||||||
class Camp(CreatedUpdatedModel, UUIDModel):
|
class Camp(CreatedUpdatedModel, UUIDModel):
|
||||||
|
@ -42,6 +45,36 @@ class Camp(CreatedUpdatedModel, UUIDModel):
|
||||||
help_text='The camp teardown period.',
|
help_text='The camp teardown period.',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
''' Make sure the dates make sense - meaning no overlaps and buildup before camp before teardown '''
|
||||||
|
errors = []
|
||||||
|
# sanity checking for buildup
|
||||||
|
if self.buildup.lower > self.buildup.upper:
|
||||||
|
errors.append(ValidationError({'buildup', 'Start of buildup must be before end of buildup'}))
|
||||||
|
|
||||||
|
# sanity checking for camp
|
||||||
|
if self.camp.lower > self.camp.upper:
|
||||||
|
errors.append(ValidationError({'camp', 'Start of camp must be before end of camp'}))
|
||||||
|
|
||||||
|
# sanity checking for teardown
|
||||||
|
if self.teardown.lower > self.teardown.upper:
|
||||||
|
errors.append(ValidationError({'teardown', 'Start of teardown must be before end of teardown'}))
|
||||||
|
|
||||||
|
# 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 __unicode__(self):
|
def __unicode__(self):
|
||||||
return "%s - %s" % (self.title, self.tagline)
|
return "%s - %s" % (self.title, self.tagline)
|
||||||
|
|
||||||
|
@ -58,3 +91,68 @@ class Camp(CreatedUpdatedModel, UUIDModel):
|
||||||
def logo_large(self):
|
def logo_large(self):
|
||||||
return 'img/%(slug)s/logo/%(slug)s-logo-large.png' % {'slug': self.slug}
|
return 'img/%(slug)s/logo/%(slug)s-logo-large.png' % {'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):
|
||||||
|
print("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':
|
||||||
|
print("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')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Event, Speaker, EventType
|
from .models import Event, Speaker, EventType, EventInstance
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(EventInstance)
|
||||||
|
class EventInstanceAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
@admin.register(EventType)
|
@admin.register(EventType)
|
||||||
class EventTypeAdmin(admin.ModelAdmin):
|
class EventTypeAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
21
program/migrations/0013_auto_20170121_1312.py
Normal file
21
program/migrations/0013_auto_20170121_1312.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-01-21 12:12
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('program', '0012_auto_20161229_2150'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='camp',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='events', to='camps.Camp'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -2,8 +2,10 @@ from __future__ import unicode_literals
|
||||||
from django.contrib.postgres.fields import DateTimeRangeField
|
from django.contrib.postgres.fields import DateTimeRangeField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
from django.conf import settings
|
||||||
from utils.models import CreatedUpdatedModel
|
from utils.models import CreatedUpdatedModel
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
|
||||||
class EventType(CreatedUpdatedModel):
|
class EventType(CreatedUpdatedModel):
|
||||||
|
@ -23,13 +25,13 @@ class Event(CreatedUpdatedModel):
|
||||||
slug = models.SlugField(blank=True, max_length=255)
|
slug = models.SlugField(blank=True, max_length=255)
|
||||||
abstract = models.TextField()
|
abstract = models.TextField()
|
||||||
event_type = models.ForeignKey(EventType)
|
event_type = models.ForeignKey(EventType)
|
||||||
camp = models.ForeignKey('camps.Camp', null=True)
|
camp = models.ForeignKey('camps.Camp', null=True, related_name="events")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['title']
|
ordering = ['title']
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.title
|
return '%s (%s)' % (self.title, self.camp.title)
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
if not self.slug:
|
if not self.slug:
|
||||||
|
@ -48,6 +50,39 @@ class EventInstance(CreatedUpdatedModel):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return '%s (%s)' % (self.event, self.when)
|
return '%s (%s)' % (self.event, self.when)
|
||||||
|
|
||||||
|
def __clean__(self):
|
||||||
|
errors = []
|
||||||
|
if self.when.lower > self.when.upper:
|
||||||
|
errors.append(ValidationError({'when', "Start should be earlier than finish"}))
|
||||||
|
|
||||||
|
if self.when.lower.time().minute != 0 and self.when.lower.time().minute != 30:
|
||||||
|
errors.append(ValidationError({'when', "Start time minute should be 0 or 30."}))
|
||||||
|
|
||||||
|
if self.when.upper.time().minute != 0 and self.when.upper.time().minute != 30:
|
||||||
|
errors.append(ValidationError({'when', "End time minute should be 0 or 30."}))
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
raise ValidationError(errors)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def schedule_date(self):
|
||||||
|
"""
|
||||||
|
Returns the schedule date of this eventinstance. Schedule date is determined by substracting
|
||||||
|
settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS from the eventinstance start time. This means that if
|
||||||
|
an event is scheduled for 00:30 wednesday evening (technically thursday) then the date
|
||||||
|
after substracting 5 hours would be wednesdays date, not thursdays.
|
||||||
|
"""
|
||||||
|
return (self.when.lower-timedelta(hours=settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS)).date()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timeslots(self):
|
||||||
|
"""
|
||||||
|
Find the number of timeslots this eventinstance takes up
|
||||||
|
"""
|
||||||
|
seconds = (self.when.upper-self.when.lower).seconds
|
||||||
|
minutes = seconds / 60
|
||||||
|
return minutes / settings.SCHEDULE_TIMESLOT_LENGTH_MINUTES
|
||||||
|
|
||||||
|
|
||||||
class Speaker(CreatedUpdatedModel):
|
class Speaker(CreatedUpdatedModel):
|
||||||
""" A Person anchoring an event. """
|
""" A Person anchoring an event. """
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for event in event_list %}
|
{% for event in event_list %}
|
||||||
{% if event.event_type.name != "Facilities" %}
|
{% if event.event_type.name != "Facilities" %}
|
||||||
<a href="{% url 'schedule:event' slug=event.slug %}" class="list-group-item">
|
<a href="{% url 'event_detail' camp_slug=camp.slug slug=event.slug %}" class="list-group-item">
|
||||||
<small style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">
|
<small style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">
|
||||||
{{ event.event_type.name }}
|
{{ event.event_type.name }}
|
||||||
</small>
|
</small>
|
||||||
|
|
|
@ -2,17 +2,15 @@
|
||||||
|
|
||||||
{% block schedule_content %}
|
{% block schedule_content %}
|
||||||
|
|
||||||
<a href="{% url 'schedule:index' %}" class="btn btn-default" style="display: inline-block; padding: 5px;">
|
<a href="{% url 'schedule_index' camp_slug=camp.slug %}" class="btn btn-default" style="display: inline-block; padding: 5px;">Overview</a>
|
||||||
Overview
|
{% for day in camp.camp_days %}
|
||||||
</a>
|
{% with day.lower.date|date:"m" as month_padded %}
|
||||||
{% for day in days %}
|
{% with day.lower.date|date:"d" as day_padded %}
|
||||||
{% with day.date|date:"m" as month_padded %}
|
<a href="{% url 'schedule_day' camp_slug=camp.slug year=day.lower.date.year month=month_padded day=day_padded %}" class="btn btn-default" style="display: inline-block; padding: 5px;">
|
||||||
{% with day.date|date:"d" as day_padded %}
|
{{ day.lower.date|date:"l" }}
|
||||||
<a href="{% url 'schedule:day' year=day.date.year month=month_padded day=day_padded %}" class="btn btn-default" style="display: inline-block; padding: 5px;">
|
</a>
|
||||||
{{ day.date|date:"l" }}
|
{% endwith %}
|
||||||
</a>
|
{% endwith %}
|
||||||
{% endwith %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
|
@ -3,17 +3,14 @@
|
||||||
{% block program_content %}
|
{% block program_content %}
|
||||||
<h2>{{ date|date:"l, F jS" }}</h2>
|
<h2>{{ date|date:"l, F jS" }}</h2>
|
||||||
{% for event in events %}
|
{% for event in events %}
|
||||||
|
|
||||||
{% ifchanged event.event_type %}
|
{% ifchanged event.event_type %}
|
||||||
|
|
||||||
{% if not forloop.first %}</div>{% endif %}
|
{% if not forloop.first %}</div>{% endif %}
|
||||||
<h3>{{ event.event_type }}</h3>
|
<h3>{{ event.event_type }}</h3>
|
||||||
<div style="display: flex; flex-wrap: wrap;">
|
<div style="display: flex; flex-wrap: wrap;">
|
||||||
|
|
||||||
{% endifchanged %}
|
{% endifchanged %}
|
||||||
|
|
||||||
<a class="event"
|
<a class="event"
|
||||||
href="{% url 'schedule:event' slug=event.slug %}"
|
href="{% url 'event_detail' camp_slut=camp.slug slug=event.slug %}"
|
||||||
style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %};">
|
style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %};">
|
||||||
<small>{{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }}</small>
|
<small>{{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }}</small>
|
||||||
<br />
|
<br />
|
||||||
|
@ -25,6 +22,27 @@
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<table>
|
||||||
|
{% for timeslot in timeslots %}
|
||||||
|
<tr>
|
||||||
|
<td style="height: 50px; padding: 5px;">{{ timeslot.time }}</td>
|
||||||
|
{% for eventinstance in eventinstances %}
|
||||||
|
{% if eventinstance.when.lower.time == timeslot.time %}
|
||||||
|
<td style="background-color: {{ eventinstance.event.event_type.color }}; color: {% if event.event_type.light_text %}white{% else %}black{% endif %};" class="event" rowspan={{ eventinstance.timeslots }}>
|
||||||
|
<a style="color:inherit;" href="{% url 'event_detail' camp_slug=camp.slug slug=eventinstance.event.slug %}">
|
||||||
|
{{ eventinstance.event.title }}<br>
|
||||||
|
{{ eventinstance.when.lower.time }}-{{ eventinstance.when.upper.time }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
{% endblock program_content %}
|
{% endblock program_content %}
|
||||||
|
|
|
@ -26,7 +26,7 @@ Not scheduled yet
|
||||||
{% if event.speakers.exists %}
|
{% if event.speakers.exists %}
|
||||||
{% for speaker in event.speakers.all %}
|
{% for speaker in event.speakers.all %}
|
||||||
|
|
||||||
<h3><a href="{% url 'schedule:speaker_detail' slug=speaker.slug %}">{{ speaker }}</a></h3>
|
<h3><a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}">{{ speaker }}</a></h3>
|
||||||
{{ speaker.biography|commonmark }}
|
{{ speaker.biography|commonmark }}
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,36 +1,37 @@
|
||||||
{% extends 'program_base.html' %}
|
{% extends 'program_base.html' %}
|
||||||
|
|
||||||
{% block program_content %}
|
{% block program_content %}
|
||||||
<a href="{% url 'schedule:index' %}" style="background-color: black; border: 0; color: white; display: inline-block; padding: 5px;">
|
<a href="{% url 'schedule_index' camp_slug=camp.slug %}" style="background-color: black; border: 0; color: white; display: inline-block; padding: 5px;">
|
||||||
All
|
All
|
||||||
</a>
|
</a>
|
||||||
{% for event_type in camp.event_types %}
|
{% for event_type in camp.event_types %}
|
||||||
<a href="{% url 'schedule_index' %}?type={{ event_type.slug }}" style="background-color: {{ event_type.color }}; border: 0; color: {% if event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">
|
<a href="{% url 'schedule_index' camp_slug=camp.slug %}?type={{ event_type.slug }}" style="background-color: {{ event_type.color }}; border: 0; color: {% if event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">
|
||||||
{{ event_type.name }}
|
{{ event_type.name }}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
{% for day, events in day_events.items %}
|
{% for day in camp.camp_days %}
|
||||||
{{ day.date|date:"D d/m" }} <br />
|
{{ day.lower.date|date:"D d/m" }} <br />
|
||||||
<div style="display: flex; flex-wrap: wrap;">
|
<div style="display: flex; flex-wrap: wrap;">
|
||||||
{% for event in events %}
|
{% for event in camp.events.all %}
|
||||||
|
{% for eventinstance in event.instances.all %}
|
||||||
|
{% if eventinstance.schedule_date == day.lower.date %}
|
||||||
<a class="event"
|
<a class="event"
|
||||||
href="{% url 'schedule:event' slug=event.slug %}"
|
href="{% url 'event_detail' camp_slug=camp.slug slug=eventinstance.event.slug %}"
|
||||||
style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %};">
|
style="background-color: {{ eventinstance.event.event_type.color }}; border: 0; color: {% if eveninstance.event.event_type.light_text %}white{% else %}black{% endif %};">
|
||||||
<small>{{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }}</small>
|
<small>{{ eventinstance.when.lower|date:"H:i" }} - {{ eventinstance.when.upper|date:"H:i" }}</small>
|
||||||
<br />
|
<br />
|
||||||
{{ event }}
|
{{ event }}
|
||||||
<br />
|
<br />
|
||||||
{% if event.speakers.exists %}
|
{% if event.speakers.exists %}<i>by {{ event.speakers.all|join:", " }}{% endif %}</i>
|
||||||
<i>by {{ event.speakers.all|join:", " }}
|
|
||||||
{% endif %}</i>
|
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% endblock program_content %}
|
{% endblock program_content %}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="{% url 'schedule:index' %}" class="btn btn-default">Schedule</a>
|
<a href="{% url 'schedule_index' camp_slug=camp.slug %}" class="btn btn-default">Schedule</a>
|
||||||
<a href="{% url 'call-for-speakers' %}" class="btn btn-default">Call for Speakers</a>
|
<a href="{% url 'call_for_speakers' camp_slug=camp.slug %}" class="btn btn-default">Call for Speakers</a>
|
||||||
<a href="{% url 'schedule:speaker_index' %}" class="btn btn-default">Speakers</a>
|
<a href="{% url 'speaker_index' camp_slug=camp.slug %}" class="btn btn-default">Speakers</a>
|
||||||
<a href="{% url 'schedule:event_index' %}" class="btn btn-default">Talks & Events</a>
|
<a href="{% url 'event_index' camp_slug=camp.slug %}" class="btn btn-default">Talks & Events</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<small style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">
|
<small style="background-color: {{ event.event_type.color }}; border: 0; color: {% if event.event_type.light_text %}white{% else %}black{% endif %}; display: inline-block; padding: 5px;">
|
||||||
{{ event.event_type.name }}
|
{{ event.event_type.name }}
|
||||||
</small><br>
|
</small><br>
|
||||||
<a href="{% url 'schedule:event' slug=event.slug %}">{{ event.title }}</a></h3>
|
<a href="{% url 'event_detail' camp_slug=camp.slug slug=event.slug %}">{{ event.title }}</a></h3>
|
||||||
{{ event.abstract|commonmark }}
|
{{ event.abstract|commonmark }}
|
||||||
{% if event.start and event.end and event.days.all.exists %}
|
{% if event.start and event.end and event.days.all.exists %}
|
||||||
At {{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }} on
|
At {{ event.start|date:"H:i" }} - {{ event.end|date:"H:i" }} on
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for speaker in speaker_list %}
|
{% for speaker in speaker_list %}
|
||||||
<a href="{% url 'schedule:speaker_detail' slug=speaker.slug %}" class="list-group-item">
|
<a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}" class="list-group-item">
|
||||||
{{ speaker.name }} ({{ speaker.events.all.count }} event{{ speaker.events.all.count|pluralize }})
|
{{ speaker.name }} ({{ speaker.events.all.count }} event{{ speaker.events.all.count|pluralize }})
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
from django.conf.urls import url
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})/$', views.ProgramDayView.as_view(), name='day'),
|
|
||||||
url(r'^$', views.ProgramOverviewView.as_view(), name='index'),
|
|
||||||
url(r'^speakers/$', views.SpeakerListView.as_view(), name='speaker_index'),
|
|
||||||
url(r'^speakers/(?P<slug>[-_\w+]+)/$', views.SpeakerDetailView.as_view(), name='speaker_detail'),
|
|
||||||
url(r'^events/$', views.EventListView.as_view(), name='event_index'),
|
|
||||||
url(r'^(?P<slug>[-_\w+]+)/$', views.EventDetailView.as_view(), name='event'),
|
|
||||||
]
|
|
|
@ -1,10 +1,11 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from django.views.generic import ListView, TemplateView, DetailView
|
from django.views.generic import ListView, TemplateView, DetailView
|
||||||
from camps.mixins import CampViewMixin
|
from camps.mixins import CampViewMixin
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
from django.http import Http404
|
||||||
|
import datetime
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class SpeakerDetailView(CampViewMixin, DetailView):
|
class SpeakerDetailView(CampViewMixin, DetailView):
|
||||||
|
@ -29,6 +30,47 @@ class ProgramOverviewView(CampViewMixin, ListView):
|
||||||
|
|
||||||
class ProgramDayView(CampViewMixin, TemplateView):
|
class ProgramDayView(CampViewMixin, TemplateView):
|
||||||
template_name = 'program_day.html'
|
template_name = 'program_day.html'
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
""" If an event type has been supplied check if it is valid """
|
||||||
|
if 'type' in self.request.GET:
|
||||||
|
try:
|
||||||
|
eventtype = EventType.objects.get(
|
||||||
|
slug=self.request.GET['type']
|
||||||
|
)
|
||||||
|
except EventType.DoesNotExist:
|
||||||
|
raise Http404
|
||||||
|
return super(ProgramDayView, self).dispatch(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
context = super(ProgramDayView, self).get_context_data(**kwargs)
|
||||||
|
when = datetime.datetime(year=int(self.kwargs['year']), month=int(self.kwargs['month']), day=int(self.kwargs['day']))
|
||||||
|
eventinstances = models.EventInstance.objects.filter(event__in=self.camp.events.all())
|
||||||
|
skip = []
|
||||||
|
for ei in eventinstances:
|
||||||
|
if ei.schedule_date != when.date():
|
||||||
|
print "skipping ei %s (wrong date %s vs %s)" % (ei, ei.schedule_date, when.date())
|
||||||
|
skip.append(ei.id)
|
||||||
|
else:
|
||||||
|
if 'type' in self.request.GET:
|
||||||
|
eventtype = EventType.objects.get(
|
||||||
|
slug=self.request.GET['type']
|
||||||
|
)
|
||||||
|
if ei.event.event_type != eventtype:
|
||||||
|
print "skipping ei %s (wrong type)" % ei
|
||||||
|
skip.append(ei.id)
|
||||||
|
print "skipping %s" % skip
|
||||||
|
context['eventinstances'] = eventinstances.exclude(id__in=skip).order_by('event__event_type')
|
||||||
|
|
||||||
|
start = when + datetime.timedelta(hours=settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS)
|
||||||
|
timeslots = []
|
||||||
|
# calculate how many timeslots we have in the schedule based on the lenght of the timeslots in minutes,
|
||||||
|
# and the number of minutes in 24 hours
|
||||||
|
for i in range(0,(24*60)/settings.SCHEDULE_TIMESLOT_LENGTH_MINUTES):
|
||||||
|
timeslot = start + datetime.timedelta(minutes=i*settings.SCHEDULE_TIMESLOT_LENGTH_MINUTES)
|
||||||
|
timeslots.append(timeslot)
|
||||||
|
context['timeslots'] = timeslots
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class EventDetailView(CampViewMixin, DetailView):
|
class EventDetailView(CampViewMixin, DetailView):
|
||||||
|
@ -36,3 +78,8 @@ class EventDetailView(CampViewMixin, DetailView):
|
||||||
template_name = 'program_event_detail.html'
|
template_name = 'program_event_detail.html'
|
||||||
|
|
||||||
|
|
||||||
|
class CallForSpeakersView(CampViewMixin, TemplateView):
|
||||||
|
def get_template_names(self):
|
||||||
|
return 'call_for_speakers_%s.html' % self.get_object().slug
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -105,13 +105,8 @@ footer {
|
||||||
}
|
}
|
||||||
|
|
||||||
.event {
|
.event {
|
||||||
max-width: 200px;
|
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 150px;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 5px 5px;
|
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.event:hover {
|
.event:hover {
|
||||||
|
@ -119,3 +114,4 @@ footer {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue