new content submission flow monster commit of doom. fixes a large part of #191. Split out /program/ urls into a seperate program/urls.py file in the program: URL namespace. Change call for speakers to call for participation everywhere (I think). Add boolean fields call_for_participation_open and call_for_sponsors_open to Camp model. Switch to font-awesome 5.0.13 and update <i> tags everywhere accordingly. Introduce Tracks so all Events belong to a Track, which in turn belongs to a Camp. Add seperate forms for submitting SpeakerProposals and EventProposals so we can set labels and help_text according to EventType, and remove fields we dont need. Remove Pictures from Speaker and SpeakerProposals, it was almost never used, and was a lot of code/complexity. Remove a few PROPOSAL_STATUS namely DRAFT and MODIFIED_AFTER_APPROVAL to simplify the workflow for submitters. Add description, icon and host_title fields to EventType. Add a CombinedProposalSubmitView which allows users to submit a SpeakerProposal and EventProposal from the same page, introducing a new requirements.txt dependency for django-betterforms==1.1.4. Update bootstrap-devsite to match the new reality.
This commit is contained in:
parent
12563c890d
commit
039af44a92
|
@ -51,6 +51,7 @@ INSTALLED_APPS = [
|
|||
'allauth.account',
|
||||
'bootstrap3',
|
||||
'django_extensions',
|
||||
'betterforms',
|
||||
]
|
||||
|
||||
#MEDIA_URL = '/media/'
|
||||
|
|
|
@ -135,126 +135,8 @@ urlpatterns = [
|
|||
),
|
||||
|
||||
url(
|
||||
r'^program/', include([
|
||||
url(
|
||||
r'^$',
|
||||
ScheduleView.as_view(),
|
||||
name='schedule_index'
|
||||
),
|
||||
url(
|
||||
r'^noscript/$',
|
||||
NoScriptScheduleView.as_view(),
|
||||
name='noscript_schedule_index'
|
||||
),
|
||||
url(
|
||||
r'^ics/', ICSView.as_view(), name="ics_view"
|
||||
),
|
||||
url(
|
||||
r'^control/', ProgramControlCenter.as_view(), name="program_control_center"
|
||||
),
|
||||
url(
|
||||
r'^proposals/', include([
|
||||
url(
|
||||
r'^$',
|
||||
ProposalListView.as_view(),
|
||||
name='proposal_list',
|
||||
),
|
||||
url(
|
||||
r'^speakers/', include([
|
||||
url(
|
||||
r'^create/$',
|
||||
SpeakerProposalCreateView.as_view(),
|
||||
name='speakerproposal_create'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>[a-f0-9-]+)/$',
|
||||
SpeakerProposalDetailView.as_view(),
|
||||
name='speakerproposal_detail'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>[a-f0-9-]+)/edit/$',
|
||||
SpeakerProposalUpdateView.as_view(),
|
||||
name='speakerproposal_update'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>[a-f0-9-]+)/submit/$',
|
||||
SpeakerProposalSubmitView.as_view(),
|
||||
name='speakerproposal_submit'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>[a-f0-9-]+)/pictures/(?P<picture>[-_\w+]+)/$',
|
||||
SpeakerProposalPictureView.as_view(),
|
||||
name='speakerproposal_picture',
|
||||
),
|
||||
])
|
||||
),
|
||||
url(
|
||||
r'^events/', include([
|
||||
url(
|
||||
r'^create/$',
|
||||
EventProposalCreateView.as_view(),
|
||||
name='eventproposal_create'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>[a-f0-9-]+)/$',
|
||||
EventProposalDetailView.as_view(),
|
||||
name='eventproposal_detail'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>[a-f0-9-]+)/edit/$',
|
||||
EventProposalUpdateView.as_view(),
|
||||
name='eventproposal_update'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>[a-f0-9-]+)/submit/$',
|
||||
EventProposalSubmitView.as_view(),
|
||||
name='eventproposal_submit'
|
||||
),
|
||||
])
|
||||
),
|
||||
])
|
||||
),
|
||||
url(
|
||||
r'^speakers/', include([
|
||||
url(
|
||||
r'^$',
|
||||
SpeakerListView.as_view(),
|
||||
name='speaker_index'
|
||||
),
|
||||
url(
|
||||
r'^(?P<slug>[-_\w+]+)/$',
|
||||
SpeakerDetailView.as_view(),
|
||||
name='speaker_detail'
|
||||
),
|
||||
url(
|
||||
r'^(?P<slug>[-_\w+]+)/pictures/(?P<picture>[-_\w+]+)/$',
|
||||
SpeakerPictureView.as_view(),
|
||||
name='speaker_picture',
|
||||
),
|
||||
]),
|
||||
),
|
||||
url(
|
||||
r'^events/$',
|
||||
EventListView.as_view(),
|
||||
name='event_index'
|
||||
),
|
||||
url(
|
||||
r'^call-for-speakers/$',
|
||||
CallForSpeakersView.as_view(),
|
||||
name='call_for_speakers'
|
||||
),
|
||||
url(
|
||||
r'^calendar/',
|
||||
ICSView.as_view(),
|
||||
name='ics_calendar'
|
||||
),
|
||||
# this has to be the last URL here
|
||||
url(
|
||||
r'^(?P<slug>[-_\w+]+)/$',
|
||||
EventDetailView.as_view(),
|
||||
name='event_detail'
|
||||
),
|
||||
])
|
||||
r'^program/',
|
||||
include('program.urls', namespace='program'),
|
||||
),
|
||||
|
||||
url(
|
||||
|
|
|
@ -30,7 +30,7 @@ class Command(BaseCommand):
|
|||
files = [
|
||||
'sponsors/templates/{camp_slug}_sponsors.html',
|
||||
'camps/templates/{camp_slug}_camp_detail.html',
|
||||
'program/templates/{camp_slug}_call_for_speakers.html'
|
||||
'program/templates/{camp_slug}_call_for_participation.html'
|
||||
]
|
||||
|
||||
# directories to create, relative to DJANGO_BASE_PATH
|
||||
|
@ -68,3 +68,4 @@ class Command(BaseCommand):
|
|||
'static_src/img/{camp_slug}/logo/{camp_slug}-logo-small.png'.format(camp_slug=camp_slug)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
23
src/camps/migrations/0026_auto_20180506_1633.py
Normal file
23
src/camps/migrations/0026_auto_20180506_1633.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.0.4 on 2018-05-06 14:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('camps', '0025_auto_20180318_1250'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='camp',
|
||||
name='call_for_participation_open',
|
||||
field=models.BooleanField(default=False, help_text='Check if the Call for Participation is open for this camp'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='camp',
|
||||
name='call_for_sponsors_open',
|
||||
field=models.BooleanField(default=False, help_text='Check if the Call for Sponsors is open for this camp'),
|
||||
),
|
||||
]
|
|
@ -65,6 +65,16 @@ class Camp(CreatedUpdatedModel, UUIDModel):
|
|||
max_length=7
|
||||
)
|
||||
|
||||
call_for_participation_open = models.BooleanField(
|
||||
help_text='Check if the Call for Participation is open for this camp',
|
||||
default=False,
|
||||
)
|
||||
|
||||
call_for_sponsors_open = models.BooleanField(
|
||||
help_text='Check if the Call for Sponsors is open for this camp',
|
||||
default=False,
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('camp_detail', kwargs={'camp_slug': self.slug})
|
||||
|
||||
|
@ -91,7 +101,7 @@ class Camp(CreatedUpdatedModel, UUIDModel):
|
|||
|
||||
@property
|
||||
def event_types(self):
|
||||
# return all event types with at least one event in this camp
|
||||
""" 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
|
||||
|
@ -179,18 +189,3 @@ class Camp(CreatedUpdatedModel, UUIDModel):
|
|||
'''
|
||||
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
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
{% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5149.JPG' 'Danish politicians debating at BornHack 2016' %}
|
||||
</div>
|
||||
<div class="col-md-9 col-sm-9 text-container">
|
||||
<div class="lead">We want to encourage <strong>hackers, makers, politicians, activists, developers, artists, sysadmins, engineers</strong> with something to say to read our <a href="{% url 'call_for_speakers' camp_slug=camp.slug %}">call for speakers</a>.</div>
|
||||
<div class="lead">We want to encourage <strong>hackers, makers, politicians, activists, developers, artists, sysadmins, engineers</strong> with something to say to read our <a href="{% url 'program:call_for_participation' camp_slug=camp.slug %}">call for participation</a>.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
{% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5149.JPG' 'Danish politicians debating at BornHack 2016' %}
|
||||
</div>
|
||||
<div class="col-md-9 col-sm-9 text-container">
|
||||
<div class="lead">We want to encourage <strong>hackers, makers, politicians, activists, developers, artists, sysadmins, engineers</strong> with something to say to read our <a href="{% url 'call_for_speakers' camp_slug=camp.slug %}">call for speakers</a>.</div>
|
||||
<div class="lead">We want to encourage <strong>hackers, makers, politicians, activists, developers, artists, sysadmins, engineers</strong> with something to say to read our <a href="{% url 'program:call_for_participation' camp_slug=camp.slug %}">call for participation</a>.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
{% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5149.JPG' 'Danish politicians debating at BornHack 2016' %}
|
||||
</div>
|
||||
<div class="col-md-9 col-sm-9 text-container">
|
||||
<div class="lead">We want to encourage <strong>hackers, makers, politicians, activists, developers, artists, sysadmins, engineers</strong> with something to say to read our <a href="{% url 'call_for_speakers' camp_slug=camp.slug %}">call for speakers</a>.</div>
|
||||
<div class="lead">We want to encourage <strong>hackers, makers, politicians, activists, developers, artists, sysadmins, engineers</strong> with something to say to read our <a href="{% url 'program:call_for_participation' camp_slug=camp.slug %}">call for participation</a>.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ def handle_team_event(eventtype, irc_message=None, irc_timeout=60, email_templat
|
|||
The type of event determines which teams receive notifications.
|
||||
TODO: Add some sort of priority to messages
|
||||
"""
|
||||
logger.info("Inside handle_team_event, eventtype %s" % eventtype)
|
||||
#logger.info("Inside handle_team_event, eventtype %s" % eventtype)
|
||||
|
||||
# get event type from database
|
||||
from .models import Type
|
||||
|
|
|
@ -22,5 +22,5 @@
|
|||
<td>{{ profile.nickserv_username|default:"N/A" }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a href="{% url 'profiles:update' %}" class="btn btn-black"><i class="fa fa-edit"></i> Edit Profile</a>
|
||||
<a href="{% url 'profiles:update' %}" class="btn btn-black"><i class="fas fa-edit"></i> Edit Profile</a>
|
||||
{% endblock profile_content %}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
<button type="submit" class="btn btn-black"><i class="fa fa-save"></i> Submit</button>
|
||||
<a href="{% url 'profiles:detail' %}" class="btn btn-black"><i class="fa fa-remove"></i> Cancel</a>
|
||||
<button type="submit" class="btn btn-black"><i class="fas fa-save"></i> Submit</button>
|
||||
<a href="{% url 'profiles:detail' %}" class="btn btn-black"><i class="fas fa-times"></i> Cancel</a>
|
||||
</form>
|
||||
{% endblock profile_content %}
|
||||
|
|
|
@ -11,6 +11,7 @@ from .models import (
|
|||
EventType,
|
||||
EventInstance,
|
||||
EventLocation,
|
||||
EventTrack,
|
||||
SpeakerProposal,
|
||||
EventProposal,
|
||||
Favorite
|
||||
|
@ -47,7 +48,7 @@ class EventProposalAdmin(admin.ModelAdmin):
|
|||
mark_eventproposal_as_approved.description = 'Approve and create Event object(s)'
|
||||
|
||||
actions = ['mark_eventproposal_as_approved']
|
||||
list_filter = ('camp', 'proposal_status', 'user')
|
||||
list_filter = ('track', 'proposal_status', 'user')
|
||||
|
||||
|
||||
@admin.register(EventLocation)
|
||||
|
@ -56,6 +57,11 @@ class EventLocationAdmin(admin.ModelAdmin):
|
|||
list_display = ('name', 'camp')
|
||||
|
||||
|
||||
@admin.register(EventTrack)
|
||||
class EventTrackAdmin(admin.ModelAdmin):
|
||||
list_filter = ('camp',)
|
||||
list_display = ('name', 'camp')
|
||||
|
||||
@admin.register(EventInstance)
|
||||
class EventInstanceAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
@ -82,7 +88,7 @@ class SpeakerInline(admin.StackedInline):
|
|||
|
||||
@admin.register(Event)
|
||||
class EventAdmin(admin.ModelAdmin):
|
||||
list_filter = ('camp', 'speakers')
|
||||
list_filter = ('track', 'speakers')
|
||||
list_display = [
|
||||
'title',
|
||||
'event_type',
|
||||
|
@ -91,3 +97,4 @@ class EventAdmin(admin.ModelAdmin):
|
|||
inlines = [
|
||||
SpeakerInline
|
||||
]
|
||||
|
||||
|
|
267
src/program/forms.py
Normal file
267
src/program/forms.py
Normal file
|
@ -0,0 +1,267 @@
|
|||
from django import forms
|
||||
from betterforms.multiform import MultiModelForm
|
||||
from collections import OrderedDict
|
||||
from .models import SpeakerProposal, EventProposal, EventTrack
|
||||
from django.forms.widgets import TextInput
|
||||
from django.utils.dateparse import parse_duration
|
||||
import logging
|
||||
logger = logging.getLogger("bornhack.%s" % __name__)
|
||||
|
||||
|
||||
class BaseSpeakerProposalForm(forms.ModelForm):
|
||||
"""
|
||||
The BaseSpeakerProposalForm is not used directly.
|
||||
It is subclassed for each eventtype, where fields are removed or get new labels and help_text as needed
|
||||
"""
|
||||
class Meta:
|
||||
model = SpeakerProposal
|
||||
fields = ['name', 'biography', 'needs_oneday_ticket', 'submission_notes']
|
||||
|
||||
|
||||
class BaseEventProposalForm(forms.ModelForm):
|
||||
"""
|
||||
The BaseEventProposalForm is not used directly.
|
||||
It is subclassed for each eventtype, where fields are removed or get new labels and help_text as needed
|
||||
"""
|
||||
class Meta:
|
||||
model = EventProposal
|
||||
fields = ['title', 'abstract', 'allow_video_recording', 'duration', 'submission_notes', 'track']
|
||||
|
||||
def clean_duration(self):
|
||||
duration = self.cleaned_data['duration']
|
||||
if duration < 60 or duration > 180:
|
||||
raise forms.ValidationError("Please keep duration between 60 and 180 minutes.")
|
||||
return duration
|
||||
|
||||
def clean_track(self):
|
||||
track = self.cleaned_data['track']
|
||||
# TODO: make sure the track is part of the current camp, needs camp as form kwarg to verify
|
||||
return track
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# disable the empty_label for the track select box
|
||||
self.fields['track'].empty_label = None
|
||||
|
||||
|
||||
################################ EventType "Talk" ################################################
|
||||
|
||||
|
||||
class TalkEventProposalForm(BaseEventProposalForm):
|
||||
"""
|
||||
EventProposalForm with field names and help_text adapted to talk submissions
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# fix label and help_text for the title field
|
||||
self.fields['title'].label = 'Title of Talk'
|
||||
self.fields['title'].help_text = 'The title of this talk/presentation.'
|
||||
|
||||
# fix label and help_text for the abstract field
|
||||
self.fields['abstract'].label = 'Abstract of Talk'
|
||||
self.fields['abstract'].help_text = 'The description/abstract of this talk/presentation. Explain what the audience will experience.'
|
||||
|
||||
# fix label and help_text for the submission_notes field
|
||||
self.fields['submission_notes'].label = 'Talk Notes'
|
||||
self.fields['submission_notes'].help_text = 'Private notes regarding this talk. Only visible to yourself and the BornHack organisers.'
|
||||
|
||||
# no duration for talks
|
||||
del(self.fields['duration'])
|
||||
|
||||
|
||||
class TalkSpeakerProposalForm(BaseSpeakerProposalForm):
|
||||
"""
|
||||
SpeakerProposalForm with field labels and help_text adapted for talk submissions
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# fix label and help_text for the name field
|
||||
self.fields['name'].label = 'Speaker Name'
|
||||
self.fields['name'].help_text = 'The name of the speaker. Can be a real name or an alias.'
|
||||
|
||||
# fix label and help_text for the biograpy field
|
||||
self.fields['biography'].label = 'Speaker Biography'
|
||||
self.fields['biography'].help_text = 'The biography of the speaker.'
|
||||
|
||||
# fix label and help_text for the submission_notes field
|
||||
self.fields['submission_notes'].label = 'Speaker Notes'
|
||||
self.fields['submission_notes'].help_text = 'Private notes regarding this speaker. Only visible to yourself and the BornHack organisers.'
|
||||
|
||||
|
||||
################################ EventType "Lightning Talk" ################################################
|
||||
|
||||
|
||||
class LightningTalkEventProposalForm(TalkEventProposalForm):
|
||||
"""
|
||||
LightningTalkEventProposalForm is identical to TalkEventProposalForm for now. Keeping the class here for easy customisation later.
|
||||
"""
|
||||
pass
|
||||
|
||||
class LightningTalkSpeakerProposalForm(TalkSpeakerProposalForm):
|
||||
"""
|
||||
LightningTalkSpeakerProposalForm is identical to TalkSpeakerProposalForm except for no free tickets
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# no free tickets for lightning talks
|
||||
del(self.fields['needs_oneday_ticket'])
|
||||
|
||||
|
||||
################################ EventType "Workshop" ################################################
|
||||
|
||||
|
||||
class WorkshopEventProposalForm(BaseEventProposalForm):
|
||||
"""
|
||||
EventProposalForm with field names and help_text adapted for workshop submissions
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# fix label and help_text for the title field
|
||||
self.fields['title'].label = 'Workshop Title'
|
||||
self.fields['title'].help_text = 'The title of this workshop.'
|
||||
|
||||
# fix label and help_text for the submission_notes field
|
||||
self.fields['submission_notes'].label = 'Workshop Notes'
|
||||
self.fields['submission_notes'].help_text = 'Private notes regarding this workshop. Only visible to yourself and the BornHack organisers.'
|
||||
|
||||
# fix label and help_text for the abstract field
|
||||
self.fields['abstract'].label = 'Workshop Abstract'
|
||||
self.fields['abstract'].help_text = 'The description/abstract of this workshop. Explain what the participants will learn.'
|
||||
|
||||
# no video recording for workshops
|
||||
del(self.fields['allow_video_recording'])
|
||||
|
||||
# duration field
|
||||
self.fields['duration'].label = 'Workshop Duration'
|
||||
self.fields['duration'].help_text = 'How much time (in minutes) should we set aside for this workshop? Please keep it between 60 and 180 minutes (1-3 hours).'
|
||||
|
||||
class WorkshopSpeakerProposalForm(BaseSpeakerProposalForm):
|
||||
"""
|
||||
SpeakerProposalForm with field labels and help_text adapted for workshop submissions
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# fix label and help_text for the name field
|
||||
self.fields['name'].label = 'Host Name'
|
||||
self.fields['name'].help_text = 'The name of the workshop host. Can be a real name or an alias.'
|
||||
|
||||
# fix label and help_text for the biograpy field
|
||||
self.fields['biography'].label = 'Host Biography'
|
||||
self.fields['biography'].help_text = 'The biography of the host.'
|
||||
|
||||
# fix label and help_text for the submission_notes field
|
||||
self.fields['submission_notes'].label = 'Host Notes'
|
||||
self.fields['submission_notes'].help_text = 'Private notes regarding this host. Only visible to yourself and the BornHack organisers.'
|
||||
|
||||
# no free tickets for workshops
|
||||
del(self.fields['needs_oneday_ticket'])
|
||||
|
||||
|
||||
################################ EventType "Music" ################################################
|
||||
|
||||
|
||||
class MusicEventProposalForm(BaseEventProposalForm):
|
||||
"""
|
||||
EventProposalForm with field names and help_text adapted to music submissions
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# fix label and help_text for the title field
|
||||
self.fields['title'].label = 'Title of music act'
|
||||
self.fields['title'].help_text = 'The title of this music act/concert/set.'
|
||||
|
||||
# fix label and help_text for the submission_notes field
|
||||
self.fields['submission_notes'].label = 'Music Act Notes'
|
||||
self.fields['submission_notes'].help_text = 'Private notes regarding this music act. Only visible to yourself and the BornHack organisers.'
|
||||
|
||||
# no video recording for music acts
|
||||
del(self.fields['allow_video_recording'])
|
||||
|
||||
# no abstract for music acts
|
||||
del(self.fields['abstract'])
|
||||
|
||||
# better placeholder text for duration field
|
||||
self.fields['duration'].widget.attrs['placeholder'] = 'Duration (minutes)'
|
||||
|
||||
|
||||
class MusicSpeakerProposalForm(BaseSpeakerProposalForm):
|
||||
"""
|
||||
SpeakerProposalForm with field labels and help_text adapted for music submissions
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# fix label and help_text for the name field
|
||||
self.fields['name'].label = 'Artist Name'
|
||||
self.fields['name'].help_text = 'The name of the artist. Can be a real name or artist alias.'
|
||||
|
||||
# fix label and help_text for the biograpy field
|
||||
self.fields['biography'].label = 'Artist Description'
|
||||
self.fields['biography'].help_text = 'The description of the artist.'
|
||||
|
||||
# fix label and help_text for the submission_notes field
|
||||
self.fields['submission_notes'].label = 'Artist Notes'
|
||||
self.fields['submission_notes'].help_text = 'Private notes regarding this artist. Only visible to yourself and the BornHack organisers.'
|
||||
|
||||
# no oneday tickets for music acts
|
||||
del(self.fields['needs_oneday_ticket'])
|
||||
|
||||
|
||||
################################ EventType "Slacking Off" ################################################
|
||||
|
||||
|
||||
class SlackEventProposalForm(BaseEventProposalForm):
|
||||
"""
|
||||
EventProposalForm with field names and help_text adapted to slacking off submissions
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# fix label and help_text for the title field
|
||||
self.fields['title'].label = 'Event Title'
|
||||
self.fields['title'].help_text = 'The title of this recreational event'
|
||||
|
||||
# fix label and help_text for the abstract field
|
||||
self.fields['abstract'].label = 'Event Abstract'
|
||||
self.fields['abstract'].help_text = 'The description/abstract of this recreational event.'
|
||||
|
||||
# fix label and help_text for the submission_notes field
|
||||
self.fields['submission_notes'].label = 'Event Notes'
|
||||
self.fields['submission_notes'].help_text = 'Private notes regarding this recreational event. Only visible to yourself and the BornHack organisers.'
|
||||
|
||||
# no video recording for music acts
|
||||
del(self.fields['allow_video_recording'])
|
||||
|
||||
# better placeholder text for duration field
|
||||
self.fields['duration'].label = 'Event Duration'
|
||||
self.fields['duration'].widget.attrs['placeholder'] = 'Duration (minutes)'
|
||||
|
||||
|
||||
class SlackSpeakerProposalForm(BaseSpeakerProposalForm):
|
||||
"""
|
||||
SpeakerProposalForm with field labels and help_text adapted for recreational events
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# fix label and help_text for the name field
|
||||
self.fields['name'].label = 'Host Name'
|
||||
self.fields['name'].help_text = 'The name of the event host. Can be a real name or an alias.'
|
||||
|
||||
# fix label and help_text for the biograpy field
|
||||
self.fields['biography'].label = 'Host Biography'
|
||||
self.fields['biography'].help_text = 'The biography of the host.'
|
||||
|
||||
# fix label and help_text for the submission_notes field
|
||||
self.fields['submission_notes'].label = 'Host Notes'
|
||||
self.fields['submission_notes'].help_text = 'Private notes regarding this host. Only visible to yourself and the BornHack organisers.'
|
||||
|
||||
# no oneday tickets for music acts
|
||||
del(self.fields['needs_oneday_ticket'])
|
||||
|
134
src/program/migrations/0048_auto_20180512_1625.py
Normal file
134
src/program/migrations/0048_auto_20180512_1625.py
Normal file
|
@ -0,0 +1,134 @@
|
|||
# Generated by Django 2.0.4 on 2018-05-12 14:25
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('camps', '0026_auto_20180506_1633'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('program', '0047_auto_20180415_1159'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EventTrack',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('slug', models.SlugField()),
|
||||
('camp', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='eventtracks', to='camps.Camp')),
|
||||
('managers', models.ManyToManyField(related_name='managed_tracks', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='speaker',
|
||||
name='picture_large',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='speaker',
|
||||
name='picture_small',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='speakerproposal',
|
||||
name='picture_large',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='speakerproposal',
|
||||
name='picture_small',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventproposal',
|
||||
name='duration',
|
||||
field=models.IntegerField(blank=True, default=None, help_text='How much time (in minutes) should we set aside for this act? Please keep it between 60 and 180 minutes (1-3 hours).', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventtype',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, default='', help_text='The description of this type of event. Used in content submission flow.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventtype',
|
||||
name='icon',
|
||||
field=models.CharField(default='wrench', help_text="Name of the fontawesome icon to use, without the 'fa-' part", max_length=25),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventtype',
|
||||
name='oneday_ticket_possible',
|
||||
field=models.BooleanField(default=False, help_text='Check if hosting an event of this type qualifies someone for a free oneday ticket'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='speaker',
|
||||
name='needs_oneday_ticket',
|
||||
field=models.BooleanField(default=False, help_text='Check if BornHack needs to provide a free one-day ticket for this speaker'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='speakerproposal',
|
||||
name='needs_oneday_ticket',
|
||||
field=models.BooleanField(default=False, help_text='Check if BornHack needs to provide a free one-day ticket for this speaker'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventlocation',
|
||||
name='icon',
|
||||
field=models.CharField(help_text="Name of the fontawesome icon to use without the 'fa-' part", max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventproposal',
|
||||
name='abstract',
|
||||
field=models.TextField(blank=True, help_text='The abstract for this event. Describe what the audience can expect to see/hear.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventproposal',
|
||||
name='proposal_status',
|
||||
field=models.CharField(choices=[('pending', 'Pending approval'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='pending', max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventproposal',
|
||||
name='speakers',
|
||||
field=models.ManyToManyField(blank=True, help_text='Pick the speaker(s) for this event. If you cannot see anything here you need to go back and create Speaker Proposal(s) first.', related_name='eventproposals', to='program.SpeakerProposal'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventproposal',
|
||||
name='title',
|
||||
field=models.CharField(help_text='The title of this event. Keep it short and memorable.', max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='speakerproposal',
|
||||
name='biography',
|
||||
field=models.TextField(help_text='Biography of the speaker/artist/host. Markdown is supported.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='speakerproposal',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Name or alias of the speaker/artist/host', max_length=150),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='speakerproposal',
|
||||
name='proposal_status',
|
||||
field=models.CharField(choices=[('pending', 'Pending approval'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='pending', max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='speakerproposal',
|
||||
name='submission_notes',
|
||||
field=models.TextField(blank=True, help_text='Private notes for this speaker/artist/host. Only visible to the submitting user and the BornHack organisers.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='track',
|
||||
field=models.ForeignKey(blank=True, help_text='The track this event belongs to', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='events', to='program.EventTrack'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventproposal',
|
||||
name='track',
|
||||
field=models.ForeignKey(blank=True, help_text='The track this event belongs to', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='eventproposals', to='program.EventTrack'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='eventtrack',
|
||||
unique_together={('camp', 'slug'), ('camp', 'name')},
|
||||
),
|
||||
]
|
30
src/program/migrations/0049_add_event_tracks.py
Normal file
30
src/program/migrations/0049_add_event_tracks.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Generated by Django 2.0.4 on 2018-05-12 14:29
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
def add_event_tracks(apps, schema_editor):
|
||||
Camp = apps.get_model('camps', 'Camp')
|
||||
EventTrack = apps.get_model('program', 'EventTrack')
|
||||
EventProposal = apps.get_model('program', 'EventProposal')
|
||||
Event = apps.get_model('program', 'Event')
|
||||
for camp in Camp.objects.all():
|
||||
# create the default track for this camp
|
||||
track = EventTrack.objects.create(
|
||||
name="BornHack",
|
||||
slug="bornhack",
|
||||
camp=camp
|
||||
)
|
||||
Event.objects.filter(camp=camp).update(track=track)
|
||||
EventProposal.objects.filter(camp=camp).update(track=track)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('program', '0048_auto_20180512_1625'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_event_tracks),
|
||||
]
|
||||
|
25
src/program/migrations/0050_auto_20180512_1650.py
Normal file
25
src/program/migrations/0050_auto_20180512_1650.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 2.0.4 on 2018-05-12 14:50
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('program', '0049_add_event_tracks'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name='event',
|
||||
unique_together={('track', 'title'), ('track', 'slug')},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='eventproposal',
|
||||
name='camp',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='event',
|
||||
name='camp',
|
||||
),
|
||||
]
|
24
src/program/migrations/0051_auto_20180512_1801.py
Normal file
24
src/program/migrations/0051_auto_20180512_1801.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 2.0.4 on 2018-05-12 16:01
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('program', '0050_auto_20180512_1650'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='track',
|
||||
field=models.ForeignKey(help_text='The track this event belongs to', on_delete=django.db.models.deletion.PROTECT, related_name='events', to='program.EventTrack'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventproposal',
|
||||
name='track',
|
||||
field=models.ForeignKey(help_text='The track this event belongs to', on_delete=django.db.models.deletion.PROTECT, related_name='eventproposals', to='program.EventTrack'),
|
||||
),
|
||||
]
|
18
src/program/migrations/0052_auto_20180519_2324.py
Normal file
18
src/program/migrations/0052_auto_20180519_2324.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.0.4 on 2018-05-19 21:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('program', '0051_auto_20180512_1801'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='eventproposal',
|
||||
name='allow_video_recording',
|
||||
field=models.BooleanField(default=False, help_text='Check if we can video record the event. Leave unchecked to avoid video recording.'),
|
||||
),
|
||||
]
|
18
src/program/migrations/0053_auto_20180519_2325.py
Normal file
18
src/program/migrations/0053_auto_20180519_2325.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.0.4 on 2018-05-19 21:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('program', '0052_auto_20180519_2324'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='eventproposal',
|
||||
name='allow_video_recording',
|
||||
field=models.BooleanField(default=False, help_text='Check to allow video recording of the event. Leave unchecked to avoid video recording.'),
|
||||
),
|
||||
]
|
22
src/program/migrations/0054_auto_20180520_1509.py
Normal file
22
src/program/migrations/0054_auto_20180520_1509.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 2.0.4 on 2018-05-20 13:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('program', '0053_auto_20180519_2325'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='eventtype',
|
||||
name='oneday_ticket_possible',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='eventtype',
|
||||
name='host_title',
|
||||
field=models.CharField(default='Person', help_text='What to call someone hosting this type of event. Like "Artist" for Music or "Speaker" for talks.', max_length=30),
|
||||
),
|
||||
]
|
|
@ -8,47 +8,36 @@ import sys
|
|||
import mimetypes
|
||||
|
||||
|
||||
class EnsureCFSOpenMixin(SingleObjectMixin):
|
||||
class EnsureCFPOpenMixin(object):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
# do not permit editing if call for speakers is not open
|
||||
if not self.camp.call_for_speakers_open:
|
||||
messages.error(request, "The Call for Speakers is not open.")
|
||||
# do not permit this action if call for participation is not open
|
||||
if not self.camp.call_for_participation_open:
|
||||
messages.error(request, "The Call for Participation is not open.")
|
||||
return redirect(
|
||||
reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
)
|
||||
|
||||
# alright, continue with the request
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class CreateProposalMixin(SingleObjectMixin):
|
||||
def form_valid(self, form):
|
||||
# set camp and user before saving
|
||||
form.instance.camp = self.camp
|
||||
form.instance.user = self.request.user
|
||||
form.save()
|
||||
return redirect(
|
||||
reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
)
|
||||
|
||||
|
||||
class EnsureUnapprovedProposalMixin(SingleObjectMixin):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
# do not permit editing if the proposal is already approved
|
||||
if self.get_object().proposal_status == models.UserSubmittedModel.PROPOSAL_APPROVED:
|
||||
messages.error(request, "This proposal has already been approved. Please contact the organisers if you need to modify something.")
|
||||
return redirect(reverse('proposal_list', kwargs={'camp_slug': self.camp.slug}))
|
||||
return redirect(reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug}))
|
||||
|
||||
# alright, continue with the request
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class EnsureWritableCampMixin(SingleObjectMixin):
|
||||
class EnsureWritableCampMixin(object):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
# do not permit view if camp is in readonly mode
|
||||
if self.camp.read_only:
|
||||
messages.error(request, "No thanks")
|
||||
return redirect(reverse('proposal_list', kwargs={'camp_slug': self.camp.slug}))
|
||||
return redirect(reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug}))
|
||||
|
||||
# alright, continue with the request
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
@ -60,47 +49,9 @@ class EnsureUserOwnsProposalMixin(SingleObjectMixin):
|
|||
if self.get_object().user.username != request.user.username:
|
||||
messages.error(request, "No thanks")
|
||||
return redirect(
|
||||
reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
)
|
||||
|
||||
# alright, continue with the request
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class PictureViewMixin(SingleObjectMixin):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
# do we have the requested picture?
|
||||
if kwargs['picture'] == 'thumbnail':
|
||||
if self.get_object().picture_small:
|
||||
self.picture = self.get_object().picture_small
|
||||
else:
|
||||
raise Http404()
|
||||
elif kwargs['picture'] == 'large':
|
||||
if self.get_object().picture_large:
|
||||
self.picture = self.get_object().picture_large
|
||||
else:
|
||||
raise Http404()
|
||||
else:
|
||||
# only 'thumbnail' and 'large' pictures supported
|
||||
raise Http404()
|
||||
|
||||
# alright, continue with the request
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_picture_response(self, path):
|
||||
if 'runserver' in sys.argv or 'runserver_plus' in sys.argv:
|
||||
# this is a local devserver situation, guess mimetype from extension and return picture directly
|
||||
response = HttpResponse(
|
||||
self.picture,
|
||||
content_type=mimetypes.types_map[".%s" % self.picture.name.split(".")[-1]]
|
||||
)
|
||||
else:
|
||||
# make nginx serve the picture using X-Accel-Redirect
|
||||
# (this works for nginx only, other webservers use x-sendfile)
|
||||
# TODO: maybe make the header name configurable
|
||||
response = HttpResponse()
|
||||
response['X-Accel-Redirect'] = path
|
||||
response['Content-Type'] = ''
|
||||
return response
|
||||
|
||||
|
||||
|
|
|
@ -4,8 +4,7 @@ import icalendar
|
|||
import logging
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django.contrib.postgres.fields import DateTimeRangeField
|
||||
from django.contrib.postgres.fields import DateTimeRangeField, ArrayField
|
||||
from django.contrib import messages
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
|
@ -21,43 +20,6 @@ from utils.models import CreatedUpdatedModel, CampRelatedModel
|
|||
logger = logging.getLogger("bornhack.%s" % __name__)
|
||||
|
||||
|
||||
class CustomUrlStorage(FileSystemStorage):
|
||||
def __init__(self, location=None):
|
||||
super(CustomUrlStorage, self).__init__(location)
|
||||
|
||||
def url(self, name):
|
||||
url = super(CustomUrlStorage, self).url(name)
|
||||
parts = url.split("/")
|
||||
if parts[0] != "public":
|
||||
# first bit should always be "public"
|
||||
return False
|
||||
|
||||
if parts[1] == "speakerproposals":
|
||||
# find speakerproposal
|
||||
speakerproposal_model = apps.get_model('program', 'speakerproposal')
|
||||
try:
|
||||
speakerproposal = speakerproposal_model.objects.get(picture_small=name)
|
||||
picture = "small"
|
||||
except speakerproposal_model.DoesNotExist:
|
||||
try:
|
||||
speakerproposal = speakerproposal_model.objects.get(picture_large=name)
|
||||
picture = "large"
|
||||
except speakerproposal_model.DoesNotExist:
|
||||
return False
|
||||
url = reverse('speakerproposal_picture', kwargs={
|
||||
'camp_slug': speakerproposal.camp.slug,
|
||||
'pk': speakerproposal.pk,
|
||||
'picture': picture,
|
||||
})
|
||||
else:
|
||||
return False
|
||||
|
||||
return url
|
||||
|
||||
|
||||
storage = CustomUrlStorage()
|
||||
|
||||
|
||||
class UserSubmittedModel(CampRelatedModel):
|
||||
"""
|
||||
An abstract model containing the stuff that is shared
|
||||
|
@ -78,72 +40,48 @@ class UserSubmittedModel(CampRelatedModel):
|
|||
on_delete=models.PROTECT
|
||||
)
|
||||
|
||||
PROPOSAL_DRAFT = 'draft'
|
||||
PROPOSAL_PENDING = 'pending'
|
||||
PROPOSAL_APPROVED = 'approved'
|
||||
PROPOSAL_REJECTED = 'rejected'
|
||||
PROPOSAL_MODIFIED_AFTER_APPROVAL = 'modified after approval'
|
||||
|
||||
PROPOSAL_STATUSES = [
|
||||
PROPOSAL_DRAFT,
|
||||
PROPOSAL_PENDING,
|
||||
PROPOSAL_APPROVED,
|
||||
PROPOSAL_REJECTED,
|
||||
PROPOSAL_MODIFIED_AFTER_APPROVAL
|
||||
]
|
||||
|
||||
PROPOSAL_STATUS_CHOICES = [
|
||||
(PROPOSAL_DRAFT, 'Draft'),
|
||||
(PROPOSAL_PENDING, 'Pending approval'),
|
||||
(PROPOSAL_APPROVED, 'Approved'),
|
||||
(PROPOSAL_REJECTED, 'Rejected'),
|
||||
(PROPOSAL_MODIFIED_AFTER_APPROVAL, 'Modified after approval'),
|
||||
]
|
||||
|
||||
proposal_status = models.CharField(
|
||||
max_length=50,
|
||||
choices=PROPOSAL_STATUS_CHOICES,
|
||||
default=PROPOSAL_DRAFT,
|
||||
default=PROPOSAL_PENDING,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return '%s (submitted by: %s, status: %s)' % (self.headline, self.user, self.proposal_status)
|
||||
|
||||
def save(self, **kwargs):
|
||||
if not self.camp.call_for_speakers_open:
|
||||
message = 'Call for speakers is not open'
|
||||
if not self.camp.call_for_participation_open:
|
||||
message = 'Call for participation is not open'
|
||||
if hasattr(self, 'request'):
|
||||
messages.error(self.request, message)
|
||||
raise ValidationError(message)
|
||||
super().save(**kwargs)
|
||||
|
||||
def delete(self, **kwargs):
|
||||
if not self.camp.call_for_speakers_open:
|
||||
message = 'Call for speakers is not open'
|
||||
if not self.camp.call_for_participation_open:
|
||||
message = 'Call for participation is not open'
|
||||
if hasattr(self, 'request'):
|
||||
messages.error(self.request, message)
|
||||
raise ValidationError(message)
|
||||
super().delete(**kwargs)
|
||||
|
||||
|
||||
def get_speakerproposal_picture_upload_path(instance, filename):
|
||||
""" We want speakerproposal pictures saved as MEDIA_ROOT/public/speakerproposals/camp-slug/proposal-uuid/filename """
|
||||
return 'public/speakerproposals/%(campslug)s/%(proposaluuid)s/%(filename)s' % {
|
||||
'campslug': instance.camp.slug,
|
||||
'proposaluuid': instance.uuid,
|
||||
'filename': filename
|
||||
}
|
||||
|
||||
|
||||
def get_speakersubmission_picture_upload_path(instance, filename):
|
||||
""" We want speakerproposal pictures saved as MEDIA_ROOT/public/speakerproposals/camp-slug/proposal-uuid/filename """
|
||||
return 'public/speakerproposals/%(campslug)s/%(proposaluuid)s/%(filename)s' % {
|
||||
'campslug': instance.camp.slug,
|
||||
'proposaluuidd': instance.uuid,
|
||||
'filename': filename
|
||||
}
|
||||
|
||||
|
||||
class SpeakerProposal(UserSubmittedModel):
|
||||
""" A speaker proposal """
|
||||
|
||||
|
@ -155,42 +93,29 @@ class SpeakerProposal(UserSubmittedModel):
|
|||
|
||||
name = models.CharField(
|
||||
max_length=150,
|
||||
help_text='Name or alias of the speaker',
|
||||
help_text='Name or alias of the speaker/artist/host',
|
||||
)
|
||||
|
||||
biography = models.TextField(
|
||||
help_text='Markdown is supported.'
|
||||
)
|
||||
|
||||
picture_large = models.ImageField(
|
||||
null=True,
|
||||
blank=True,
|
||||
upload_to=get_speakerproposal_picture_upload_path,
|
||||
help_text='A picture of the speaker',
|
||||
storage=storage,
|
||||
max_length=255
|
||||
)
|
||||
|
||||
picture_small = models.ImageField(
|
||||
null=True,
|
||||
blank=True,
|
||||
upload_to=get_speakerproposal_picture_upload_path,
|
||||
help_text='A thumbnail of the speaker picture',
|
||||
storage=storage,
|
||||
max_length=255
|
||||
help_text='Biography of the speaker/artist/host. Markdown is supported.'
|
||||
)
|
||||
|
||||
submission_notes = models.TextField(
|
||||
help_text='Private notes for this speaker. Only visible to the submitting user and the BornHack organisers.',
|
||||
help_text='Private notes for this speaker/artist/host. Only visible to the submitting user and the BornHack organisers.',
|
||||
blank=True
|
||||
)
|
||||
|
||||
needs_oneday_ticket = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Check if BornHack needs to provide a free one-day ticket for this speaker',
|
||||
)
|
||||
|
||||
@property
|
||||
def headline(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('speakerproposal_detail', kwargs={'camp_slug': self.camp.slug, 'pk': self.uuid})
|
||||
return reverse_lazy('program:speakerproposal_detail', kwargs={'camp_slug': self.camp.slug, 'pk': self.uuid})
|
||||
|
||||
def mark_as_approved(self):
|
||||
speakermodel = apps.get_model('program', 'speaker')
|
||||
|
@ -199,13 +124,7 @@ class SpeakerProposal(UserSubmittedModel):
|
|||
speaker.camp = self.camp
|
||||
speaker.name = self.name
|
||||
speaker.biography = self.biography
|
||||
if self.picture_small and self.picture_large:
|
||||
temp = ContentFile(self.picture_small.read())
|
||||
temp.name = os.path.basename(self.picture_small.name)
|
||||
speaker.picture_small = temp
|
||||
temp = ContentFile(self.picture_large.read())
|
||||
temp.name = os.path.basename(self.picture_large.name)
|
||||
speaker.picture_large = temp
|
||||
speaker.needs_oneday_ticket = self.needs_oneday_ticket
|
||||
speaker.proposal = self
|
||||
speaker.save()
|
||||
|
||||
|
@ -216,19 +135,21 @@ class SpeakerProposal(UserSubmittedModel):
|
|||
class EventProposal(UserSubmittedModel):
|
||||
""" An event proposal """
|
||||
|
||||
camp = models.ForeignKey(
|
||||
'camps.Camp',
|
||||
track = models.ForeignKey(
|
||||
'program.EventTrack',
|
||||
related_name='eventproposals',
|
||||
help_text='The track this event belongs to',
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
|
||||
title = models.CharField(
|
||||
max_length=255,
|
||||
help_text='The title of this event',
|
||||
help_text='The title of this event. Keep it short and memorable.',
|
||||
)
|
||||
|
||||
abstract = models.TextField(
|
||||
help_text='The abstract for this event'
|
||||
help_text='The abstract for this event. Describe what the audience can expect to see/hear.',
|
||||
blank=True,
|
||||
)
|
||||
|
||||
event_type = models.ForeignKey(
|
||||
|
@ -241,11 +162,19 @@ class EventProposal(UserSubmittedModel):
|
|||
'program.SpeakerProposal',
|
||||
blank=True,
|
||||
help_text='Pick the speaker(s) for this event. If you cannot see anything here you need to go back and create Speaker Proposal(s) first.',
|
||||
related_name='eventproposals',
|
||||
)
|
||||
|
||||
allow_video_recording = models.BooleanField(
|
||||
default=False,
|
||||
help_text='If we can video record the event or not'
|
||||
help_text='Check to allow video recording of the event. Leave unchecked to avoid video recording.'
|
||||
)
|
||||
|
||||
duration = models.IntegerField(
|
||||
default=None,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text='How much time (in minutes) should we set aside for this act? Please keep it between 60 and 180 minutes (1-3 hours).'
|
||||
)
|
||||
|
||||
submission_notes = models.TextField(
|
||||
|
@ -253,16 +182,30 @@ class EventProposal(UserSubmittedModel):
|
|||
blank=True
|
||||
)
|
||||
|
||||
@property
|
||||
def camp(self):
|
||||
return self.track.camp
|
||||
|
||||
@property
|
||||
def headline(self):
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy(
|
||||
'eventproposal_detail',
|
||||
'program:eventproposal_detail',
|
||||
kwargs={'camp_slug': self.camp.slug, 'pk': self.uuid}
|
||||
)
|
||||
|
||||
def get_available_speakerproposals(self):
|
||||
"""
|
||||
Return all SpeakerProposals submitted by the user who submitted this EventProposal,
|
||||
which are not already added to this EventProposal
|
||||
"""
|
||||
return SpeakerProposal.objects.filter(
|
||||
camp=self.track.camp,
|
||||
user=self.user
|
||||
).exclude(uuid__in=self.speakers.all().values_list('uuid'))
|
||||
|
||||
def mark_as_approved(self):
|
||||
eventmodel = apps.get_model('program', 'event')
|
||||
eventproposalmodel = apps.get_model('program', 'eventproposal')
|
||||
|
@ -285,9 +228,37 @@ class EventProposal(UserSubmittedModel):
|
|||
self.proposal_status = eventproposalmodel.PROPOSAL_APPROVED
|
||||
self.save()
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
class EventTrack(CampRelatedModel):
|
||||
""" All events belong to a track. Administration of a track can be delegated to one or more users. """
|
||||
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
)
|
||||
|
||||
slug = models.SlugField()
|
||||
|
||||
camp = models.ForeignKey(
|
||||
'camps.Camp',
|
||||
related_name='eventtracks',
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
|
||||
managers = models.ManyToManyField(
|
||||
'auth.User',
|
||||
related_name='managed_tracks',
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
unique_together = (('camp', 'slug'), ('camp', 'name'))
|
||||
|
||||
|
||||
class EventLocation(CampRelatedModel):
|
||||
""" The places where stuff happens """
|
||||
|
||||
|
@ -299,7 +270,7 @@ class EventLocation(CampRelatedModel):
|
|||
|
||||
icon = models.CharField(
|
||||
max_length=100,
|
||||
help_text="hex for the unicode character in the fontawesome icon set to use, like 'f000' for 'fa-glass'"
|
||||
help_text="Name of the fontawesome icon to use without the 'fa-' part"
|
||||
)
|
||||
|
||||
camp = models.ForeignKey(
|
||||
|
@ -332,6 +303,12 @@ class EventType(CreatedUpdatedModel):
|
|||
|
||||
slug = models.SlugField()
|
||||
|
||||
description = models.TextField(
|
||||
default='',
|
||||
help_text='The description of this type of event. Used in content submission flow.',
|
||||
blank=True,
|
||||
)
|
||||
|
||||
color = models.CharField(
|
||||
max_length=50,
|
||||
help_text='The background color of this event type',
|
||||
|
@ -342,6 +319,12 @@ class EventType(CreatedUpdatedModel):
|
|||
help_text='Check if this event type should use white text color',
|
||||
)
|
||||
|
||||
icon = models.CharField(
|
||||
max_length=25,
|
||||
help_text="Name of the fontawesome icon to use, without the 'fa-' part",
|
||||
default='wrench',
|
||||
)
|
||||
|
||||
notifications = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Check to send notifications for this event type',
|
||||
|
@ -357,6 +340,12 @@ class EventType(CreatedUpdatedModel):
|
|||
help_text='Include events of this type in the event list?',
|
||||
)
|
||||
|
||||
host_title = models.CharField(
|
||||
max_length=30,
|
||||
help_text='What to call someone hosting this type of event. Like "Artist" for Music or "Speaker" for talks.',
|
||||
default='Person',
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
@ -393,10 +382,10 @@ class Event(CampRelatedModel):
|
|||
help_text='The slug for this event, created automatically',
|
||||
)
|
||||
|
||||
camp = models.ForeignKey(
|
||||
'camps.Camp',
|
||||
track = models.ForeignKey(
|
||||
'program.EventTrack',
|
||||
related_name='events',
|
||||
help_text='The camp this event belongs to',
|
||||
help_text='The track this event belongs to',
|
||||
on_delete=models.PROTECT
|
||||
)
|
||||
|
||||
|
@ -422,7 +411,7 @@ class Event(CampRelatedModel):
|
|||
|
||||
class Meta:
|
||||
ordering = ['title']
|
||||
unique_together = (('camp', 'slug'), ('camp', 'title'))
|
||||
unique_together = (('track', 'slug'), ('track', 'title'))
|
||||
|
||||
def __str__(self):
|
||||
return '%s (%s)' % (self.title, self.camp.title)
|
||||
|
@ -432,6 +421,10 @@ class Event(CampRelatedModel):
|
|||
self.slug = slugify(self.title)
|
||||
super(Event, self).save(**kwargs)
|
||||
|
||||
@property
|
||||
def camp(self):
|
||||
return self.track.camp
|
||||
|
||||
@property
|
||||
def speakers_list(self):
|
||||
if self.speakers.exists():
|
||||
|
@ -439,7 +432,7 @@ class Event(CampRelatedModel):
|
|||
return False
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('event_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug})
|
||||
return reverse_lazy('program:event_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug})
|
||||
|
||||
def serialize(self):
|
||||
data = {
|
||||
|
@ -514,9 +507,7 @@ class EventInstance(CampRelatedModel):
|
|||
|
||||
@property
|
||||
def timeslots(self):
|
||||
"""
|
||||
Find the number of timeslots this eventinstance takes up
|
||||
"""
|
||||
""" 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
|
||||
|
@ -564,15 +555,6 @@ class EventInstance(CampRelatedModel):
|
|||
return data
|
||||
|
||||
|
||||
def get_speaker_picture_upload_path(instance, filename):
|
||||
""" We want speaker pictures are saved as MEDIA_ROOT/public/speakers/camp-slug/speaker-slug/filename """
|
||||
return 'public/speakers/%(campslug)s/%(speakerslug)s/%(filename)s' % {
|
||||
'campslug': instance.camp.slug,
|
||||
'speakerslug': instance.slug,
|
||||
'filename': filename
|
||||
}
|
||||
|
||||
|
||||
class Speaker(CampRelatedModel):
|
||||
""" A Person (co)anchoring one or more events on a camp. """
|
||||
|
||||
|
@ -585,20 +567,6 @@ class Speaker(CampRelatedModel):
|
|||
help_text='Markdown is supported.'
|
||||
)
|
||||
|
||||
picture_small = models.ImageField(
|
||||
null=True,
|
||||
blank=True,
|
||||
upload_to=get_speaker_picture_upload_path,
|
||||
help_text='A thumbnail of the speaker picture'
|
||||
)
|
||||
|
||||
picture_large = models.ImageField(
|
||||
null=True,
|
||||
blank=True,
|
||||
upload_to=get_speaker_picture_upload_path,
|
||||
help_text='A picture of the speaker'
|
||||
)
|
||||
|
||||
slug = models.SlugField(
|
||||
blank=True,
|
||||
max_length=255,
|
||||
|
@ -628,6 +596,11 @@ class Speaker(CampRelatedModel):
|
|||
on_delete=models.PROTECT
|
||||
)
|
||||
|
||||
needs_oneday_ticket = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Check if BornHack needs to provide a free one-day ticket for this speaker',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = (('camp', 'name'), ('camp', 'slug'))
|
||||
|
@ -641,16 +614,7 @@ class Speaker(CampRelatedModel):
|
|||
super(Speaker, self).save(**kwargs)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('speaker_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug})
|
||||
|
||||
def get_picture_url(self, size):
|
||||
return reverse('speaker_picture', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug, 'picture': size})
|
||||
|
||||
def get_small_picture_url(self):
|
||||
return self.get_picture_url('thumbnail')
|
||||
|
||||
def get_large_picture_url(self):
|
||||
return self.get_picture_url('large')
|
||||
return reverse_lazy('program:speaker_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug})
|
||||
|
||||
def serialize(self):
|
||||
data = {
|
||||
|
@ -658,11 +622,6 @@ class Speaker(CampRelatedModel):
|
|||
'slug': self.slug,
|
||||
'biography': self.biography,
|
||||
}
|
||||
|
||||
if self.picture_small and self.picture_large:
|
||||
data['large_picture_url'] = self.get_large_picture_url()
|
||||
data['small_picture_url'] = self.get_small_picture_url()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
@ -680,3 +639,33 @@ class Favorite(models.Model):
|
|||
class Meta:
|
||||
unique_together = ['user', 'event_instance']
|
||||
|
||||
# classes and functions below here was used by picture handling for speakers before it was removed in May 2018 by tyk
|
||||
|
||||
class CustomUrlStorage(FileSystemStorage):
|
||||
"""
|
||||
Must exist because it is mentioned in old migrations.
|
||||
Can be removed when we clean up old migrations at some point
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_speaker_picture_upload_path():
|
||||
"""
|
||||
Must exist because it is mentioned in old migrations.
|
||||
Can be removed when we clean up old migrations at some point
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_speakerproposal_picture_upload_path():
|
||||
"""
|
||||
Must exist because it is mentioned in old migrations.
|
||||
Can be removed when we clean up old migrations at some point
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_speakersubmission_picture_upload_path():
|
||||
"""
|
||||
Must exist because it is mentioned in old migrations.
|
||||
Can be removed when we clean up old migrations at some point
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
{% extends 'program_base.html' %}
|
||||
|
||||
{% block title %}
|
||||
Call for Speakers | {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block program_content %}
|
||||
|
||||
{% if not camp.call_for_participation_open %}
|
||||
<div class="alert alert-danger">
|
||||
<strong>Note!</strong> This Call for Speakers is no longer relevant. It is kept here for historic purposes.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>BornHack 2016: Call for Speakers</h2>
|
||||
|
||||
<p>BornHack 2016 is a 7 days outdoor technology tent camping festival that will take place from the 27th of August to the 3rd of September 2016 on the island of Bornholm in Denmark. It is first time that BornHack will take place and it is our goal to make BornHack a yearly recurring event with 100 to 350 participants.</p>
|
||||
|
||||
<p>We are looking for gifted, entertaining and technically enlightening speakers to host talks, lightning talks and workshops at BornHack.</p>
|
||||
|
||||
<p>Please reach out to us on speakers@bornhack.dk with a title, abstract, biography, an optional picture of yourself and whether it is a regular talk, lightning talk, workshop or something entirely different. Please ensure that all information is in English. The submitted information will be published both as a news entry and in the official event program on our website, if the submission is accepted.</p>
|
||||
|
||||
<p>We are very open to different topics. We expect that the majority of the presentation at BornHack will be on security, networking, programming, distributed systems, privacy, and how these technologies relate to society.</p>
|
||||
|
||||
<p>The ticket shop for BornHack 2016 is already open and available at <a href="{% url 'shop:index' %}">https://bornhack.dk/shop/</a> - please make sure you have also read our <a href="{% url 'conduct' %}">Code of Conduct</a>.</p>
|
||||
|
||||
<h3>Regular Talk</h3>
|
||||
|
||||
<p>Regular talks are 45 minutes of presentation, 10 minutes of questions from the audience followed by 5 minutes of preparation for setting up the next speaker.</p>
|
||||
|
||||
<p>Please bring your own laptop with your presentation on; it should have an HDMI socket and we will provide the cable to the projector. We do not guarantee that audio will work, even if your laptop supports that.</p>
|
||||
|
||||
<p>We will provide you with a one-day entrance ticket free of charge, but due to our limited funds, you would have to pay for transportation to and from the event yourself. We also encourage you to participate for the entire week, but you would also have to pay for the ticket yourself.</p>
|
||||
|
||||
<h3>Lightning Talk</h3>
|
||||
|
||||
<p>Lightning talks are 10 minutes of presentation. A laptop will be connected to the projector at the location of the presentations.</p>
|
||||
|
||||
<p>A lightning talk is an excellent opportunity for inexperienced speakers to present a topic that you find interesting.</p>
|
||||
|
||||
<p>You MUST buy yourself an entrance ticket to host a lightning talk; we are unable to offer free tickets for everyone that gives a lightning talk.</p>
|
||||
|
||||
<h3>Workshop</h3>
|
||||
|
||||
<p>We have two workshop areas that will be able to host workshops for approximately 20 people per room. Workshops can be up to 3 hours per slot and can be extended for daily workshops.</p>
|
||||
|
||||
<p>You MUST buy yourself an entrance ticket to host a workshop; we are unable to offer free tickets for everyone that hosts a workshop.</p>
|
||||
|
||||
<h2>Contact Information</h2>
|
||||
|
||||
<p>The BornHack speakers team can be contacted via speakers@bornhack.dk - for general information reach out to the info team via info@bornhack.dk</p>
|
||||
|
||||
<p>We are also reachable via IRC in #BornHack on irc.baconsvin.org or 6nbtgccn5nbcodn3.onion - both listening for TLS connections on port 6697.</p>
|
||||
|
||||
<p>For more information, please have a look at <a href="{% url 'camp_detail' camp_slug='bornhack-2016' %}">https://bornhack.dk/</a> or follow us on Twitter at <a href="https://twitter.com/bornhax">@bornhax</a>.</p>
|
||||
{% endblock %}
|
|
@ -0,0 +1,58 @@
|
|||
{% extends 'program_base.html' %}
|
||||
|
||||
{% block title %}
|
||||
Call for Speakers | {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block program_content %}
|
||||
|
||||
{% if not camp.call_for_participation_open %}
|
||||
<div class="alert alert-danger">
|
||||
<strong>Note!</strong> This Call for Speakers is no longer relevant. It is kept here for historic purposes.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>Call for Speakers</h2>
|
||||
<p>We are looking for gifted, talented, humourous, technically enlightened speakers to host talks, lightning talks, and workshops at BornHack.</p>
|
||||
|
||||
<p>We are very open to different topics. We expect that the majority of the presentation at BornHack will be on security, networking, programming, distributed systems, privacy, and how these technologies relate to society.</p>
|
||||
|
||||
<p>BornHack is trying to be an inclusive event so please make sure you have read and understood our <a href="{% url 'conduct' %}">Code of Conduct</a>.</p>
|
||||
|
||||
<h3>Regular Talk</h3>
|
||||
<p>Regular talks are 45 minutes of presentation, 10 minutes of questions from the audience followed by 5 minutes of preparation for setting up the next speaker.</p>
|
||||
|
||||
<p>Please bring your own laptop with your presentation on; it should have an ordinary HDMI output and we will provide the cable to the projector. We do not guarantee that audio will work, even if your laptop supports it - please reach out to us early if this is a requirement.</p>
|
||||
|
||||
<p>We will provide speakers with a one-day entrance ticket free of charge, but due to our limited funds, you would have to pay for transportation to and from the event yourself. We also encourage speakers to participate for the entire week, but you will have to pay for the full ticket yourself.</p>
|
||||
|
||||
<h3>Lightning Talk</h3>
|
||||
<p>Lightning talks are 10 minutes of presentation. A laptop will be connected to the projector at the location of the presentations.</p>
|
||||
|
||||
<p>A lightning talk is an excellent opportunity for inexperienced speakers to share an interesting idea, presentation, or maybe just a small story.</p>
|
||||
|
||||
<p>You must buy an entrance ticket to host a lightning talk; we are unable to offer free tickets for lightning talks.</p>
|
||||
|
||||
<h3>Workshops</h3>
|
||||
<p>We have two workshop areas that will be able to host workshops for approximately 20 people per room. Workshops can be up to 3 hours per slot and can be extended to full day workshops.</p>
|
||||
|
||||
<p>You must buy an entrance ticket to host a workshop; we are unable to offer free tickets for workshops.</p>
|
||||
|
||||
<h3>Submitting Content</h3>
|
||||
<p>Please submit content for BornHack 2017 as early as possible. You can submit content via our website:</p>
|
||||
|
||||
<ol>
|
||||
<li>Create a <a href="{% url 'account_signup' %}">user account</a> on the BornHack website</li>
|
||||
<li>Visit <a href="{% url 'program:proposal_list' camp_slug='bornhack-2017' %}">the proposals page</a></li>
|
||||
<li>Propose a new speaker</li>
|
||||
<li>Propose a new event</li>
|
||||
</ol>
|
||||
|
||||
<p>We will review incoming proposals and notify you as early as possible on whether the proposal was accepted or not. Proposals submitted before 1st of July will be notified by us no later than the 16th of July. Late submissions are welcome, but we might be running low on available slots at that time.</p>
|
||||
|
||||
<h3>Contact Information</h3>
|
||||
<p>The BornHack content team can be reached at content@bornhack.dk - for general questions regarding the event please reach out to the info team at info@bornhack.dk</p>
|
||||
|
||||
<p>We are reachable via IRC in #BornHack on irc.baconsvin.org (6nbtgccn5nbcodn3.onion) on port 6697 with TLS, you can also follow us on Twitter at @bornhax.</p>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
{% extends 'program_base.html' %}
|
||||
|
||||
{% block title %}
|
||||
Call for Participation | {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block program_content %}
|
||||
|
||||
<h2>Call for Participation coming soon!</h2>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1 @@
|
|||
program/templates/bornhack-2019_call_for_speakers.html
|
16
src/program/templates/combined_proposal_submit.html
Normal file
16
src/program/templates/combined_proposal_submit.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% extends 'program_base.html' %}
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block program_content %}
|
||||
<h3>Submit {{ camp.title }} {{ eventtype.name }} <i class="fas fa-{{ eventtype.icon }}" style="color: {{ eventtype.color }};"></i></h3>
|
||||
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% bootstrap_field field %}
|
||||
{% endfor %}
|
||||
{% bootstrap_button "Submit for Review" button_type="submit" button_class="btn-primary" %}
|
||||
</form>
|
||||
|
||||
{% endblock program_content %}
|
||||
|
|
@ -21,16 +21,16 @@
|
|||
{% if event.event_type.include_in_event_list %}
|
||||
<tr>
|
||||
<td style="background-color: {{ event.event_type.color }}; ">
|
||||
<a href="{% url 'schedule_index' camp_slug=camp.slug %}?type={{ event.event_type.slug }}" style="color: {% if event.event_type.light_text %}white{% else %}black{% endif %};">
|
||||
<a href="{% url 'program:schedule_index' camp_slug=camp.slug %}?type={{ event.event_type.slug }}" style="color: {% if event.event_type.light_text %}white{% else %}black{% endif %};">
|
||||
{{ event.event_type.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'event_detail' camp_slug=camp.slug slug=event.slug %}">{{ event.title }}</a>
|
||||
<a href="{% url 'program:event_detail' camp_slug=camp.slug slug=event.slug %}">{{ event.title }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% for speaker in event.speakers.all %}
|
||||
<a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}">{{ speaker.name }}</a><br>
|
||||
<a href="{% url 'program:speaker_detail' camp_slug=camp.slug slug=speaker.slug %}">{{ speaker.name }}</a><br>
|
||||
{% empty %}
|
||||
N/A
|
||||
{% endfor %}
|
||||
|
|
15
src/program/templates/event_proposal_add_person.html
Normal file
15
src/program/templates/event_proposal_add_person.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% extends 'program_base.html' %}
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block program_content %}
|
||||
<h3>Add {{ eventproposal.event_type.host_title }} {{ speakerproposal.name }} to {{ eventproposal.title }}</h3>
|
||||
|
||||
<p class="lead">Really add {{ speakerproposal.name }} as {{ eventproposal.event_type.host_title }} for {{ eventproposal.title }}?
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% bootstrap_button "<i class='fas fa-check'></i> Yes" button_type="submit" button_class="btn-success" %}
|
||||
<a href="{% url 'program:proposal_list' camp_slug=camp.slug %}" class="btn btn-primary"><i class="fas fa-undo"></i> Cancel</a>
|
||||
</form>
|
||||
{% endblock program_content %}
|
||||
|
33
src/program/templates/event_proposal_select_person.html
Normal file
33
src/program/templates/event_proposal_select_person.html
Normal file
|
@ -0,0 +1,33 @@
|
|||
{% extends 'program_base.html' %}
|
||||
|
||||
{% block title %}
|
||||
Add {{ eventproposal.event_type.host_title }} to {{ eventproposal.title }} | {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block program_content %}
|
||||
|
||||
<h3>Add New {{ eventproposal.event_type.host_title }} to {{ eventproposal.title }}</h3>
|
||||
|
||||
<p class="lead">You are adding a new {{ eventproposal.event_type.host_title }} to {{ eventproposal.title }}. Either pick an existing {{ eventproposal.event_type.host_title }} from the list below, or press the button to create a new {{ eventproposal.event_type.host_title }}.</p>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Existing Artists</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="list-group">
|
||||
{% for speakerproposal in speakerproposal_list %}
|
||||
<a href="{% url 'program:eventproposal_addperson' camp_slug=camp.slug event_uuid=eventproposal.uuid speaker_uuid=speakerproposal.uuid %}" class="list-group-item">
|
||||
<h4 class="list-group-item-heading">
|
||||
Add {{ speakerproposal.name }} to {{ eventproposal.title }}
|
||||
</h4>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="{% url 'program:speakerproposal_create' camp_slug=camp.slug event_uuid=eventproposal.uuid %}" class="btn btn-primary btn-success"><i class="fas fa-plus"></i> Add New {{ eventproposal.event_type.host_title }}</a>
|
||||
<a href="{% url 'program:proposal_list' camp_slug=camp.slug %}" class="btn btn-primary"><i class="fas fa-undo"></i> Cancel</a>
|
||||
{% endblock %}
|
||||
|
10
src/program/templates/event_proposal_type_select.html
Normal file
10
src/program/templates/event_proposal_type_select.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends 'program_base.html' %}
|
||||
|
||||
{% block title %}
|
||||
Select Event Type | {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block program_content %}
|
||||
{% include 'includes/event_proposal_type_select.html' %}
|
||||
{% endblock %}
|
||||
|
10
src/program/templates/event_type_select.html
Normal file
10
src/program/templates/event_type_select.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends 'program_base.html' %}
|
||||
|
||||
{% block title %}
|
||||
Select Event Type | {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block program_content %}
|
||||
{% include 'includes/event_proposal_type_select.html' %}
|
||||
{% endblock %}
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
</div>
|
||||
|
||||
<p>
|
||||
<a href="{% url 'proposal_list' camp_slug=camp.slug %}" class="btn btn-primary">Back to List</a>
|
||||
<a href="{% url 'program:proposal_list' camp_slug=camp.slug %}" class="btn btn-primary">Back to List</a>
|
||||
</p>
|
||||
|
||||
{% endblock program_content %}
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
{% load bootstrap3 %}
|
||||
|
||||
{% block program_content %}
|
||||
<h3>{% if object %}Update{% else %}Create{% endif %} {{ camp.title }} Event Proposal</h3>
|
||||
{% if speaker %}
|
||||
<h3>Submit new {{ event_type.name }} by {{ speaker.name }}</h3>
|
||||
{% else %}
|
||||
<h3>{% if object %}Update{% else %}Create{% endif %} {{ camp.title }} {{ event_type.name }}</h3>
|
||||
{% endif %}
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% bootstrap_button "Save draft" button_type="submit" button_class="btn-primary" %}
|
||||
{% bootstrap_button "Submit for Review" button_type="submit" button_class="btn-primary" %}
|
||||
</form>
|
||||
|
||||
{% endblock program_content %}
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
{% extends 'program_base.html' %}
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block program_content %}
|
||||
<h3>Confirm Submission</h3>
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
<p class="lead">Really submit this event proposal for approval?</p>
|
||||
{% bootstrap_button "Submit" button_type="submit" button_class="btn-primary" %}
|
||||
</form>
|
||||
|
||||
{% endblock program_content %}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Submit New Proposal{% if speaker %} for {{ speaker.name }}{% endif %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<h4>What would {% if speaker %}{{ speaker.name }}{% else %}you{% endif %} like to host?</h4>
|
||||
{% if speaker %}
|
||||
<p>You are submitting a new proposal for {{ speaker.name }}. Please begin by selecting the type of proposal below:</p>
|
||||
{% else %}
|
||||
<p>To submit content for {{ camp.title }} please begin by selecting the type of event below:</p>
|
||||
{% endif %}
|
||||
<div class="list-group">
|
||||
{% for eventtype in eventtype_list %}
|
||||
{% if speaker %}
|
||||
<a href="{%url 'program:eventproposal_create' camp_slug=camp.slug event_type_slug=eventtype.slug speaker_uuid=speaker.uuid %}" class="list-group-item">
|
||||
{% else %}
|
||||
<a href="{% url 'program:proposal_combined_submit' camp_slug=camp.slug event_type_slug=eventtype.slug %}" class="list-group-item">
|
||||
{% endif %}
|
||||
<h4 class="list-group-item-heading">
|
||||
<i class="fas fa-{{ eventtype.icon }} fa-2x fa-pull-left fa-fw" style="color: {{ eventtype.color }};"></i>
|
||||
{{ eventtype.name }}<span class="pull-right"><i class="fas fa-plus fa-2x fa-pull-right" style="color: {{ eventtype.color }};"></i></span>
|
||||
</h4>
|
||||
{% if eventtype.description %}<p class="list-group-item-text">{{ eventtype.description }}</p>{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<p><i>If you have questions or experience problems submitting proposals here please let us know on IRC or by mail. You can also send an email with your proposal and the Content team will take care of creating it in the system.</i></p>
|
||||
</div>
|
||||
</div>
|
||||
|
10
src/program/templates/includes/program_menu.html
Normal file
10
src/program/templates/includes/program_menu.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<a href="{% url 'program:schedule_index' camp_slug=camp.slug %}" class="btn {% if url_name == "schedule_index" or urlyear %}btn-primary{% else %}btn-default{% endif %}">Schedule</a>
|
||||
<a href="{% url 'program:event_index' camp_slug=camp.slug %}" class="btn {% if url_name == "event_index" %}btn-primary{% else %}btn-default{% endif %}">Events</a>
|
||||
<a href="{% url 'program:speaker_index' camp_slug=camp.slug %}" class="btn {% if url_name == "speaker_index" %}btn-primary{% else %}btn-default{% endif %}">Speakers</a>
|
||||
{% if camp.call_for_participation_open %}
|
||||
<a href="{% url 'program:call_for_participation' camp_slug=camp.slug %}" class="btn {% if url_name == "call_for_participation" %}btn-primary{% else %}btn-default{% endif %}">Call for Participation</a>
|
||||
{% if request.user.is_authenticated %}
|
||||
<a href="{% url 'program:proposal_list' camp_slug=camp.slug %}" class="btn {% if url_name in proposal_urls %}btn-primary{% else %}btn-default{% endif %}">Submit Proposal</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<tr>
|
||||
<td>{{ instance.when.lower|date:"H:i" }}-{{ instance.when.upper|date:"H:i" }}</td>
|
||||
<td><a href="{% url 'event_detail' camp_slug=camp.slug slug=instance.event.slug %}">{{ instance.event.title }}</a></td>
|
||||
<td><a href="{% url 'program:event_detail' camp_slug=camp.slug slug=instance.event.slug %}">{{ instance.event.title }}</a></td>
|
||||
<td>{{ instance.location.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -6,20 +6,10 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="btn-group btn-group-justified hidden-xs">
|
||||
<a href="{% url 'schedule_index' camp_slug=camp.slug %}" class="btn {% if url_name == "schedule_index" or urlyear %}btn-primary{% else %}btn-default{% endif %}">Schedule</a>
|
||||
<a href="{% url 'event_index' camp_slug=camp.slug %}" class="btn {% if url_name == "event_index" %}btn-primary{% else %}btn-default{% endif %}">Events</a>
|
||||
<a href="{% url 'speaker_index' camp_slug=camp.slug %}" class="btn {% if url_name == "speaker_index" %}btn-primary{% else %}btn-default{% endif %}">Speakers</a>
|
||||
{% if request.user.is_authenticated %}
|
||||
<a href="{% url 'proposal_list' camp_slug=camp.slug %}" class="btn {% if url_name in proposal_urls %}btn-primary{% else %}btn-default{% endif %}">Your Proposals</a>
|
||||
{% endif %}
|
||||
{% include 'includes/program_menu.html' %}
|
||||
</div>
|
||||
<div class="btn-group-vertical visible-xs">
|
||||
<a href="{% url 'schedule_index' camp_slug=camp.slug %}" class="btn {% if url_name == "schedule_index" or urlyear %}btn-primary{% else %}btn-default{% endif %}">Schedule</a>
|
||||
<a href="{% url 'event_index' camp_slug=camp.slug %}" class="btn {% if url_name == "event_index" %}btn-primary{% else %}btn-default{% endif %}">Events</a>
|
||||
<a href="{% url 'speaker_index' camp_slug=camp.slug %}" class="btn {% if url_name == "speaker_index" %}btn-primary{% else %}btn-default{% endif %}">Speakers</a>
|
||||
{% if request.user.is_authenticated %}
|
||||
<a href="{% url 'proposal_list' camp_slug=camp.slug %}" class="btn {% if url_name in proposal_urls %}btn-primary{% else %}btn-default{% endif %}">Your Proposals</a>
|
||||
{% endif %}
|
||||
{% include 'includes/program_menu.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
|
|
19
src/program/templates/proposal_delete.html
Normal file
19
src/program/templates/proposal_delete.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends 'program_base.html' %}
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block program_content %}
|
||||
{% if object.name %}
|
||||
<h3>Delete "{{ object.name }}"</h3>
|
||||
{% else %}
|
||||
<h3>Delete "{{ object.title }}"</h3>
|
||||
{% endif %}
|
||||
<p class="lead">Really delete this proposal? This action cannot be undone.</p>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_button "<i class='fas fa-times'></i> Delete" button_type="submit" button_class="btn-danger" %}
|
||||
<a href="{% url 'program:proposal_list' camp_slug=camp.slug %}">{% bootstrap_button "<i class='fas fa-undo'></i> Cancel" button_type="link" button_class="btn-primary" %}</a>
|
||||
</form>
|
||||
|
||||
{% endblock program_content %}
|
||||
|
|
@ -5,98 +5,118 @@ Proposals | {{ block.super }}
|
|||
{% endblock %}
|
||||
|
||||
{% block program_content %}
|
||||
<h3>Submitting</h3>
|
||||
<p>To submit a talk or other event for {{ camp.title }} you need to to the following:</p>
|
||||
|
||||
<ol>
|
||||
<li>First you propose one or more speakers. Most events just have one speaker, but some events might have two or more. Be sure to create everyone before going on to step 2.</li>
|
||||
<li>Then you propose one or more events. The <i>Propose New Event</i> form will allow you to choose the speaker(s) you proposed.</li>
|
||||
</ol>
|
||||
{% include 'includes/event_proposal_type_select.html' %}
|
||||
|
||||
<p>If you experience problems submitting proposals here please let us know on IRC or by mail. You can also send an email with your proposal and the Content team will take care of creating it in the system.</p>
|
||||
{% if speakerproposal_list or eventproposal_list %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><i class="fas fa-pencil"></i> Existing Proposals</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-sm-10 col-md-10 col-lg-10">
|
||||
<h4>People</h4>
|
||||
{% if speakerproposal_list %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="text-center">Events</th>
|
||||
<th>Status</th>
|
||||
<th class="text-right">Available Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for speakerproposal in speakerproposal_list %}
|
||||
<tr>
|
||||
<td><span class="h4">{{ speakerproposal.name }}</span></td>
|
||||
<td class="text-center">
|
||||
{% if speakerproposal.eventproposals.all %}
|
||||
{% for ep in speakerproposal.eventproposals.all %}
|
||||
<a href="{% url 'program:eventproposal_update' camp_slug=camp.slug pk=ep.uuid %}"><i class="fas fa-{{ ep.event_type.icon }} fa-lg" style="color: {{ ep.event_type.color }};" data-toggle="tooltip" title="{{ ep.title }} ({{ ep.event_type.name }})"></i></a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
N/A
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class="badge">{{ speakerproposal.proposal_status }}</span></td>
|
||||
<td class="text-right">
|
||||
{% if not camp.read_only %}
|
||||
<a href="{% url 'program:speakerproposal_update' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-sm"><i class="fas fa-edit"></i><span class="h5"> Modify</span></a>
|
||||
<a href="{% url 'program:eventproposal_typeselect' camp_slug=camp.slug speaker_uuid=speakerproposal.uuid %}" class="btn btn-success btn-sm" data-toggle="tooltip" title="Click to add a new talk/act/event for '{{ speakerproposal.name }}'"><i class="fas fa-plus"></i><span class="h5"> Add Event</span></a>
|
||||
{% if not speakerproposal.eventproposals.all %}
|
||||
<a href="{% url 'program:speakerproposal_delete' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-danger btn-sm"><i class="fas fa-times"></i><span class="h5"> Delete</span></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<i>Nothing found.</i>
|
||||
{% endif %}
|
||||
|
||||
<h3>Your {{ camp.title }} Speaker Proposals</h3>
|
||||
{% if speakerproposal_list %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for speakerproposal in speakerproposal_list %}
|
||||
<tr>
|
||||
<td><b>{{ speakerproposal.name }}</b></td>
|
||||
<td><span class="badge">{{ speakerproposal.proposal_status }}</span></td>
|
||||
<td>
|
||||
<a href="{% url 'speakerproposal_detail' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-xs">Details</a>
|
||||
{% if not camp.read_only %}
|
||||
<a href="{% url 'speakerproposal_update' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-xs">Modify</a>
|
||||
{% if speakerproposal.proposal_status == "pending" or speakerproposal.proposal_status == "approved" %}
|
||||
<a href="{% url 'speakerproposal_submit' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-xs btn-disabled" disabled>Submit</a>
|
||||
{% else %}
|
||||
<a href="{% url 'speakerproposal_submit' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-xs">Submit</a>
|
||||
{% endif %}
|
||||
<a href="#" class="btn btn-danger btn-xs btn-disabled" disabled>Delete</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<h4>No speaker proposals found</h4>
|
||||
{% endif %}
|
||||
<p><hr></p>
|
||||
|
||||
{% if not camp.read_only and camp.call_for_speakers_open %}
|
||||
<a href="{% url 'speakerproposal_create' camp_slug=camp.slug %}" class="btn btn-primary btn-sm">Propose New Speaker</a>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
<h3>Your {{ camp.title }} Event Proposals</h3>
|
||||
{% if eventproposal_list %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Type</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for eventproposal in eventproposal_list %}
|
||||
<tr>
|
||||
<td><b>{{ eventproposal.title }}</b></td>
|
||||
<td><b>{{ eventproposal.event_type }}</b></td>
|
||||
<td><span class="badge">{{ eventproposal.proposal_status }}</span></td>
|
||||
<td>
|
||||
<a href="{% url 'eventproposal_detail' camp_slug=camp.slug pk=eventproposal.uuid %}" class="btn btn-primary btn-xs">Details</a>
|
||||
{% if not camp.read_only %}
|
||||
<a href="{% url 'eventproposal_update' camp_slug=camp.slug pk=eventproposal.uuid %}" class="btn btn-primary btn-xs">Modify</a>
|
||||
{% if eventproposal.proposal_status == "pending" %}
|
||||
<a href="{% url 'eventproposal_submit' camp_slug=camp.slug pk=eventproposal.uuid %}" class="btn btn-primary btn-xs btn-disabled" disabled>Submit</a>
|
||||
{% else %}
|
||||
<a href="{% url 'eventproposal_submit' camp_slug=camp.slug pk=eventproposal.uuid %}" class="btn btn-primary btn-xs">Submit</a>
|
||||
{% endif %}
|
||||
<a href="#" class="btn btn-danger btn-xs btn-disabled" disabled>Delete</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<h4>No event proposals found</h4>
|
||||
{% endif %}
|
||||
|
||||
{% if not camp.read_only and camp.call_for_speakers_open %}
|
||||
<a href="{% url 'eventproposal_create' camp_slug=camp.slug %}" class="btn btn-primary btn-sm">Propose New Event</a>
|
||||
<h4>Events</h4>
|
||||
{% if eventproposal_list %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Type</th>
|
||||
<th>People</th>
|
||||
<th>Track</th>
|
||||
<th>Status</th>
|
||||
<th class='text-right'>Available Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for eventproposal in eventproposal_list %}
|
||||
<tr>
|
||||
<td><span class="h4">{{ eventproposal.title }}</span></td>
|
||||
<td><i class="fas fa-{{ eventproposal.event_type.icon }} fa-lg" style="color: {{ eventproposal.event_type.color }};"></i><span class="h4"> {{ eventproposal.event_type }}</span></td>
|
||||
<td><span class="h4">{% for person in eventproposal.speakers.all %}<a href="{% url 'program:speakerproposal_update' camp_slug=camp.slug pk=person.uuid %}"><i class="fas fa-user" data-toggle="tooltip" title="{{ person.name }}"></i></a> {% endfor %}</span></td>
|
||||
<td><span class="h4">{{ eventproposal.track.name }}</span></td>
|
||||
<td><span class="badge">{{ eventproposal.proposal_status }}</span></td>
|
||||
<td class='text-right'>
|
||||
{% if not camp.read_only %}
|
||||
<a href="{% url 'program:eventproposal_update' camp_slug=camp.slug pk=eventproposal.uuid %}" class="btn btn-primary btn-sm"><i class="fas fa-edit"></i><span class="h5"> Modify</span></a>
|
||||
{% if eventproposal.get_available_speakerproposals.exists %}
|
||||
<a href="{% url 'program:eventproposal_selectperson' camp_slug=camp.slug event_uuid=eventproposal.uuid %}" class="btn btn-success btn-sm"><i class="fas fa-plus"></i><span class="h5"> Add {{ eventproposal.event_type.host_title }}</span></a>
|
||||
{% else %}
|
||||
<a href="{% url 'program:speakerproposal_create' camp_slug=camp.slug event_uuid=eventproposal.uuid %}" class="btn btn-success btn-sm"><i class="fas fa-plus"></i><span class="h5"> Add {{ eventproposal.event_type.host_title }}</span></a>
|
||||
{% endif %}
|
||||
<a href="{% url 'program:eventproposal_delete' camp_slug=camp.slug pk=eventproposal.uuid %}" class="btn btn-danger btn-sm"><i class="fas fa-times"></i><span class="h5"> Delete</span></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<i>Nothing found.</i>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-sm-2 col-md-2 col-lg-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Status Help<i class="fas fa-question fa-pull-right"></i></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<dl>
|
||||
<dt><span class="badge">pending</span></dt>
|
||||
<dd>Submission is pending review from the Content Team.</dd><br>
|
||||
<dt><span class="badge">approved</span></dt>
|
||||
<dd>Submission was approved and will be part of this years camp.</dd><br>
|
||||
<dt><span class="badge">rejected</span></dt>
|
||||
<dd>Submission was not approved.</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
<div class="btn-group">
|
||||
<label for="event-type-{{ type.slug }}" class="btn btn-default" style="min-width: 200px; text-align: left;">
|
||||
<span>
|
||||
<i class="fa fa-minus"></i>
|
||||
<i class="fa fa-plus"></i>
|
||||
<i class="fas fa-minus"></i>
|
||||
<i class="fas fa-plus"></i>
|
||||
|
||||
{{ type.name }}
|
||||
</span>
|
||||
|
@ -59,12 +59,12 @@
|
|||
<div class="btn-group">
|
||||
<label for="location-{{ location.slug }}" class="btn btn-default" style="min-width: 200px; text-align: left;">
|
||||
<span class="pull-left">
|
||||
<i class="fa fa-minus"></i>
|
||||
<i class="fa fa-plus"></i>
|
||||
<i class="fas fa-minus"></i>
|
||||
<i class="fas fa-plus"></i>
|
||||
|
||||
{{ location.name }}
|
||||
</span>
|
||||
<i class="pull-right fa fa-{{ location.icon }}"></i>
|
||||
<i class="pull-right fas fa-{{ location.icon }}"></i>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
@ -74,7 +74,7 @@
|
|||
</div>
|
||||
|
||||
<a id="ics-button" class="btn btn-default form-control filter-control">
|
||||
<i class="fa fa-calendar"></i> ICS
|
||||
<i class="fas fa-calendar"></i> ICS
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -88,7 +88,7 @@
|
|||
<hr />
|
||||
|
||||
|
||||
{% url 'schedule_index' camp_slug=camp.slug as baseurl %}
|
||||
{% url 'program:schedule_index' camp_slug=camp.slug as baseurl %}
|
||||
|
||||
<script>
|
||||
$('.filter-control').on('change', function() {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{% for eventinstance in eventinstances %}
|
||||
{% if eventinstance.when.lower.time == timeslot.time %}
|
||||
<td style="background-color: {{ eventinstance.event.event_type.color }}; color: {% if eventinstance.event.event_type.light_text %}white{% else %}black{% endif %};" class="event-td" rowspan={{ eventinstance.timeslots }} data-eventinstance-id="{{ eventinstance.id }}">
|
||||
<a style="color:inherit;" href="{% url 'event_detail' camp_slug=camp.slug slug=eventinstance.event.slug %}">
|
||||
<a style="color:inherit;" href="{% url 'program:event_detail' camp_slug=camp.slug slug=eventinstance.event.slug %}">
|
||||
{{ eventinstance.event.title }}<br>
|
||||
{{ eventinstance.when.lower.time }}-{{ eventinstance.when.upper.time }}
|
||||
</a>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
<div class="row">
|
||||
<noscript>
|
||||
<a href="{% url "noscript_schedule_index" camp_slug=camp.slug %}" class="btn btn-primary">
|
||||
<a href="{% url "program:noscript_schedule_index" camp_slug=camp.slug %}" class="btn btn-primary">
|
||||
Back to noscript schedule
|
||||
</a>
|
||||
<hr />
|
||||
|
@ -37,7 +37,7 @@
|
|||
<h4>Speakers</h4>
|
||||
<div class="list-group">
|
||||
{% for speaker in event.speakers.all %}
|
||||
<h4><a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}" class="list-group-item">{{ speaker.name }}</a></h4>
|
||||
<h4><a href="{% url 'program:speaker_detail' camp_slug=camp.slug slug=speaker.slug %}" class="list-group-item">{{ speaker.name }}</a></h4>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
{% block extra_head %}
|
||||
<noscript>
|
||||
<meta http-equiv="refresh" content="0; url={% url "noscript_schedule_index" camp_slug=camp.slug %}" />
|
||||
<meta http-equiv="refresh" content="0; url={% url "program:noscript_schedule_index" camp_slug=camp.slug %}" />
|
||||
</noscript>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
|||
No javascript? Don't worry, we have a HTML only version of the schedule! Redirecting you there now.
|
||||
</p>
|
||||
<p>
|
||||
<a href="{% url "noscript_schedule_index" camp_slug=camp.slug %}">
|
||||
<a href="{% url "program:noscript_schedule_index" camp_slug=camp.slug %}">
|
||||
Click here if you are not redirected.
|
||||
</a>
|
||||
</p>
|
||||
|
@ -34,7 +34,7 @@ var elm_app = Elm.Main.embed(
|
|||
container,
|
||||
{ 'schedule_timeslot_length_minutes': Number('{{ schedule_timeslot_length_minutes }}')
|
||||
, 'schedule_midnight_offset_hours': Number('{{ schedule_midnight_offset_hours }}')
|
||||
, 'ics_button_href': "{% url 'ics_view' camp_slug=camp.slug %}"
|
||||
, 'ics_button_href': "{% url 'program:ics_view' camp_slug=camp.slug %}"
|
||||
, 'camp_slug': "{{ camp.slug }}"
|
||||
, 'websocket_server': "ws://" + window.location.host + "/schedule/"
|
||||
}
|
||||
|
|
|
@ -5,20 +5,11 @@
|
|||
|
||||
<h3>{{ speaker.name }}</h3>
|
||||
|
||||
{% if speaker.picture_large and speaker.picture_small %}
|
||||
<div class="row">
|
||||
<div class="col-md-8 text-container">
|
||||
<div class="col-md-12 text-container">
|
||||
{{ speaker.biography|commonmark }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<a href="{% url 'speaker_picture' camp_slug=camp.slug slug=speaker.slug picture='large' %}" >
|
||||
<img src="{% url 'speaker_picture' camp_slug=camp.slug slug=speaker.slug picture='thumbnail' %}" alt="{{ camp.title }} speaker picture of {{ speaker.name }}" width="200px">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ speaker.biography|commonmark }}
|
||||
{% endif %}
|
||||
|
||||
<hr />
|
||||
|
||||
|
@ -28,7 +19,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;">
|
||||
{{ event.event_type.name }}
|
||||
</small>
|
||||
<a href="{% url 'event_detail' camp_slug=camp.slug slug=event.slug %}">{{ event.title }}</a>
|
||||
<a href="{% url 'program:event_detail' camp_slug=camp.slug slug=event.slug %}">{{ event.title }}</a>
|
||||
</h3>
|
||||
{{ event.abstract|commonmark }}
|
||||
|
||||
|
|
|
@ -13,12 +13,10 @@
|
|||
|
||||
<div class="list-group">
|
||||
{% for speaker in speaker_list %}
|
||||
<a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}" class="list-group-item">
|
||||
<a href="{% url 'program: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 }})
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p><a href="{% url 'call_for_speakers' camp_slug=camp.slug %}" class="btn btn-primary"><span {% if not camp.call_for_speakers_open %}style="text-decoration: line-through;"{% endif %}>Call for Speakers</span></a></p>
|
||||
{% endblock program_content %}
|
||||
|
|
15
src/program/templates/speakerproposal_delete.html
Normal file
15
src/program/templates/speakerproposal_delete.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% extends 'program_base.html' %}
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block program_content %}
|
||||
<h3>Delete {{ object.name }}</h3>
|
||||
<p class="lead">Really delete this proposal? This action cannot be undone.</p>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_button "Delete" button_type="submit" button_class="btn-danger" %}
|
||||
<a href="{% url 'program:proposal_list' camp_slug=camp.slug %}">{% bootstrap_button "Cancel" button_type="link" button_class="btn-primary" %}</a>
|
||||
</form>
|
||||
|
||||
{% endblock program_content %}
|
||||
|
|
@ -19,8 +19,8 @@
|
|||
{{ speakerproposal.biography|commonmark }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<a href="{% url 'speakerproposal_picture' camp_slug=camp.slug pk=speakerproposal.pk picture='large' %}" >
|
||||
<img src="{% url 'speakerproposal_picture' camp_slug=camp.slug pk=speakerproposal.pk picture='thumbnail' %}" alt="{{ camp.title }} speaker picture of {{ speakerproposal.name }}">
|
||||
<a href="{% url 'program:speakerproposal_picture' camp_slug=camp.slug pk=speakerproposal.pk picture='large' %}" >
|
||||
<img src="{% url 'program:speakerproposal_picture' camp_slug=camp.slug pk=speakerproposal.pk picture='thumbnail' %}" alt="{{ camp.title }} speaker picture of {{ speakerproposal.name }}">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,7 +31,7 @@
|
|||
</div>
|
||||
|
||||
<p>
|
||||
<a href="{% url 'proposal_list' camp_slug=camp.slug %}" class="btn btn-primary">Back to List</a>
|
||||
<a href="{% url 'program:proposal_list' camp_slug=camp.slug %}" class="btn btn-primary">Back to List</a>
|
||||
</p>
|
||||
|
||||
{% endblock program_content %}
|
||||
|
|
|
@ -2,11 +2,19 @@
|
|||
{% load bootstrap3 %}
|
||||
|
||||
{% block program_content %}
|
||||
<h3>{% if object %}Update{% else %}Create{% endif %} {{ camp.title }} Speaker Proposal</h3>
|
||||
|
||||
<h3>
|
||||
{% if object %}
|
||||
Update {{ object.name }} Details
|
||||
{% else %}
|
||||
Add {{ eventproposal.event_type.host_title }} to {{ eventproposal.title }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% bootstrap_button "Save draft" button_type="submit" button_class="btn-primary" %}
|
||||
{% bootstrap_button "Submit for review" button_type="submit" button_class="btn-primary" %}
|
||||
</form>
|
||||
|
||||
{% endblock program_content %}
|
||||
|
|
141
src/program/urls.py
Normal file
141
src/program/urls.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
from django.conf.urls import include, url
|
||||
from .views import *
|
||||
|
||||
app_name = 'program'
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
r'^$',
|
||||
ScheduleView.as_view(),
|
||||
name='schedule_index'
|
||||
),
|
||||
url(
|
||||
r'^noscript/$',
|
||||
NoScriptScheduleView.as_view(),
|
||||
name='noscript_schedule_index'
|
||||
),
|
||||
url(
|
||||
r'^ics/', ICSView.as_view(), name="ics_view"
|
||||
),
|
||||
url(
|
||||
r'^control/', ProgramControlCenter.as_view(), name="program_control_center"
|
||||
),
|
||||
url(
|
||||
r'^proposals/', include([
|
||||
url(
|
||||
r'^$',
|
||||
ProposalListView.as_view(),
|
||||
name='proposal_list',
|
||||
),
|
||||
url(
|
||||
r'^submit/', include([
|
||||
url(
|
||||
r'^$',
|
||||
CombinedProposalTypeSelectView.as_view(),
|
||||
name='proposal_combined_type_select',
|
||||
),
|
||||
url(
|
||||
r'^(?P<event_type_slug>[-_\w+]+)/$',
|
||||
CombinedProposalSubmitView.as_view(),
|
||||
name='proposal_combined_submit',
|
||||
),
|
||||
]),
|
||||
),
|
||||
url(
|
||||
r'^people/', include([
|
||||
url(
|
||||
r'^(?P<pk>[a-f0-9-]+)/update/$',
|
||||
SpeakerProposalUpdateView.as_view(),
|
||||
name='speakerproposal_update'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>[a-f0-9-]+)/delete/$',
|
||||
SpeakerProposalDeleteView.as_view(),
|
||||
name='speakerproposal_delete'
|
||||
),
|
||||
url(
|
||||
r'^(?P<speaker_uuid>[a-f0-9-]+)/add_event/$',
|
||||
EventProposalTypeSelectView.as_view(),
|
||||
name='eventproposal_typeselect'
|
||||
),
|
||||
url(
|
||||
r'^(?P<speaker_uuid>[a-f0-9-]+)/add_event/(?P<event_type_slug>[-_\w+]+)/$',
|
||||
EventProposalCreateView.as_view(),
|
||||
name='eventproposal_create'
|
||||
),
|
||||
])
|
||||
),
|
||||
url(
|
||||
r'^events/', include([
|
||||
url(
|
||||
r'^(?P<pk>[a-f0-9-]+)/edit/$',
|
||||
EventProposalUpdateView.as_view(),
|
||||
name='eventproposal_update'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>[a-f0-9-]+)/delete/$',
|
||||
EventProposalDeleteView.as_view(),
|
||||
name='eventproposal_delete'
|
||||
),
|
||||
url(
|
||||
r'^(?P<event_uuid>[a-f0-9-]+)/add_person/$',
|
||||
EventProposalSelectPersonView.as_view(),
|
||||
name='eventproposal_selectperson'
|
||||
),
|
||||
url(
|
||||
r'^(?P<event_uuid>[a-f0-9-]+)/add_person/new/$',
|
||||
SpeakerProposalCreateView.as_view(),
|
||||
name='speakerproposal_create'
|
||||
),
|
||||
url(
|
||||
r'^(?P<event_uuid>[a-f0-9-]+)/add_person/(?P<speaker_uuid>[a-f0-9-]+)/$',
|
||||
EventProposalAddPersonView.as_view(),
|
||||
name='eventproposal_addperson'
|
||||
),
|
||||
])
|
||||
),
|
||||
])
|
||||
),
|
||||
url(
|
||||
r'^speakers/', include([
|
||||
url(
|
||||
r'^$',
|
||||
SpeakerListView.as_view(),
|
||||
name='speaker_index'
|
||||
),
|
||||
url(
|
||||
r'^(?P<slug>[-_\w+]+)/$',
|
||||
SpeakerDetailView.as_view(),
|
||||
name='speaker_detail'
|
||||
),
|
||||
]),
|
||||
),
|
||||
url(
|
||||
r'^events/$',
|
||||
EventListView.as_view(),
|
||||
name='event_index'
|
||||
),
|
||||
# legacy CFS url kept on purpose to keep old links functional
|
||||
url(
|
||||
r'^call-for-speakers/$',
|
||||
CallForParticipationView.as_view(),
|
||||
name='call_for_speakers'
|
||||
),
|
||||
url(
|
||||
r'^call-for-participation/$',
|
||||
CallForParticipationView.as_view(),
|
||||
name='call_for_participation'
|
||||
),
|
||||
url(
|
||||
r'^calendar/',
|
||||
ICSView.as_view(),
|
||||
name='ics_calendar'
|
||||
),
|
||||
# this must be the last URL here or the regex will overrule the others
|
||||
url(
|
||||
r'^(?P<slug>[-_\w+]+)/$',
|
||||
EventDetailView.as_view(),
|
||||
name='event_detail'
|
||||
),
|
||||
]
|
||||
|
38
src/program/utils.py
Normal file
38
src/program/utils.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
from .forms import *
|
||||
|
||||
def get_speakerproposal_form_class(eventtype):
|
||||
"""
|
||||
Return a SpeakerProposal form class suitable for the provided EventType
|
||||
"""
|
||||
if eventtype.name == 'Music Act':
|
||||
return MusicSpeakerProposalForm
|
||||
elif eventtype.name == 'Talk':
|
||||
return TalkSpeakerProposalForm
|
||||
elif eventtype.name == 'Workshop':
|
||||
return WorkshopSpeakerProposalForm
|
||||
elif eventtype.name == 'Lightning Talk':
|
||||
return LightningTalkSpeakerProposalForm
|
||||
elif eventtype.name == 'Recreational Event':
|
||||
return SlackSpeakerProposalForm
|
||||
else:
|
||||
raise ImproperlyConfigured("Unsupported event type, don't know which form class to use")
|
||||
|
||||
|
||||
def get_eventproposal_form_class(eventtype):
|
||||
"""
|
||||
Return an EventProposal form class suitable for the provided EventType
|
||||
"""
|
||||
if eventtype.name == 'Music Act':
|
||||
return MusicEventProposalForm
|
||||
elif eventtype.name == 'Talk':
|
||||
return TalkEventProposalForm
|
||||
elif eventtype.name == 'Workshop':
|
||||
return WorkshopEventProposalForm
|
||||
elif eventtype.name == 'Lightning Talk':
|
||||
return LightningTalkEventProposalForm
|
||||
elif eventtype.name == 'Recreational Event':
|
||||
return SlackEventProposalForm
|
||||
else:
|
||||
raise ImproperlyConfigured("Unsupported event type, don't know which form class to use")
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import logging
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.views.generic import ListView, TemplateView, DetailView, View
|
||||
from django.views.generic.edit import CreateView, UpdateView
|
||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
|
||||
from django.conf import settings
|
||||
from django.views.decorators.http import require_safe
|
||||
from django.http import Http404, HttpResponse
|
||||
|
@ -10,28 +11,34 @@ from django.utils.decorators import method_decorator
|
|||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib import messages
|
||||
from django.urls import reverse
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.template import Engine, Context
|
||||
|
||||
from django.shortcuts import redirect
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.shortcuts import get_object_or_404
|
||||
from betterforms.multiform import MultiModelForm
|
||||
import icalendar
|
||||
|
||||
from camps.mixins import CampViewMixin
|
||||
from .mixins import (
|
||||
CreateProposalMixin,
|
||||
EnsureUnapprovedProposalMixin,
|
||||
EnsureUserOwnsProposalMixin,
|
||||
EnsureWritableCampMixin,
|
||||
PictureViewMixin,
|
||||
EnsureCFSOpenMixin
|
||||
EnsureCFPOpenMixin
|
||||
)
|
||||
from .email import (
|
||||
add_speakerproposal_updated_email,
|
||||
add_eventproposal_updated_email
|
||||
)
|
||||
from . import models
|
||||
from .utils import get_speakerproposal_form_class, get_eventproposal_form_class
|
||||
from .forms import BaseSpeakerProposalForm
|
||||
|
||||
|
||||
logger = logging.getLogger("bornhack.%s" % __name__)
|
||||
|
||||
|
||||
###################################################################################################
|
||||
# ical calendar
|
||||
|
||||
|
||||
|
@ -88,7 +95,8 @@ class ICSView(CampViewMixin, View):
|
|||
return response
|
||||
|
||||
|
||||
# proposals
|
||||
###################################################################################################
|
||||
# proposals list view
|
||||
|
||||
|
||||
class ProposalListView(LoginRequiredMixin, CampViewMixin, ListView):
|
||||
|
@ -103,158 +111,364 @@ class ProposalListView(LoginRequiredMixin, CampViewMixin, ListView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
# also add eventproposals to the context
|
||||
context['eventproposal_list'] = models.EventProposal.objects.filter(camp=self.camp, user=self.request.user)
|
||||
context['eventproposal_list'] = models.EventProposal.objects.filter(track__camp=self.camp, user=self.request.user)
|
||||
context['eventtype_list'] = models.EventType.objects.filter(public=True)
|
||||
return context
|
||||
|
||||
|
||||
class SpeakerProposalCreateView(LoginRequiredMixin, CampViewMixin, CreateProposalMixin, EnsureWritableCampMixin, EnsureCFSOpenMixin, CreateView):
|
||||
###################################################################################################
|
||||
# speakerproposal views
|
||||
|
||||
|
||||
class SpeakerProposalCreateView(LoginRequiredMixin, CampViewMixin, EnsureWritableCampMixin, EnsureCFPOpenMixin, CreateView):
|
||||
""" This view allows a user to create a new SpeakerProposal linked to an existing EventProposal """
|
||||
model = models.SpeakerProposal
|
||||
fields = ['name', 'biography', 'picture_small', 'picture_large', 'submission_notes']
|
||||
template_name = 'speakerproposal_form.html'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
|
||||
|
||||
class SpeakerProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureWritableCampMixin, EnsureCFSOpenMixin, UpdateView):
|
||||
model = models.SpeakerProposal
|
||||
fields = ['name', 'biography', 'picture_small', 'picture_large', 'submission_notes']
|
||||
template_name = 'speakerproposal_form.html'
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
""" Get the eventproposal object """
|
||||
self.eventproposal = get_object_or_404(models.EventProposal, pk=kwargs['event_uuid'])
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
return reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
|
||||
def form_valid(self, form):
|
||||
if form.instance.proposal_status == models.UserSubmittedModel.PROPOSAL_PENDING:
|
||||
messages.warning(self.request, "Your speaker proposal has been reverted to status draft. Please submit it again when you are ready.")
|
||||
form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_DRAFT
|
||||
|
||||
if form.instance.proposal_status == models.UserSubmittedModel.PROPOSAL_APPROVED:
|
||||
messages.warning(self.request, "Your speaker proposal has been set to modified after approval. Please await approval of the changes.")
|
||||
form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_MODIFIED_AFTER_APPROVAL
|
||||
if not add_speakerproposal_updated_email(form.instance):
|
||||
logger.error(
|
||||
'Unable to add update email to queue for speaker: {}'.format(form.instance)
|
||||
)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class SpeakerProposalSubmitView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureUnapprovedProposalMixin, EnsureWritableCampMixin, EnsureCFSOpenMixin, UpdateView):
|
||||
model = models.SpeakerProposal
|
||||
fields = []
|
||||
template_name = 'speakerproposal_submit.html'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_PENDING
|
||||
messages.info(self.request, "Your proposal has been submitted and is now pending approval")
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class SpeakerProposalDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView):
|
||||
model = models.SpeakerProposal
|
||||
template_name = 'speakerproposal_detail.html'
|
||||
|
||||
|
||||
@method_decorator(require_safe, name='dispatch')
|
||||
class SpeakerProposalPictureView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, PictureViewMixin, DetailView):
|
||||
model = models.SpeakerProposal
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# is the proposal owned by current user?
|
||||
if self.get_object().user != request.user:
|
||||
raise Http404()
|
||||
|
||||
# get and return the response
|
||||
response = self.get_picture_response('/public/speakerproposals/%(campslug)s/%(proposaluuid)s/%(filename)s' % {
|
||||
'campslug': self.camp.slug,
|
||||
'proposaluuid': self.get_object().uuid,
|
||||
'filename': os.path.basename(self.picture.name),
|
||||
})
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class EventProposalCreateView(LoginRequiredMixin, CampViewMixin, CreateProposalMixin, EnsureWritableCampMixin, EnsureCFSOpenMixin, CreateView):
|
||||
model = models.EventProposal
|
||||
fields = ['title', 'abstract', 'event_type', 'speakers', 'allow_video_recording', 'submission_notes']
|
||||
template_name = 'eventproposal_form.html'
|
||||
def get_form_class(self):
|
||||
return get_speakerproposal_form_class(eventtype=self.eventproposal.event_type)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['form'].fields['speakers'].queryset = models.SpeakerProposal.objects.filter(camp=self.camp, user=self.request.user)
|
||||
context['form'].fields['event_type'].queryset = models.EventType.objects.filter(public=True)
|
||||
return context
|
||||
|
||||
|
||||
class EventProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureWritableCampMixin, EnsureCFSOpenMixin, UpdateView):
|
||||
model = models.EventProposal
|
||||
fields = ['title', 'abstract', 'event_type', 'speakers', 'allow_video_recording', 'submission_notes']
|
||||
template_name = 'eventproposal_form.html'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['form'].fields['speakers'].queryset = models.SpeakerProposal.objects.filter(camp=self.camp, user=self.request.user)
|
||||
context['form'].fields['event_type'].queryset = models.EventType.objects.filter(public=True)
|
||||
context['eventproposal'] = self.eventproposal
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
if form.instance.proposal_status == models.UserSubmittedModel.PROPOSAL_PENDING:
|
||||
messages.warning(self.request, "Your event proposal has been reverted to status draft. Please submit it again when you are ready.")
|
||||
form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_DRAFT
|
||||
# set user before saving
|
||||
form.instance.user = self.request.user
|
||||
form.instance.camp = self.camp
|
||||
speakerproposal = form.save()
|
||||
|
||||
if form.instance.proposal_status == models.UserSubmittedModel.PROPOSAL_APPROVED:
|
||||
messages.warning(self.request, "Your event proposal has been set to status modified after approval. Please await approval of the changes.")
|
||||
form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_MODIFIED_AFTER_APPROVAL
|
||||
if not add_eventproposal_updated_email(form.instance):
|
||||
logger.error(
|
||||
'Unable to add update email to queue for event: {}'.format(form.instance)
|
||||
)
|
||||
# add speakerproposal to eventproposal
|
||||
self.eventproposal.speakers.add(speakerproposal)
|
||||
|
||||
return super().form_valid(form)
|
||||
return redirect(
|
||||
reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
)
|
||||
|
||||
|
||||
class EventProposalSubmitView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureUnapprovedProposalMixin, EnsureWritableCampMixin, EnsureCFSOpenMixin, UpdateView):
|
||||
model = models.EventProposal
|
||||
fields = []
|
||||
template_name = 'eventproposal_submit.html'
|
||||
class SpeakerProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureWritableCampMixin, EnsureUserOwnsProposalMixin, EnsureCFPOpenMixin, UpdateView):
|
||||
"""
|
||||
This view allows a user to update an existing SpeakerProposal.
|
||||
"""
|
||||
model = models.SpeakerProposal
|
||||
template_name = 'speakerproposal_form.html'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
return reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_PENDING
|
||||
messages.info(self.request, "Your proposal has been submitted and is now pending approval")
|
||||
return super().form_valid(form)
|
||||
def get_form_class(self):
|
||||
""" Get the appropriate form class based on the eventtype """
|
||||
if self.get_object().eventproposals.count() == 1:
|
||||
# determine which form to use based on the type of event associated with the proposal
|
||||
return get_speakerproposal_form_class(self.get_object().eventproposals.get().event_type)
|
||||
else:
|
||||
# more than one eventproposal. If all events are the same type we can still show a non-generic form here
|
||||
eventtypes = set()
|
||||
for ep in self.get_object().eventproposals.all():
|
||||
eventtypes.add(ep.event_type)
|
||||
if len(eventtypes) == 1:
|
||||
return get_speakerproposal_form_class(ep.event_type)
|
||||
# more than one type of event for this person, return the generic speakerproposal form
|
||||
return BaseSpeakerProposalForm
|
||||
|
||||
|
||||
class EventProposalDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView):
|
||||
model = models.EventProposal
|
||||
template_name = 'eventproposal_detail.html'
|
||||
|
||||
|
||||
# speakers
|
||||
|
||||
|
||||
@method_decorator(require_safe, name='dispatch')
|
||||
class SpeakerPictureView(CampViewMixin, PictureViewMixin, DetailView):
|
||||
model = models.Speaker
|
||||
class SpeakerProposalDeleteView(LoginRequiredMixin, CampViewMixin, EnsureWritableCampMixin, EnsureUserOwnsProposalMixin, EnsureCFPOpenMixin, DeleteView):
|
||||
"""
|
||||
This view allows a user to delete an existing SpeakerProposal object, as long as it is not linked to any EventProposals
|
||||
"""
|
||||
model = models.SpeakerProposal
|
||||
template_name = 'proposal_delete.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# get and return the response
|
||||
response = self.get_picture_response(path='/public/speakers/%(campslug)s/%(slug)s/%(filename)s' % {
|
||||
'campslug': self.camp.slug,
|
||||
'slug': self.get_object().slug,
|
||||
'filename': os.path.basename(self.picture.name),
|
||||
})
|
||||
# do not permit deleting if this speakerproposal is linked to any eventproposals
|
||||
if self.get_object().eventproposals.exists():
|
||||
messages.error(request, "Cannot delete a person while it is associated with one or more eventproposals. Delete those first.")
|
||||
return redirect(reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug}))
|
||||
|
||||
# continue with the request
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
messages.success(self.request, "Proposal '%s' has been deleted." % self.object.name)
|
||||
return reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
|
||||
|
||||
###################################################################################################
|
||||
# eventproposal views
|
||||
|
||||
|
||||
class EventProposalTypeSelectView(LoginRequiredMixin, CampViewMixin, EnsureWritableCampMixin, EnsureCFPOpenMixin, ListView):
|
||||
"""
|
||||
This view is for selecting the type of event to submit (when adding a new eventproposal to an existing speakerproposal)
|
||||
"""
|
||||
model = models.EventType
|
||||
template_name = 'event_type_select.html'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
""" Get the speakerproposal object """
|
||||
self.speaker = get_object_or_404(models.SpeakerProposal, pk=kwargs['speaker_uuid'])
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
""" We only allow submissions of events with EventTypes where public=True """
|
||||
return super().get_queryset().filter(public=True)
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
""" Make speakerproposal object available in template """
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['speaker'] = self.speaker
|
||||
return context
|
||||
|
||||
|
||||
class EventProposalSelectPersonView(LoginRequiredMixin, CampViewMixin, EnsureWritableCampMixin, EnsureCFPOpenMixin, ListView):
|
||||
"""
|
||||
This view is for selecting an existing speakerproposal to add to an existing eventproposal
|
||||
"""
|
||||
model = models.SpeakerProposal
|
||||
template_name = 'event_proposal_select_person.html'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
""" Get EventProposal from url kwargs """
|
||||
self.eventproposal = get_object_or_404(models.EventProposal, pk=kwargs['event_uuid'], user=request.user)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
""" Filter out any speakerproposals already added to this eventproposal """
|
||||
return self.eventproposal.get_available_speakerproposals().all()
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
""" Make eventproposal object available in template """
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['eventproposal'] = self.eventproposal
|
||||
return context
|
||||
|
||||
|
||||
class EventProposalAddPersonView(LoginRequiredMixin, CampViewMixin, EnsureWritableCampMixin, EnsureCFPOpenMixin, UpdateView):
|
||||
"""
|
||||
This view is for adding an existing speakerproposal to an existing eventproposal
|
||||
"""
|
||||
model = models.EventProposal
|
||||
template_name = 'event_proposal_add_person.html'
|
||||
fields = []
|
||||
pk_url_kwarg = 'event_uuid'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
""" Get the speakerproposal object """
|
||||
response = super().dispatch(request, *args, **kwargs)
|
||||
self.speakerproposal = get_object_or_404(models.SpeakerProposal, pk=kwargs['speaker_uuid'], user=request.user)
|
||||
return response
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
""" Make speakerproposal object available in template """
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['speakerproposal'] = self.speakerproposal
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.speakers.add(self.speakerproposal)
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
|
||||
|
||||
class EventProposalCreateView(LoginRequiredMixin, CampViewMixin, EnsureWritableCampMixin, EnsureCFPOpenMixin, CreateView):
|
||||
"""
|
||||
This view allows a user to create a new eventproposal linked to an existing speakerproposal
|
||||
"""
|
||||
model = models.EventProposal
|
||||
template_name = 'eventproposal_form.html'
|
||||
|
||||
def get_form_class(self):
|
||||
""" Get the appropriate form class based on the eventtype """
|
||||
return get_eventproposal_form_class(self.event_type)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
""" Get the speakerproposal object """
|
||||
self.speakerproposal = get_object_or_404(models.SpeakerProposal, pk=self.kwargs['speaker_uuid'])
|
||||
self.event_type = get_object_or_404(models.EventType, slug=self.kwargs['event_type_slug'])
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
""" Make speakerproposal object available in template """
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['speaker'] = self.speakerproposal
|
||||
context['event_type'] = self.event_type
|
||||
return context
|
||||
|
||||
def get_form(self):
|
||||
"""
|
||||
Override get_form() method so we can set the queryset for the track selector.
|
||||
Usually this kind of thing would go into get_initial() but that does not work for some reason, so we do it here instead.
|
||||
"""
|
||||
form_class = self.get_form_class()
|
||||
form = form_class(**self.get_form_kwargs())
|
||||
form.fields['track'].queryset = models.EventTrack.objects.filter(camp=self.camp)
|
||||
return form
|
||||
|
||||
|
||||
def form_valid(self, form):
|
||||
# set camp and user for this eventproposal
|
||||
eventproposal = form.save(commit=False)
|
||||
eventproposal.user = self.request.user
|
||||
eventproposal.event_type = self.event_type
|
||||
eventproposal.save()
|
||||
|
||||
# add the speakerproposal to the eventproposal
|
||||
eventproposal.speakers.add(self.speakerproposal)
|
||||
|
||||
# all good
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
|
||||
|
||||
class EventProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureWritableCampMixin, EnsureUserOwnsProposalMixin, EnsureCFPOpenMixin, UpdateView):
|
||||
model = models.EventProposal
|
||||
template_name = 'eventproposal_form.html'
|
||||
|
||||
def get_form_class(self):
|
||||
""" Get the appropriate form class based on the eventtype """
|
||||
return get_eventproposal_form_class(self.get_object().event_type)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
""" Make speakerproposal and eventtype objects available in the template """
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['event_type'] = self.get_object().event_type
|
||||
return context
|
||||
|
||||
def get_form(self):
|
||||
"""
|
||||
Override get_form() method so we can set the queryset for the track selector.
|
||||
Usually this kind of thing would go into get_initial() but that does not work for some reason, so we do it here instead.
|
||||
"""
|
||||
form_class = self.get_form_class()
|
||||
form = form_class(**self.get_form_kwargs())
|
||||
form.fields['track'].queryset = models.EventTrack.objects.filter(camp=self.camp)
|
||||
return form
|
||||
|
||||
|
||||
class EventProposalDeleteView(LoginRequiredMixin, CampViewMixin, EnsureWritableCampMixin, EnsureUserOwnsProposalMixin, EnsureCFPOpenMixin, DeleteView):
|
||||
model = models.EventProposal
|
||||
template_name = 'proposal_delete.html'
|
||||
|
||||
def get_success_url(self):
|
||||
messages.success(self.request, "Proposal '%s' has been deleted." % self.object.title)
|
||||
return reverse('program:proposal_list', kwargs={'camp_slug': self.camp.slug})
|
||||
|
||||
|
||||
###################################################################################################
|
||||
# combined proposal views
|
||||
|
||||
|
||||
class CombinedProposalTypeSelectView(LoginRequiredMixin, CampViewMixin, ListView):
|
||||
"""
|
||||
A view which allows the user to select event type without anything else on the page
|
||||
"""
|
||||
model = models.EventType
|
||||
template_name = 'event_type_select.html'
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
""" We only allow submissions of events with EventTypes where public=True """
|
||||
return super().get_queryset().filter(public=True)
|
||||
|
||||
|
||||
class CombinedProposalSubmitView(LoginRequiredMixin, CampViewMixin, CreateView):
|
||||
"""
|
||||
This view is used by users to submit CFP proposals.
|
||||
It allows the user to submit an EventProposal and a SpeakerProposal together.
|
||||
"""
|
||||
template_name = 'combined_proposal_submit.html'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""
|
||||
Check that we have a valid EventType
|
||||
"""
|
||||
try:
|
||||
self.eventtype = models.EventType.objects.get(
|
||||
slug=self.kwargs['event_type_slug']
|
||||
)
|
||||
except models.EventType.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
return super().dispatch(
|
||||
request, *args, **kwargs
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
Add EventType to template context
|
||||
"""
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['eventtype'] = self.eventtype
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
We save each object here before redirecting
|
||||
"""
|
||||
# first save the SpeakerProposal
|
||||
speakerproposal = form['speakerproposal'].save(commit=False)
|
||||
speakerproposal.camp = self.camp
|
||||
speakerproposal.user = self.request.user
|
||||
speakerproposal.save()
|
||||
|
||||
# then save the eventproposal
|
||||
eventproposal = form['eventproposal'].save(commit=False)
|
||||
eventproposal.user = self.request.user
|
||||
eventproposal.event_type = self.eventtype
|
||||
eventproposal.save()
|
||||
|
||||
# add the speakerproposal to the eventproposal
|
||||
eventproposal.speakers.add(speakerproposal)
|
||||
|
||||
# all good
|
||||
return redirect(reverse_lazy('program:proposal_list', kwargs={'camp_slug': self.camp.slug}))
|
||||
|
||||
def get_form_class(self):
|
||||
"""
|
||||
We use betterforms.MultiModelForm to combine two forms on the page
|
||||
"""
|
||||
SpeakerProposalForm = get_speakerproposal_form_class(eventtype=self.eventtype)
|
||||
EventProposalForm = get_eventproposal_form_class(eventtype=self.eventtype)
|
||||
|
||||
# build our MultiModelForm
|
||||
class CombinedProposalSubmitForm(MultiModelForm):
|
||||
form_classes = OrderedDict((
|
||||
('speakerproposal', SpeakerProposalForm),
|
||||
('eventproposal', EventProposalForm),
|
||||
))
|
||||
|
||||
# return the form class
|
||||
return CombinedProposalSubmitForm
|
||||
|
||||
def get_form(self):
|
||||
"""
|
||||
Override get_form() method so we can set the queryset for the track selector.
|
||||
Usually this kind of thing would go into get_initial() but that does not work for some reason, so we do it here instead.
|
||||
"""
|
||||
form_class = self.get_form_class()
|
||||
form = form_class(**self.get_form_kwargs())
|
||||
form.forms['eventproposal'].fields['track'].queryset = models.EventTrack.objects.filter(camp=self.camp)
|
||||
return form
|
||||
|
||||
|
||||
###################################################################################################
|
||||
# speaker views
|
||||
|
||||
|
||||
class SpeakerDetailView(CampViewMixin, DetailView):
|
||||
model = models.Speaker
|
||||
|
@ -266,7 +480,8 @@ class SpeakerListView(CampViewMixin, ListView):
|
|||
template_name = 'speaker_list.html'
|
||||
|
||||
|
||||
# events
|
||||
###################################################################################################
|
||||
# event views
|
||||
|
||||
|
||||
class EventListView(CampViewMixin, ListView):
|
||||
|
@ -279,7 +494,8 @@ class EventDetailView(CampViewMixin, DetailView):
|
|||
template_name = 'schedule_event_detail.html'
|
||||
|
||||
|
||||
# schedule
|
||||
###################################################################################################
|
||||
# schedule views
|
||||
|
||||
|
||||
class NoScriptScheduleView(CampViewMixin, TemplateView):
|
||||
|
@ -300,12 +516,13 @@ class ScheduleView(CampViewMixin, TemplateView):
|
|||
return context
|
||||
|
||||
|
||||
class CallForSpeakersView(CampViewMixin, TemplateView):
|
||||
class CallForParticipationView(CampViewMixin, TemplateView):
|
||||
def get_template_names(self):
|
||||
return '%s_call_for_speakers.html' % self.camp.slug
|
||||
return '%s_call_for_participation.html' % self.camp.slug
|
||||
|
||||
|
||||
# control center
|
||||
###################################################################################################
|
||||
# control center csv
|
||||
|
||||
|
||||
class ProgramControlCenter(CampViewMixin, TemplateView):
|
||||
|
@ -328,3 +545,4 @@ class ProgramControlCenter(CampViewMixin, TemplateView):
|
|||
context['csv'] = csv
|
||||
|
||||
return context
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ django-bleach==0.3.0
|
|||
django-bootstrap3==8.2.2
|
||||
django-extensions==1.7.7
|
||||
django-wkhtmltopdf==3.1.0
|
||||
django-betterforms==1.1.4
|
||||
docopt==0.6.2
|
||||
future==0.16.0
|
||||
html5lib==0.9999999
|
||||
|
|
4
src/static_src/css/font-awesome.min.css
vendored
4
src/static_src/css/font-awesome.min.css
vendored
File diff suppressed because one or more lines are too long
5
src/static_src/css/fontawesome-all.min.css
vendored
Normal file
5
src/static_src/css/fontawesome-all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/static_src/webfonts/fa-solid-900.eot
Normal file
BIN
src/static_src/webfonts/fa-solid-900.eot
Normal file
Binary file not shown.
1896
src/static_src/webfonts/fa-solid-900.svg
Normal file
1896
src/static_src/webfonts/fa-solid-900.svg
Normal file
File diff suppressed because it is too large
Load diff
After Width: | Height: | Size: 477 KiB |
BIN
src/static_src/webfonts/fa-solid-900.ttf
Normal file
BIN
src/static_src/webfonts/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
src/static_src/webfonts/fa-solid-900.woff
Normal file
BIN
src/static_src/webfonts/fa-solid-900.woff
Normal file
Binary file not shown.
BIN
src/static_src/webfonts/fa-solid-900.woff2
Normal file
BIN
src/static_src/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
|
@ -12,7 +12,7 @@ Fix IRC permissions for NickServ user {{ request.user.profile.nickserv_username
|
|||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<button class="btn btn-success" type="submit"><i class="fa fa-check"></i> Yes Please</button>
|
||||
<a href="{% url 'teams:detail' camp_slug=team.camp.slug team_slug=team.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-check"></i> Yes Please</button>
|
||||
<a href="{% url 'teams:detail' camp_slug=team.camp.slug team_slug=team.slug %}" class="btn btn-default" type="submit"><i class="fas fa-times"></i> Cancel</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -72,17 +72,17 @@ Team: {{ team.name }} | {{ block.super }}
|
|||
|
||||
{% if request.user in team.members.all %}
|
||||
{% if team.irc_channel and team.irc_channel_managed and request.user.profile.nickserv_username %}
|
||||
<a href="{% url 'teams:fix_irc_acl' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fa fa-wrench"></i> Fix IRC ACL</a>
|
||||
<a href="{% url 'teams:fix_irc_acl' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-wrench"></i> Fix IRC ACL</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'teams:leave' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-danger"><i class="fa fa-remove"></i> Leave Team</a>
|
||||
<a href="{% url 'teams:leave' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-danger"><i class="fas fa-times"></i> Leave Team</a>
|
||||
{% else %}
|
||||
{% if team.needs_members %}
|
||||
<b>This team is looking for members!</b> <a href="{% url 'teams:join' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-xs btn-success"><i class="fa fa-plus"></i> Join Team</a>
|
||||
<b>This team is looking for members!</b> <a href="{% url 'teams:join' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-xs btn-success"><i class="fas fa-plus"></i> Join Team</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if request.user in team.responsible_members.all %}
|
||||
<a href="{% url 'teams:manage' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fa fa-cog"></i> Manage Team</a>
|
||||
<a href="{% url 'teams:manage' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-cog"></i> Manage Team</a>
|
||||
{% endif %}
|
||||
|
||||
<hr>
|
||||
|
@ -103,9 +103,9 @@ Team: {{ team.name }} | {{ block.super }}
|
|||
<td><a href="{% url 'teams:task_detail' slug=task.slug camp_slug=camp.slug team_slug=team.slug %}">{{ task.name }}</a></td>
|
||||
<td>{{ task.description }}</td>
|
||||
<td>
|
||||
<a href="{% url 'teams:task_detail' camp_slug=camp.slug team_slug=team.slug slug=task.slug %}" class="btn btn-primary btn-sm"><i class="fa fa-search"></i> Details</a>
|
||||
<a href="{% url 'teams:task_detail' camp_slug=camp.slug team_slug=team.slug slug=task.slug %}" class="btn btn-primary btn-sm"><i class="fas fa-search"></i> Details</a>
|
||||
{% if request.user in team.responsible_members.all %}
|
||||
<a href="{% url 'teams:task_update' camp_slug=camp.slug team_slug=team.slug slug=task.slug %}" class="btn btn-primary btn-sm"><i class="fa fa-edit"></i> Edit Task</a>
|
||||
<a href="{% url 'teams:task_update' camp_slug=camp.slug team_slug=team.slug slug=task.slug %}" class="btn btn-primary btn-sm"><i class="fas fa-edit"></i> Edit Task</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -113,7 +113,7 @@ Team: {{ team.name }} | {{ block.super }}
|
|||
</tbody>
|
||||
</table>
|
||||
{% if request.user in team.responsible_members.all %}
|
||||
<a href="{% url 'teams:task_create' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fa fa-plus"></i> Create Task</a>
|
||||
<a href="{% url 'teams:task_create' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-plus"></i> Create Task</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -15,8 +15,8 @@ Join Team: {{ team.name }} | {{ block.super }}
|
|||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<button class="btn btn-success" type="submit"><i class="fa fa-check"></i> Join {{ team.name }} Team</button>
|
||||
<a href="{% url 'teams:list' camp_slug=camp.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-check"></i> Join {{ team.name }} Team</button>
|
||||
<a href="{% url 'teams:list' camp_slug=camp.slug %}" class="btn btn-default" type="submit"><i class="fas fa-times"></i> Cancel</a>
|
||||
</form>
|
||||
</p>
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ Leave Team: {{ team.name }} | {{ block.super }}
|
|||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<button class="btn btn-success" type="submit"><i class="fa fa-check"></i> Leave {{ team.name }} Team</button>
|
||||
<a href="{% url 'teams:list' camp_slug=camp.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-check"></i> Leave {{ team.name }} Team</button>
|
||||
<a href="{% url 'teams:list' camp_slug=camp.slug %}" class="btn btn-default" type="submit"><i class="fas fa-times"></i> Cancel</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -63,15 +63,15 @@ Teams | {{ block.super }}
|
|||
|
||||
<td>
|
||||
<div class="btn-group-vertical">
|
||||
<a class="btn btn-primary" href="{% url 'teams:detail' camp_slug=camp.slug team_slug=team.slug %}"><i class="fa fa-search"></i> Details</a>
|
||||
<a class="btn btn-primary" href="{% url 'teams:detail' camp_slug=camp.slug team_slug=team.slug %}"><i class="fas fa-search"></i> Details</a>
|
||||
{% if request.user in team.responsible_members.all %}
|
||||
<a href="{% url 'teams:manage' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fa fa-cog"></i> Manage</a>
|
||||
<a href="{% url 'teams:manage' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-cog"></i> Manage</a>
|
||||
{% endif %}
|
||||
{% if request.user in team.members.all %}
|
||||
<a href="{% url 'teams:leave' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-danger"><i class="fa fa-remove"></i> Leave</a>
|
||||
<a href="{% url 'teams:leave' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-danger"><i class="fas fa-times"></i> Leave</a>
|
||||
{% else %}
|
||||
{% if team.needs_members %}
|
||||
<a href="{% url 'teams:join' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-success"><i class="fa fa-plus"></i> Join</a>
|
||||
<a href="{% url 'teams:join' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-success"><i class="fas fa-plus"></i> Join</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -17,8 +17,8 @@ Manage Team: {{ team.name }} | {{ block.super }}
|
|||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<button class="btn btn-success pull-right" type="submit"><i class="fa fa-check"></i> Save Team</button>
|
||||
<a class="btn btn-primary pull-right" href="{% url 'teams:detail' team_slug=team.slug camp_slug=camp.slug %}"><i class="fa fa-remove"></i> Cancel</a>
|
||||
<button class="btn btn-success pull-right" type="submit"><i class="fas fa-check"></i> Save Team</button>
|
||||
<a class="btn btn-primary pull-right" href="{% url 'teams:detail' team_slug=team.slug camp_slug=camp.slug %}"><i class="fas fa-times"></i> Cancel</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
</div>
|
||||
|
@ -79,9 +79,9 @@ Manage Team: {{ team.name }} | {{ block.super }}
|
|||
</td>
|
||||
<td>
|
||||
<div class="btn-group-vertical">
|
||||
<a class="btn btn-danger" href="{% url 'teams:teammember_remove' camp_slug=camp.slug pk=membership.id %}"><i class="fa fa-trash-o"></i> Remove Member</a>
|
||||
<a class="btn btn-danger" href="{% url 'teams:teammember_remove' camp_slug=camp.slug pk=membership.id %}"><i class="fas fa-trash-o"></i> Remove Member</a>
|
||||
{% if not membership.approved %}
|
||||
<a class="btn btn-success" href="{% url 'teams:teammember_approve' camp_slug=camp.slug pk=membership.id %}"><i class="fa fa-check"></i> Approve Member</a>
|
||||
<a class="btn btn-success" href="{% url 'teams:teammember_approve' camp_slug=camp.slug pk=membership.id %}"><i class="fas fa-check"></i> Approve Member</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -12,7 +12,7 @@ Approve team member {{ teammember.user.profile.name }} for the {{ teammember.tea
|
|||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<button class="btn btn-success" type="submit"><i class="fa fa-check"></i> Add teammember</button>
|
||||
<a href="{% url 'teams:detail' camp_slug=teammember.team.camp.slug team_slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-check"></i> Add teammember</button>
|
||||
<a href="{% url 'teams:detail' camp_slug=teammember.team.camp.slug team_slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fas fa-times"></i> Cancel</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -12,7 +12,7 @@ Remove member {{ teammember.user.profile.name }} from the {{ teammember.team.nam
|
|||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<button class="btn btn-danger" type="submit"><i class="fa fa-trash-o"></i> Remove teammember</button>
|
||||
<a href="{% url 'teams:detail' camp_slug=teammember.team.camp.slug team_slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
|
||||
<button class="btn btn-danger" type="submit"><i class="fas fa-trash-o"></i> Remove teammember</button>
|
||||
<a href="{% url 'teams:detail' camp_slug=teammember.team.camp.slug team_slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fas fa-times"></i> Cancel</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
|
||||
|
||||
<!-- FontAwesome CSS -->
|
||||
<link href="{% static 'css/font-awesome.min.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/fontawesome-all.min.css' %}" rel="stylesheet">
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="{% static 'css/bornhack.css' %}" rel="stylesheet">
|
||||
|
@ -93,7 +93,7 @@
|
|||
<div class="btn-group btn-group-justified hidden-xs">
|
||||
<a class="btn {% menubuttonclass 'camps' %}" href="{% url 'camp_detail' camp_slug=camp.slug %}">{{ camp.title }}</a>
|
||||
<a class="btn {% menubuttonclass 'info' %}" href="{% url 'info' camp_slug=camp.slug %}">Info</a>
|
||||
<a class="btn {% menubuttonclass 'program' %}" href="{% url 'schedule_index' camp_slug=camp.slug %}">Program</a>
|
||||
<a class="btn {% menubuttonclass 'program' %}" href="{% url 'program:schedule_index' camp_slug=camp.slug %}">Program</a>
|
||||
<a class="btn {% menubuttonclass 'villages' %}" href="{% url 'village_list' camp_slug=camp.slug %}">Villages</a>
|
||||
<a class="btn {% menubuttonclass 'sponsors' %}" href="{% url 'sponsors' camp_slug=camp.slug %}">Sponsors</a>
|
||||
<a class="btn {% menubuttonclass 'teams' %}" href="{% url 'teams:list' camp_slug=camp.slug %}">Teams</a>
|
||||
|
@ -101,7 +101,7 @@
|
|||
<div class="btn-group-vertical visible-xs">
|
||||
<a class="btn {% menubuttonclass 'camps' %}" href="{% url 'camp_detail' camp_slug=camp.slug %}">{{ camp.title }}</a>
|
||||
<a class="btn {% menubuttonclass 'info' %}" href="{% url 'info' camp_slug=camp.slug %}">Info</a>
|
||||
<a class="btn {% menubuttonclass 'program' %}" href="{% url 'schedule_index' camp_slug=camp.slug %}">Program</a>
|
||||
<a class="btn {% menubuttonclass 'program' %}" href="{% url 'program:schedule_index' camp_slug=camp.slug %}">Program</a>
|
||||
<a class="btn {% menubuttonclass 'villages' %}" href="{% url 'village_list' camp_slug=camp.slug %}">Villages</a>
|
||||
<a class="btn {% menubuttonclass 'sponsors' %}" href="{% url 'sponsors' camp_slug=camp.slug %}">Sponsors</a>
|
||||
<a class="btn {% menubuttonclass 'teams' %}" href="{% url 'teams:list' camp_slug=camp.slug %}">Teams</a>
|
||||
|
@ -122,5 +122,11 @@
|
|||
{% endblock %}
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Enable all bootstrap tooltips on the page, only works if the useragent has JS enabled -->
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -40,11 +40,11 @@
|
|||
Not yet
|
||||
{% endif %}
|
||||
<td>
|
||||
<a href="{% url 'tickets:shopticket_download' pk=ticket.pk %}" class="btn btn-primary"><i class="fa fa-download" aria-hidden="true"></i> Download PDF</a>
|
||||
<a href="{% url 'tickets:shopticket_download' pk=ticket.pk %}" class="btn btn-primary"><i class="fas fa-download" aria-hidden="true"></i> Download PDF</a>
|
||||
{% if not ticket.name %}
|
||||
<a href="{% url 'tickets:shopticket_edit' pk=ticket.pk %}" class="btn btn-primary"><i class="fa fa-edit" aria-hidden="true"></i> Set name</a>
|
||||
<a href="{% url 'tickets:shopticket_edit' pk=ticket.pk %}" class="btn btn-primary"><i class="fas fa-edit" aria-hidden="true"></i> Set name</a>
|
||||
{% else %}
|
||||
<a href="{% url 'tickets:shopticket_edit' pk=ticket.pk %}" class="btn btn-primary"><i class="fa fa-edit" aria-hidden="true"></i> Edit name</a>
|
||||
<a href="{% url 'tickets:shopticket_edit' pk=ticket.pk %}" class="btn btn-primary"><i class="fas fa-edit" aria-hidden="true"></i> Edit name</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
|
@ -15,7 +15,8 @@ from program.models import (
|
|||
Event,
|
||||
EventInstance,
|
||||
Speaker,
|
||||
EventLocation
|
||||
EventLocation,
|
||||
EventTrack,
|
||||
)
|
||||
from tickets.models import (
|
||||
TicketType
|
||||
|
@ -85,9 +86,11 @@ class Command(BaseCommand):
|
|||
|
||||
camp2018 = Camp.objects.create(
|
||||
title='BornHack 2018',
|
||||
tagline='Undecided',
|
||||
tagline='scale it',
|
||||
slug='bornhack-2018',
|
||||
shortslug='bh2018',
|
||||
call_for_participation_open=True,
|
||||
call_for_sponsors_open=True,
|
||||
buildup=(
|
||||
timezone.datetime(2018, 8, 25, 12, 0, tzinfo=timezone.utc),
|
||||
timezone.datetime(2018, 8, 27, 11, 59, tzinfo=timezone.utc),
|
||||
|
@ -278,26 +281,57 @@ class Command(BaseCommand):
|
|||
|
||||
self.output("Creating event types...")
|
||||
workshop = EventType.objects.create(
|
||||
name='Workshops',
|
||||
slug='workshops',
|
||||
name='Workshop',
|
||||
slug='workshop',
|
||||
color='#ff9900',
|
||||
light_text=False,
|
||||
public=True
|
||||
public=True,
|
||||
description='Workshops actively involve the participants in the learning experience',
|
||||
icon='toolbox',
|
||||
host_title='Host',
|
||||
)
|
||||
|
||||
talk = EventType.objects.create(
|
||||
name='Talks',
|
||||
slug='talks',
|
||||
name='Talk',
|
||||
slug='talk',
|
||||
color='#2D9595',
|
||||
light_text=True,
|
||||
public=True
|
||||
public=True,
|
||||
description='A presentation on a stage',
|
||||
icon='chalkboard-teacher',
|
||||
host_title='Speaker',
|
||||
)
|
||||
|
||||
lightning = EventType.objects.create(
|
||||
name='Lightning Talk',
|
||||
slug='lightning-talk',
|
||||
color='#ff0000',
|
||||
light_text=True,
|
||||
public=True,
|
||||
description='A short 5-10 minute presentation',
|
||||
icon='bolt',
|
||||
host_title='Speaker',
|
||||
)
|
||||
|
||||
music = EventType.objects.create(
|
||||
name='Music Act',
|
||||
slug='music',
|
||||
color='#1D0095',
|
||||
light_text=True,
|
||||
public=True,
|
||||
description='A musical performance',
|
||||
icon='music',
|
||||
host_title='Artist',
|
||||
)
|
||||
|
||||
keynote = EventType.objects.create(
|
||||
name='Keynotes',
|
||||
slug='keynotes',
|
||||
name='Keynote',
|
||||
slug='keynote',
|
||||
color='#FF3453',
|
||||
light_text=True
|
||||
light_text=True,
|
||||
description='A keynote presentation',
|
||||
icon='star',
|
||||
host_title='Speaker',
|
||||
)
|
||||
|
||||
facility = EventType.objects.create(
|
||||
|
@ -306,13 +340,20 @@ class Command(BaseCommand):
|
|||
color='#cccccc',
|
||||
light_text=False,
|
||||
include_in_event_list=False,
|
||||
description='Events involving facilities like bathrooms, food area and so on',
|
||||
icon='home',
|
||||
host_title='Host',
|
||||
)
|
||||
|
||||
slack = EventType.objects.create(
|
||||
name='Slacking Off',
|
||||
slug='slacking-off',
|
||||
name='Recreational Event',
|
||||
slug='recreational-event',
|
||||
color='#0000ff',
|
||||
light_text=True
|
||||
light_text=True,
|
||||
public=True,
|
||||
description='Events of a recreational nature',
|
||||
icon='dice',
|
||||
host_title='Host',
|
||||
)
|
||||
|
||||
self.output("Creating productcategories...")
|
||||
|
@ -523,6 +564,13 @@ class Command(BaseCommand):
|
|||
)
|
||||
order3.mark_as_paid(request=None)
|
||||
|
||||
self.output('Creating eventtracks for {}...'.format(year))
|
||||
track = EventTrack.objects.create(
|
||||
camp=camp,
|
||||
name="BornHack",
|
||||
slug=camp.slug,
|
||||
)
|
||||
|
||||
self.output('Creating eventlocations for {}...'.format(year))
|
||||
speakers_tent = EventLocation.objects.create(
|
||||
name='Speakers Tent',
|
||||
|
@ -566,61 +614,61 @@ class Command(BaseCommand):
|
|||
title='Developing the BornHack website',
|
||||
abstract='abstract here, bla bla bla',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev2 = Event.objects.create(
|
||||
title='State of the world',
|
||||
abstract='abstract here, bla bla bla',
|
||||
event_type=keynote,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev3 = Event.objects.create(
|
||||
title='Welcome to bornhack!',
|
||||
abstract='abstract here, bla bla bla',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev4 = Event.objects.create(
|
||||
title='bar is open',
|
||||
abstract='the bar is open, yay',
|
||||
event_type=facility,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev5 = Event.objects.create(
|
||||
title='Network something',
|
||||
abstract='abstract here, bla bla bla',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev6 = Event.objects.create(
|
||||
title='State of outer space',
|
||||
abstract='abstract here, bla bla bla',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev9 = Event.objects.create(
|
||||
title='The Alternative Welcoming',
|
||||
abstract='Why does The Alternative support BornHack? Why does The Alternative think IT is an overlooked topic? A quick runt-hrough of our program and workshops. We will bring an IT political debate to both the stage and the beer tents.',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev10 = Event.objects.create(
|
||||
title='Words and Power - are we making the most of online activism?',
|
||||
abstract='For years, big names like Ed Snowden and Chelsea Manning have given up their lives in order to protect regular people like you and me from breaches of our privacy. But we are still struggling with getting people interested in internet privacy. Why is this, and what can we do? Using experience from communicating privacy issues on multiple levels for a couple of years, I have encountered some deep seated issues in the way we talk about what privacy means. Are we good enough at letting people know whats going on?',
|
||||
event_type=keynote,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev11 = Event.objects.create(
|
||||
title='r4d1o hacking 101',
|
||||
abstract='Learn how to enable the antenna part of your ccc badge and get started with receiving narrow band FM. In the workshop you will have the opportunity to sneak peak on the organizers radio communications using your SDR. If there is more time we will look at WiFi radar or your protocol of choice.',
|
||||
event_type=workshop,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev12 = Event.objects.create(
|
||||
title='Introduction to Sustainable Growth in a Digital World',
|
||||
abstract='Free Choice is the underestimated key to secure value creation in a complex economy, where GDP-models only measure commercial profit and ignore the environment. We reconstruct the model thinking about Utility, Production, Security and Environment around the 5 Criteria for Sustainability.',
|
||||
event_type=workshop,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev13 = Event.objects.create(
|
||||
title='American Fuzzy Lop and Address Sanitizer',
|
||||
|
@ -632,7 +680,7 @@ Code written in C and C++ is often riddled with bugs in the memory management. O
|
|||
Slides: [https://www.int21.de/slides/bornhack2016-fuzzing/](https://www.int21.de/slides/bornhack2016-fuzzing/)
|
||||
''',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev14 = Event.objects.create(
|
||||
title='PGP Keysigning Party',
|
||||
|
@ -647,7 +695,7 @@ For people who haven't attended a PGP keysigning party before, we will guide you
|
|||
2. (Optional) Bring some government-issued identification paper (passport, drivers license, etc.). The ID should contain a picture of yourself. You can leave this out, but then it will be a bit harder for others to verify your key properly.
|
||||
''',
|
||||
event_type=workshop,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev15 = Event.objects.create(
|
||||
title='Bluetooth Low Energy',
|
||||
|
@ -677,7 +725,7 @@ of applications you can build on top. Finally, a low-level
|
|||
demonstration of interfacing with a BLE controller is performed.
|
||||
''',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev16 = Event.objects.create(
|
||||
title='TLS attacks and the burden of faulty TLS implementations',
|
||||
|
@ -699,13 +747,13 @@ underappreciated problem.
|
|||
Slides: [https://www.int21.de/slides/bornhack2016-tls/](https://www.int21.de/slides/bornhack2016-tls/)
|
||||
''',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev17 = Event.objects.create(
|
||||
title='State of the Network',
|
||||
abstract='Come and meet the network team who will talk about the design and operation of the network at BornHack.',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev18 = Event.objects.create(
|
||||
title='Running Exit Nodes in the North',
|
||||
|
@ -733,19 +781,19 @@ In Finland, Juha Nurmi has been establishing good relationships with ISPs
|
|||
and law enforcement agencies to keep Finnish exit nodes online.
|
||||
''',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev19 = Event.objects.create(
|
||||
title='Hacker Jeopardy Qualifier',
|
||||
abstract='Hacker Jeopardy qualifying',
|
||||
event_type=slack,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev20 = Event.objects.create(
|
||||
title='Hacker Jeopardy Finals',
|
||||
abstract='Hacker Jeopardy Finals between the winners of the qualifying games',
|
||||
event_type=slack,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev21 = Event.objects.create(
|
||||
title='Incompleteness Phenomena in Mathematics: From Kurt Gödel to Harvey Friedman',
|
||||
|
@ -763,7 +811,7 @@ discovered and many of these (relative) unprovable sentences are of genuine math
|
|||
Note that these (early 20th century) developments also play an important role in developing the theoretical computer.
|
||||
''',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev22 = Event.objects.create(
|
||||
title='Infocalypse Now - and how to Survive It?',
|
||||
|
@ -780,7 +828,7 @@ herfbombs, solarstorms and intelligence agencies on steroids
|
|||
The Beast is unleashed, can it be stopped, or is it anyone for him self?
|
||||
''',
|
||||
event_type=keynote,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev23 = Event.objects.create(
|
||||
title='Liquid Democracy (Introduction and Debate)',
|
||||
|
@ -790,7 +838,7 @@ A lot has happened ever since the German pirates developed the first visions abo
|
|||
Monday will primarily be focused around The Alternatives experiment with Liquid Democracy and a constructive debate about how liquid democracy can improve The Alternative. Rolf Bjerre leads the process.
|
||||
''',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev24 = Event.objects.create(
|
||||
title='Badge Workshop',
|
||||
|
@ -798,7 +846,7 @@ Monday will primarily be focused around The Alternatives experiment with Liquid
|
|||
In this workshop you can learn how to solder and get help assembling your badge. We will have soldering irons and other tools to help things along. You can also discuss your ideas for badge hacks and modifications with the other participants and the host, Thomas Flummer.
|
||||
''',
|
||||
event_type=workshop,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev25 = Event.objects.create(
|
||||
title='Checking a Distributed Hash Table for Correctness',
|
||||
|
@ -824,7 +872,7 @@ to give people an overview
|
|||
of how to attack larger code bases with (semi-) formal methods.
|
||||
''',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev26 = Event.objects.create(
|
||||
title='GraphQL - A Data Language',
|
||||
|
@ -853,7 +901,7 @@ of the language. The running example is a GraphQL compiler written for
|
|||
Erlang.
|
||||
''',
|
||||
event_type=talk,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev27 = Event.objects.create(
|
||||
title='Visualisation of Public Datasets',
|
||||
|
@ -865,19 +913,19 @@ I will present some portals where it is possible to get public datasets. Afterwa
|
|||
Towards the end we will open up to debate about how to use these resources or if there are other solutions.
|
||||
''',
|
||||
event_type=workshop,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev28 = Event.objects.create(
|
||||
title='Local delicacies',
|
||||
abstract='Come taste delicacies from bornholm',
|
||||
event_type=facility,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
ev29 = Event.objects.create(
|
||||
title='Local delicacies from the world',
|
||||
abstract='An attempt to create an event where we all prepare local delicacies for each other',
|
||||
event_type=facility,
|
||||
camp=camp
|
||||
track=track
|
||||
)
|
||||
|
||||
self.output("Creating speakers for {}...".format(year))
|
||||
|
|
Loading…
Reference in a new issue