rework speaker and talk proposal stuff
This commit is contained in:
parent
1162a72627
commit
ad3b826844
|
@ -133,6 +133,58 @@ urlpatterns = [
|
||||||
ScheduleView.as_view(),
|
ScheduleView.as_view(),
|
||||||
name='schedule_index'
|
name='schedule_index'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
r'^submissions/', include([
|
||||||
|
url(
|
||||||
|
r'^$',
|
||||||
|
SubmissionListView.as_view(),
|
||||||
|
name='submission_list',
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^speakers/', include([
|
||||||
|
url(
|
||||||
|
r'^create/$',
|
||||||
|
SpeakerSubmissionCreateView.as_view(),
|
||||||
|
name='speakersubmission_create'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^(?P<pk>[a-f0-9-]+)/$',
|
||||||
|
SpeakerSubmissionDetailView.as_view(),
|
||||||
|
name='speakersubmission_detail'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^(?P<pk>[a-f0-9-]+)/edit/$',
|
||||||
|
SpeakerSubmissionUpdateView.as_view(),
|
||||||
|
name='speakersubmission_update'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^(?P<pk>[a-f0-9-]+)/pictures/(?P<picture>[-_\w+]+)/$',
|
||||||
|
SpeakerSubmissionPictureView.as_view(),
|
||||||
|
name='speakersubmission_picture',
|
||||||
|
),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^events/', include([
|
||||||
|
url(
|
||||||
|
r'^create/$',
|
||||||
|
EventSubmissionCreateView.as_view(),
|
||||||
|
name='eventsubmission_create'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^(?P<pk>[a-f0-9-]+)/$',
|
||||||
|
EventSubmissionDetailView.as_view(),
|
||||||
|
name='eventsubmission_detail'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^(?P<pk>[a-f0-9-]+)/edit/$',
|
||||||
|
EventSubmissionUpdateView.as_view(),
|
||||||
|
name='eventsubmission_update'
|
||||||
|
),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
])
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
r'^speakers/', include([
|
r'^speakers/', include([
|
||||||
url(
|
url(
|
||||||
|
@ -140,21 +192,11 @@ urlpatterns = [
|
||||||
SpeakerListView.as_view(),
|
SpeakerListView.as_view(),
|
||||||
name='speaker_index'
|
name='speaker_index'
|
||||||
),
|
),
|
||||||
url(
|
|
||||||
r'^create/$',
|
|
||||||
SpeakerCreateView.as_view(),
|
|
||||||
name='speaker_create'
|
|
||||||
),
|
|
||||||
url(
|
url(
|
||||||
r'^(?P<slug>[-_\w+]+)/$',
|
r'^(?P<slug>[-_\w+]+)/$',
|
||||||
SpeakerDetailView.as_view(),
|
SpeakerDetailView.as_view(),
|
||||||
name='speaker_detail'
|
name='speaker_detail'
|
||||||
),
|
),
|
||||||
url(
|
|
||||||
r'^(?P<slug>[-_\w+]+)/edit/$',
|
|
||||||
SpeakerEditView.as_view(),
|
|
||||||
name='speaker_edit'
|
|
||||||
),
|
|
||||||
url(
|
url(
|
||||||
r'^(?P<slug>[-_\w+]+)/pictures/(?P<picture>[-_\w+]+)/$',
|
r'^(?P<slug>[-_\w+]+)/pictures/(?P<picture>[-_\w+]+)/$',
|
||||||
SpeakerPictureView.as_view(),
|
SpeakerPictureView.as_view(),
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404
|
||||||
class CampViewMixin(object):
|
class CampViewMixin(object):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.camp = get_object_or_404(Camp, slug=self.kwargs['camp_slug'])
|
self.camp = get_object_or_404(Camp, slug=self.kwargs['camp_slug'])
|
||||||
return super(CampViewMixin, self).dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super(CampViewMixin, self).get_queryset()
|
queryset = super(CampViewMixin, self).get_queryset()
|
||||||
|
|
|
@ -1,6 +1,16 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Event, Speaker, EventType, EventInstance, EventLocation
|
from .models import Event, Speaker, EventType, EventInstance, EventLocation, SpeakerSubmission, EventSubmission
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SpeakerSubmission)
|
||||||
|
class SpeakerSubmissionAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(EventSubmission)
|
||||||
|
class EventSubmissionAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@admin.register(EventLocation)
|
@admin.register(EventLocation)
|
||||||
|
|
148
src/program/migrations/0030_auto_20170312_1230.py
Normal file
148
src/program/migrations/0030_auto_20170312_1230.py
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-03-12 11:30
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import program.models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('camps', '0020_camp_read_only'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('program', '0029_auto_20170307_2042'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EventSubmission',
|
||||||
|
fields=[
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated', models.DateTimeField(auto_now=True)),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('submission_status', models.CharField(choices=[('draft', 'Draft'), ('pending', 'Pending approval'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='draft', max_length=50)),
|
||||||
|
('title', models.CharField(help_text='The title of this event', max_length=255)),
|
||||||
|
('abstract', models.TextField(help_text='The abstract for this event')),
|
||||||
|
('camp', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='eventsubmissions', to='camps.Camp')),
|
||||||
|
('event_type', models.ForeignKey(help_text='The type of event', on_delete=django.db.models.deletion.CASCADE, to='program.EventType')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SpeakerSubmission',
|
||||||
|
fields=[
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated', models.DateTimeField(auto_now=True)),
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('submission_status', models.CharField(choices=[('draft', 'Draft'), ('pending', 'Pending approval'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='draft', max_length=50)),
|
||||||
|
('name', models.CharField(help_text='Name or alias of the speaker', max_length=150)),
|
||||||
|
('biography', models.TextField(help_text='Markdown is supported.')),
|
||||||
|
('picture_large', models.ImageField(blank=True, help_text='A picture of the speaker', null=True, upload_to=program.models.get_speakersubmission_picture_upload_path)),
|
||||||
|
('picture_small', models.ImageField(blank=True, help_text='A thumbnail of the speaker picture', null=True, upload_to=program.models.get_speakersubmission_picture_upload_path)),
|
||||||
|
('camp', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='speakersubmissions', to='camps.Camp')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='event',
|
||||||
|
name='submission_status',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='abstract',
|
||||||
|
field=models.TextField(help_text='The abstract for this event'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='camp',
|
||||||
|
field=models.ForeignKey(help_text='The camp this event belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='events', to='camps.Camp'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='event_type',
|
||||||
|
field=models.ForeignKey(help_text='The type of this event', on_delete=django.db.models.deletion.CASCADE, to='program.EventType'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(blank=True, help_text='The slug for this event, created automatically', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='title',
|
||||||
|
field=models.CharField(help_text='The title of this event', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='video_recording',
|
||||||
|
field=models.BooleanField(default=True, help_text='Do we intend to record video of this event?'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='video_url',
|
||||||
|
field=models.URLField(blank=True, help_text='URL to the recording', max_length=1000, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventlocation',
|
||||||
|
name='camp',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='eventlocations', to='camps.Camp'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='speaker',
|
||||||
|
name='biography',
|
||||||
|
field=models.TextField(help_text='Markdown is supported.'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='speaker',
|
||||||
|
name='camp',
|
||||||
|
field=models.ForeignKey(help_text='The camp this speaker belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='speakers', to='camps.Camp'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='speaker',
|
||||||
|
name='events',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text='The event(s) this speaker is anchoring', to='program.Event'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='speaker',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(help_text='Name or alias of the speaker', max_length=150),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='speaker',
|
||||||
|
name='picture_large',
|
||||||
|
field=models.ImageField(blank=True, help_text='A picture of the speaker', null=True, upload_to=program.models.get_speaker_picture_upload_path),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='speaker',
|
||||||
|
name='picture_small',
|
||||||
|
field=models.ImageField(blank=True, help_text='A thumbnail of the speaker picture', null=True, upload_to=program.models.get_speaker_picture_upload_path),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='speaker',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(blank=True, help_text='The slug for this speaker, will be autocreated', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='speaker',
|
||||||
|
name='submission_status',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='speaker',
|
||||||
|
name='submission',
|
||||||
|
field=models.OneToOneField(blank=True, help_text='The speaker submission object this speaker was created from', null=True, on_delete=django.db.models.deletion.CASCADE, to='program.SpeakerSubmission'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='speaker',
|
||||||
|
unique_together=set([('camp', 'slug'), ('camp', 'name')]),
|
||||||
|
),
|
||||||
|
]
|
24
src/program/migrations/0031_auto_20170312_1529.py
Normal file
24
src/program/migrations/0031_auto_20170312_1529.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-03-12 14:29
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('program', '0030_auto_20170312_1230'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='speaker',
|
||||||
|
name='user',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='eventsubmission',
|
||||||
|
name='speakers',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text='The speaker(s) for this event', to='program.SpeakerSubmission'),
|
||||||
|
),
|
||||||
|
]
|
36
src/program/mixins.py
Normal file
36
src/program/mixins.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
class CreateUserSubmissionMixin(SingleObjectMixin):
|
||||||
|
def form_valid(self, form):
|
||||||
|
# set camp and user before saving
|
||||||
|
form.instance.camp = self.camp
|
||||||
|
form.instance.user = self.request.user
|
||||||
|
speaker = form.save()
|
||||||
|
return redirect(reverse('submission_list', kwargs={'camp_slug': self.camp.slug}))
|
||||||
|
|
||||||
|
|
||||||
|
class EnsureUnpprovedSubmissionMixin(SingleObjectMixin):
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
# do not permit editing if the submission is already approved
|
||||||
|
if self.get_object().submission_status == models.UserSubmittedModel.SUBMISSION_APPROVED:
|
||||||
|
messages.error(request, "This submission has already been approved. Please contact the organisers if you need to modify something." % self.camp.title)
|
||||||
|
return redirect(reverse('submissions_list', kwargs={'camp_slug': self.camp.slug}))
|
||||||
|
|
||||||
|
# alright, continue with the request
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class EnsureUserOwnsSubmissionMixin(SingleObjectMixin):
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
# make sure that this submission belongs to the logged in user
|
||||||
|
if self.get_object().user.username != request.user.username:
|
||||||
|
messages.error(request, "No thanks")
|
||||||
|
return redirect(reverse('submissions_list', kwargs={'camp_slug': self.camp.slug}))
|
||||||
|
|
||||||
|
# alright, continue with the request
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
|
@ -7,12 +7,28 @@ from utils.models import CreatedUpdatedModel, CampRelatedModel
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
class UserSubmittedModel(CampRelatedModel):
|
class UserSubmittedModel(CampRelatedModel):
|
||||||
|
"""
|
||||||
|
An abstract model containing the stuff that is shared
|
||||||
|
between the SpeakerSubmission and EventSubmission models.
|
||||||
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
uuid = models.UUIDField(
|
||||||
|
primary_key=True,
|
||||||
|
default=uuid.uuid4,
|
||||||
|
editable=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
user = models.ForeignKey(
|
||||||
|
'auth.User',
|
||||||
|
)
|
||||||
|
|
||||||
SUBMISSION_DRAFT = 'draft'
|
SUBMISSION_DRAFT = 'draft'
|
||||||
SUBMISSION_PENDING = 'pending'
|
SUBMISSION_PENDING = 'pending'
|
||||||
SUBMISSION_APPROVED = 'approved'
|
SUBMISSION_APPROVED = 'approved'
|
||||||
|
@ -38,20 +54,114 @@ class UserSubmittedModel(CampRelatedModel):
|
||||||
default=SUBMISSION_DRAFT,
|
default=SUBMISSION_DRAFT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '%s (submitted by: %s, status: %s)' % (self.headline, self.user, self.submission_status)
|
||||||
|
|
||||||
|
|
||||||
|
def get_speakersubmission_picture_upload_path(instance, filename):
|
||||||
|
""" We want speakersubmission pictures saved as MEDIA_ROOT/public/speakersubmissions/camp-slug/submission-uuid/filename """
|
||||||
|
return 'public/speakersubmissions/%(campslug)s/%(submissionuuid)s/%(filename)s' % {
|
||||||
|
'campslug': instance.camp.slug,
|
||||||
|
'submissionuuidd': instance.uuid,
|
||||||
|
'filename': filename
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SpeakerSubmission(UserSubmittedModel):
|
||||||
|
""" A speaker submission """
|
||||||
|
|
||||||
|
camp = models.ForeignKey(
|
||||||
|
'camps.Camp',
|
||||||
|
related_name='speakersubmissions'
|
||||||
|
)
|
||||||
|
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=150,
|
||||||
|
help_text='Name or alias of the speaker',
|
||||||
|
)
|
||||||
|
|
||||||
|
biography = models.TextField(
|
||||||
|
help_text='Markdown is supported.'
|
||||||
|
)
|
||||||
|
|
||||||
|
picture_large = models.ImageField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
upload_to=get_speakersubmission_picture_upload_path,
|
||||||
|
help_text='A picture of the speaker'
|
||||||
|
)
|
||||||
|
|
||||||
|
picture_small = models.ImageField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
upload_to=get_speakersubmission_picture_upload_path,
|
||||||
|
help_text='A thumbnail of the speaker picture'
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_public(self):
|
def headline(self):
|
||||||
if self.submission_status == self.SUBMISSION_APPROVED:
|
return self.name
|
||||||
return True
|
|
||||||
else:
|
def get_absolute_url(self):
|
||||||
return False
|
return reverse_lazy('speakersubmission_detail', kwargs={'camp_slug': self.camp.slug, 'pk': self.uuid})
|
||||||
|
|
||||||
|
|
||||||
|
class EventSubmission(UserSubmittedModel):
|
||||||
|
""" An event submission """
|
||||||
|
|
||||||
|
camp = models.ForeignKey(
|
||||||
|
'camps.Camp',
|
||||||
|
related_name='eventsubmissions'
|
||||||
|
)
|
||||||
|
|
||||||
|
title = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
help_text='The title of this event',
|
||||||
|
)
|
||||||
|
|
||||||
|
abstract = models.TextField(
|
||||||
|
help_text='The abstract for this event'
|
||||||
|
)
|
||||||
|
|
||||||
|
event_type = models.ForeignKey(
|
||||||
|
'program.EventType',
|
||||||
|
help_text='The type of event',
|
||||||
|
)
|
||||||
|
|
||||||
|
speakers = models.ManyToManyField(
|
||||||
|
'program.SpeakerSubmission',
|
||||||
|
blank=True,
|
||||||
|
help_text='Pick the speaker(s) for this event',
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def headline(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse_lazy('eventsubmission_detail', kwargs={'camp_slug': self.camp.slug, 'pk': self.uuid})
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################################
|
||||||
|
|
||||||
|
|
||||||
class EventLocation(CampRelatedModel):
|
class EventLocation(CampRelatedModel):
|
||||||
""" The places where stuff happens """
|
""" The places where stuff happens """
|
||||||
name = models.CharField(max_length=100)
|
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100
|
||||||
|
)
|
||||||
|
|
||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
icon = models.CharField(max_length=100)
|
|
||||||
camp = models.ForeignKey('camps.Camp', null=True, related_name="eventlocations")
|
icon = models.CharField(
|
||||||
|
max_length=100
|
||||||
|
)
|
||||||
|
|
||||||
|
camp = models.ForeignKey(
|
||||||
|
'camps.Camp',
|
||||||
|
related_name='eventlocations'
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -62,33 +172,68 @@ class EventLocation(CampRelatedModel):
|
||||||
|
|
||||||
class EventType(CreatedUpdatedModel):
|
class EventType(CreatedUpdatedModel):
|
||||||
""" Every event needs to have a type. """
|
""" Every event needs to have a type. """
|
||||||
name = models.CharField(max_length=100, unique=True)
|
name = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
|
||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
color = models.CharField(max_length=50)
|
|
||||||
light_text = models.BooleanField(default=False)
|
color = models.CharField(
|
||||||
notifications = models.BooleanField(default=False)
|
max_length=50
|
||||||
|
)
|
||||||
|
|
||||||
|
light_text = models.BooleanField(
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
notifications = models.BooleanField(
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Event(UserSubmittedModel):
|
class Event(CampRelatedModel):
|
||||||
""" Something that is on the program one or more times. """
|
""" Something that is on the program one or more times. """
|
||||||
title = models.CharField(max_length=255)
|
|
||||||
slug = models.SlugField(blank=True, max_length=255)
|
title = models.CharField(
|
||||||
abstract = models.TextField()
|
max_length=255,
|
||||||
event_type = models.ForeignKey(EventType)
|
help_text='The title of this event',
|
||||||
camp = models.ForeignKey('camps.Camp', null=True, related_name="events")
|
)
|
||||||
|
|
||||||
|
abstract = models.TextField(
|
||||||
|
help_text='The abstract for this event'
|
||||||
|
)
|
||||||
|
|
||||||
|
event_type = models.ForeignKey(
|
||||||
|
'program.EventType',
|
||||||
|
help_text='The type of this event',
|
||||||
|
)
|
||||||
|
|
||||||
|
slug = models.SlugField(
|
||||||
|
blank=True,
|
||||||
|
max_length=255,
|
||||||
|
help_text='The slug for this event, created automatically',
|
||||||
|
)
|
||||||
|
|
||||||
|
camp = models.ForeignKey(
|
||||||
|
'camps.Camp',
|
||||||
|
related_name='events',
|
||||||
|
help_text='The camp this event belongs to',
|
||||||
|
)
|
||||||
|
|
||||||
video_url = models.URLField(
|
video_url = models.URLField(
|
||||||
max_length=1000,
|
max_length=1000,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('URL to the recording.')
|
help_text='URL to the recording'
|
||||||
)
|
)
|
||||||
|
|
||||||
video_recording = models.BooleanField(
|
video_recording = models.BooleanField(
|
||||||
default=True,
|
default=True,
|
||||||
help_text=_('Whether the event will be video recorded or not.')
|
help_text='Do we intend to record video of this event?'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -115,10 +260,22 @@ class Event(UserSubmittedModel):
|
||||||
|
|
||||||
class EventInstance(CampRelatedModel):
|
class EventInstance(CampRelatedModel):
|
||||||
""" An instance of an event """
|
""" An instance of an event """
|
||||||
event = models.ForeignKey('program.event', related_name='instances')
|
|
||||||
|
event = models.ForeignKey(
|
||||||
|
'program.event',
|
||||||
|
related_name='instances'
|
||||||
|
)
|
||||||
|
|
||||||
when = DateTimeRangeField()
|
when = DateTimeRangeField()
|
||||||
notifications_sent = models.BooleanField(default=False)
|
|
||||||
location = models.ForeignKey('program.EventLocation', related_name='eventinstances')
|
notifications_sent = models.BooleanField(
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
location = models.ForeignKey(
|
||||||
|
'program.EventLocation',
|
||||||
|
related_name='eventinstances'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['when']
|
ordering = ['when']
|
||||||
|
@ -168,41 +325,61 @@ def get_speaker_picture_upload_path(instance, filename):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Speaker(UserSubmittedModel):
|
class Speaker(CampRelatedModel):
|
||||||
""" A Person anchoring an event. """
|
""" A Person (co)anchoring one or more events on a camp. """
|
||||||
name = models.CharField(max_length=150)
|
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=150,
|
||||||
|
help_text='Name or alias of the speaker',
|
||||||
|
)
|
||||||
|
|
||||||
biography = models.TextField(
|
biography = models.TextField(
|
||||||
help_text='Markdown is supported.'
|
help_text='Markdown is supported.'
|
||||||
)
|
)
|
||||||
|
|
||||||
picture_small = models.ImageField(
|
picture_small = models.ImageField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
upload_to=get_speaker_picture_upload_path,
|
upload_to=get_speaker_picture_upload_path,
|
||||||
help_text='A thumbnail of your picture'
|
help_text='A thumbnail of the speaker picture'
|
||||||
)
|
)
|
||||||
|
|
||||||
picture_large = models.ImageField(
|
picture_large = models.ImageField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
upload_to=get_speaker_picture_upload_path,
|
upload_to=get_speaker_picture_upload_path,
|
||||||
help_text='A picture of you'
|
help_text='A picture of the speaker'
|
||||||
)
|
)
|
||||||
slug = models.SlugField(blank=True, max_length=255)
|
|
||||||
camp = models.ForeignKey('camps.Camp', null=True, related_name="speakers")
|
slug = models.SlugField(
|
||||||
|
blank=True,
|
||||||
|
max_length=255,
|
||||||
|
help_text='The slug for this speaker, will be autocreated',
|
||||||
|
)
|
||||||
|
|
||||||
|
camp = models.ForeignKey(
|
||||||
|
'camps.Camp',
|
||||||
|
null=True,
|
||||||
|
related_name='speakers',
|
||||||
|
help_text='The camp this speaker belongs to',
|
||||||
|
)
|
||||||
|
|
||||||
events = models.ManyToManyField(
|
events = models.ManyToManyField(
|
||||||
Event,
|
Event,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
help_text='The event(s) this speaker is anchoring',
|
||||||
)
|
)
|
||||||
|
|
||||||
user = models.ForeignKey(
|
submission = models.OneToOneField(
|
||||||
'auth.User',
|
'program.SpeakerSubmission',
|
||||||
on_delete=models.PROTECT,
|
|
||||||
null=True,
|
null=True,
|
||||||
blank=True
|
blank=True,
|
||||||
|
help_text='The speaker submission object this speaker was created from',
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
unique_together = (('camp', 'name'), ('camp', 'slug'), ('camp', 'user'))
|
unique_together = (('camp', 'name'), ('camp', 'slug'))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s (%s)' % (self.name, self.camp)
|
return '%s (%s)' % (self.name, self.camp)
|
||||||
|
@ -215,8 +392,4 @@ class Speaker(UserSubmittedModel):
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse_lazy('speaker_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug})
|
return reverse_lazy('speaker_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug})
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
if self.slug == "create":
|
|
||||||
# this is a reserved word used in urls.py
|
|
||||||
raise ValidationError({'name': 'This name is reserved, please choose another'})
|
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
{% block program_content %}
|
{% block program_content %}
|
||||||
<h3>{% if object %}Update{% else %}Create{% endif %} your {{ camp.title }} speaker biography</h3>
|
<h3>{% if object %}Update{% else %}Create{% endif %} {{ camp.title }} Event Proposal</h3>
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_form form %}
|
{% bootstrap_form form %}
|
||||||
{% bootstrap_button "Save as draft" button_type="submit" button_class="btn-primary" %}
|
{% bootstrap_button "Save as draft" button_type="submit" button_class="btn-primary" %}
|
||||||
{% bootstrap_button "Save and submit" button_type="submit" button_class="btn-primary" %}
|
{% bootstrap_button "Save and submit for approval" button_type="submit" button_class="btn-primary" %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock program_content %}
|
{% endblock program_content %}
|
|
@ -36,6 +36,11 @@
|
||||||
<option value="{{ loc.slug }}" {% if location and location == loc %}selected{% endif %}>{{ loc.name }}</option>
|
<option value="{{ loc.slug }}" {% if location and location == loc %}selected{% endif %}>{{ loc.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<a href="{% url 'submission_list' camp_slug=camp.slug %}" class="btn btn-default">Manage My Proposals</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
{% block program_content %}
|
{% block program_content %}
|
||||||
|
|
||||||
<h3>{{ speaker.name }}</h3>
|
h3>{{ speaker.name }}</h3>
|
||||||
|
|
||||||
{% if speaker.picture_large and speaker.picture_small %}
|
{% if speaker.picture_large and speaker.picture_small %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
{% for speaker in speaker_list %}
|
{% for speaker in speaker_list %}
|
||||||
<a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}" class="list-group-item">
|
<a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}" class="list-group-item">
|
||||||
{{ speaker.name }} ({{ speaker.events.all.count }} event{{ speaker.events.all.count|pluralize }})
|
{{ speaker.name }} ({{ speaker.events.all.count }} event{{ speaker.events.all.count|pluralize }})
|
||||||
|
{% if not speaker.is_public %}(unpublished, {{ speaker.submission_status }}){% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
37
src/program/templates/speakersubmission_detail.html
Normal file
37
src/program/templates/speakersubmission_detail.html
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{% extends 'program_base.html' %}
|
||||||
|
{% load commonmark %}
|
||||||
|
|
||||||
|
{% block program_content %}
|
||||||
|
|
||||||
|
<h2>{{ camp.title }} Speaker Proposal Details</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li class="list">Status: <span class="badge">{{ speakersubmission.submission_status }}</span></li>
|
||||||
|
<li class="list">ID: <span class="badge">{{ speakersubmission.uuid }}</span></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="{% url 'submission_list' camp_slug=camp.slug %}" class="btn btn-primary">Proposal List</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">{{ speakersubmission.name }}</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{% if speakersubmission.picture_large and speakersubmission.picture_small %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 text-container">
|
||||||
|
{{ speakersubmission.biography|commonmark }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<a href="{% url 'speakersubmission_picture' camp_slug=camp.slug pk=speakersubmission.pk picture='large' %}" >
|
||||||
|
<img src="{% url 'speakersubmission_picture' camp_slug=camp.slug pk=speakersubmission.pk picture='thumbnail' %}" alt="{{ camp.title }} speaker picture of {{ speakersubmission.name }}">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ speakersubmission.biography|commonmark }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock program_content %}
|
14
src/program/templates/speakersubmission_form.html
Normal file
14
src/program/templates/speakersubmission_form.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends 'program_base.html' %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
{% block program_content %}
|
||||||
|
<h3>{% if object %}Update{% else %}Create{% endif %} {{ camp.title }} Speaker Proposal</h3>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
{% bootstrap_button "Save as draft" button_type="submit" button_class="btn-primary" %}
|
||||||
|
{% bootstrap_button "Save and submit for approval" button_type="submit" button_class="btn-primary" %}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock program_content %}
|
||||||
|
|
69
src/program/templates/submission_list.html
Normal file
69
src/program/templates/submission_list.html
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
{% extends 'program_base.html' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Proposals | {{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block program_content %}
|
||||||
|
|
||||||
|
<h3>Your {{ camp.title }} Speaker Proposals</h3>
|
||||||
|
{% if speakersubmission_list %}
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for speakersubmission in speakersubmission_list %}
|
||||||
|
<tr>
|
||||||
|
<td><b>{{ speakersubmission.name }}</b></td>
|
||||||
|
<td><span class="badge">{{ speakersubmission.submission_status }}</span></td>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'speakersubmission_update' camp_slug=camp.slug pk=speakersubmission.uuid %}" class="btn btn-primary btn-xs">Modify</a>
|
||||||
|
<a href="#" class="btn btn-danger btn-xs">Delete</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<h3>No speaker proposals found</h3>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<a href="{% url 'speakersubmission_create' camp_slug=camp.slug %}" class="btn btn-primary">Propose New Speaker</a>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<h3>Your {{ camp.title }} Event Proposals</h3>
|
||||||
|
{% if eventsubmission_list %}
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for eventsubmission in eventsubmission_list %}
|
||||||
|
<tr>
|
||||||
|
<td><b>{{ eventsubmission.title }}</b></td>
|
||||||
|
<td><span class="badge">{{ eventsubmission.submission_status }}</span></td>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'eventsubmission_update' camp_slug=camp.slug pk=eventsubmission.uuid %}" class="btn btn-primary btn-xs">Modify</a>
|
||||||
|
<a href="#" class="btn btn-danger btn-xs">Delete</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<h3>No event proposals found</h3>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<a href="{% url 'eventsubmission_create' camp_slug=camp.slug %}" class="btn btn-primary">Propose New Event</a>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -1,85 +1,114 @@
|
||||||
from collections import OrderedDict
|
|
||||||
import datetime
|
|
||||||
from django.views.generic import ListView, TemplateView, DetailView
|
from django.views.generic import ListView, TemplateView, DetailView
|
||||||
from django.views.generic.edit import CreateView, UpdateView
|
from django.views.generic.edit import CreateView, UpdateView
|
||||||
from camps.mixins import CampViewMixin
|
|
||||||
from . import models
|
|
||||||
from django.http import Http404
|
|
||||||
import datetime, os
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.views import View
|
|
||||||
from django.views.decorators.http import require_safe
|
from django.views.decorators.http import require_safe
|
||||||
from django.http import Http404
|
from django.http import Http404, HttpResponse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from camps.mixins import CampViewMixin
|
||||||
|
from .mixins import CreateUserSubmissionMixin, EnsureUnpprovedSubmissionMixin, EnsureUserOwnsSubmissionMixin
|
||||||
|
from . import models
|
||||||
|
import datetime, os
|
||||||
|
|
||||||
|
|
||||||
class SpeakerCreateView(LoginRequiredMixin, CampViewMixin, CreateView):
|
############## submissions ########################################################
|
||||||
model = models.Speaker
|
|
||||||
|
|
||||||
|
class SubmissionListView(LoginRequiredMixin, CampViewMixin, ListView):
|
||||||
|
model = models.SpeakerSubmission
|
||||||
|
template_name = 'submission_list.html'
|
||||||
|
context_object_name = 'speakersubmission_list'
|
||||||
|
|
||||||
|
def get_queryset(self, **kwargs):
|
||||||
|
# only show speaker submissions for the current user
|
||||||
|
return super().get_queryset().filter(user=self.request.user)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
# add eventsubmissions to the context
|
||||||
|
context['eventsubmission_list'] = models.EventSubmission.objects.filter(camp=self.camp, user=self.request.user)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class SpeakerSubmissionCreateView(LoginRequiredMixin, CampViewMixin, CreateUserSubmissionMixin, CreateView):
|
||||||
|
model = models.SpeakerSubmission
|
||||||
fields = ['name', 'biography', 'picture_small', 'picture_large']
|
fields = ['name', 'biography', 'picture_small', 'picture_large']
|
||||||
template_name = 'speaker_form.html'
|
template_name = 'speakersubmission_form.html'
|
||||||
|
|
||||||
|
|
||||||
|
class SpeakerSubmissionUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, EnsureUnpprovedSubmissionMixin, UpdateView):
|
||||||
|
model = models.SpeakerSubmission
|
||||||
|
fields = ['name', 'biography', 'picture_small', 'picture_large']
|
||||||
|
template_name = 'speakersubmission_form.html'
|
||||||
|
|
||||||
|
|
||||||
|
class SpeakerSubmissionDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, DetailView):
|
||||||
|
model = models.SpeakerSubmission
|
||||||
|
template_name = 'speakersubmission_detail.html'
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(require_safe, name='dispatch')
|
||||||
|
class SpeakerSubmissionPictureView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, DetailView):
|
||||||
|
model = models.SpeakerSubmission
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
# first make sure we don't already have a speaker for this user for this camp
|
# is the speaker public, or owned by current user?
|
||||||
try:
|
if not self.get_object().user != request.user:
|
||||||
speaker = models.Speaker.objects.get(user=request.user, camp=self.camp)
|
raise Http404()
|
||||||
except models.Speaker.DoesNotExist:
|
|
||||||
# no speaker exists, just show the create speaker form
|
|
||||||
return super(SpeakerCreateView, self).get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
# speaker already exists, where do we want to redirect?
|
# do we have the requested picture?
|
||||||
if speaker.submission_status == models.Speaker.SUBMISSION_DRAFT:
|
if kwargs['picture'] == 'thumbnail':
|
||||||
messages.info(request, "You already have a draft speaker profile for %s, you can modify and submit it here" % self.camp.title)
|
if self.get_object().picture_small:
|
||||||
return redirect('speaker_edit', camp_slug=self.camp.slug, slug=speaker.slug)
|
picture = self.get_object().picture_small
|
||||||
elif speaker.submission_status == models.Speaker.SUBMISSION_PENDING:
|
|
||||||
messages.info(request, "You already have a pending speaker profile for %s, you can modify and resubmit it here" % self.camp.title)
|
|
||||||
return redirect('speaker_edit', camp_slug=self.camp.slug, slug=speaker.slug)
|
|
||||||
elif speaker.submission_status == models.Speaker.SUBMISSION_REJECTED:
|
|
||||||
messages.info(request, "You already have a rejected speaker profile for %s, you can modify and resubmit it here" % self.camp.title)
|
|
||||||
return redirect('speaker_edit', camp_slug=self.camp.slug, slug=speaker.slug)
|
|
||||||
elif speaker.submission_status == models.Speaker.SUBMISSION_APPROVED:
|
|
||||||
messages.info(request, "You already have an accepted speaker profile for %s, please contact the organisers if you want to modify it." % self.camp.title)
|
|
||||||
return redirect('speaker_detail', camp_slug=self.camp.slug, slug=speaker.slug)
|
|
||||||
else:
|
else:
|
||||||
# unknown submission status!
|
raise Http404()
|
||||||
return
|
elif kwargs['picture'] == 'large':
|
||||||
|
if self.get_object().picture_large:
|
||||||
|
picture = self.get_object().picture_large
|
||||||
|
else:
|
||||||
|
raise Http404()
|
||||||
|
else:
|
||||||
|
raise Http404()
|
||||||
|
|
||||||
def form_valid(self, form):
|
# make nginx return the picture using X-Accel-Redirect
|
||||||
# set camp before saving
|
# (this works for nginx only, other webservers use x-sendfile),
|
||||||
form.instance.camp = self.camp
|
# TODO: what about runserver mode here?
|
||||||
form.instance.user = self.request.user
|
response = HttpResponse()
|
||||||
speaker = form.save()
|
response['X-Accel-Redirect'] = '/public/speakersubmissions/%(campslug)s/%(submissionuuid)s/%(filename)s' % {
|
||||||
return redirect(reverse('speaker_detail', kwargs={'camp_slug': speaker.camp.slug, 'slug': speaker.slug}))
|
'campslug': self.camp.slug,
|
||||||
|
'submissionuuid': self.get_object().uuid,
|
||||||
|
'filename': os.path.basename(picture.name),
|
||||||
|
}
|
||||||
|
response['Content-Type'] = ''
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class SpeakerEditView(LoginRequiredMixin, CampViewMixin, UpdateView):
|
class EventSubmissionCreateView(LoginRequiredMixin, CampViewMixin, CreateUserSubmissionMixin, CreateView):
|
||||||
model = models.Speaker
|
model = models.EventSubmission
|
||||||
fields = ['name', 'biography', 'picture_small', 'picture_large']
|
fields = ['title', 'abstract', 'event_type', 'speakers']
|
||||||
template_name = 'speaker_form.html'
|
template_name = 'eventsubmission_form.html'
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
# call super dispatch now because it ets self.camp which is needed below
|
context = super().get_context_data(**kwargs)
|
||||||
response = super(SpeakerEditView, self).dispatch(request, *args, **kwargs)
|
context['form'].fields['speakers'].queryset = models.SpeakerSubmission.objects.filter(camp=self.camp, user=self.request.user)
|
||||||
|
return context
|
||||||
|
|
||||||
# first make sure that this speaker belongs to the logged in user
|
|
||||||
if self.get_object().user.username != request.user.username:
|
|
||||||
messages.error(request, "No thanks")
|
|
||||||
return redirect(reverse('speaker_detail', kwargs={'camp_slug': self.get_object().camp.slug, 'slug': self.get_object().slug}))
|
|
||||||
|
|
||||||
if self.get_object().submission_status == models.Speaker.SUBMISSION_PENDING:
|
class EventSubmissionUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, EnsureUnpprovedSubmissionMixin, UpdateView):
|
||||||
messages.info(request, "Your speaker profile for %s has already been submitted. If you modify it you will have to resubmit it." % self.get_object().camp.title)
|
model = models.EventSubmission
|
||||||
elif self.get_object().submission_status == models.Speaker.SUBMISSION_REJECTED:
|
fields = ['title', 'abstract', 'event_tyoe', 'speakers']
|
||||||
messages.info(request, "When you are done editing you will have to resubmit your speaker profile." % self.get_object().camp.title)
|
template_name = 'eventsubmission_form.html'
|
||||||
elif self.get_object().submission_status == models.Speaker.SUBMISSION_APPROVED:
|
|
||||||
messages.error(request, "Your speaker profile for %s has already been approved. Please contact the organisers if you want to modify it." % self.get_object().camp.title)
|
|
||||||
return redirect(reverse('speaker_detail', kwargs={'camp_slug': self.get_object().camp.slug, 'slug': self.get_object().slug}))
|
|
||||||
|
|
||||||
# alright, render the form
|
|
||||||
return super(SpeakerEditView, self).dispatch(request, *args, **kwargs)
|
class EventSubmissionDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, DetailView):
|
||||||
|
model = models.EventSubmission
|
||||||
|
template_name = 'eventsubmission_detail.html'
|
||||||
|
|
||||||
|
|
||||||
|
################## speakers ###############################################
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(require_safe, name='dispatch')
|
@method_decorator(require_safe, name='dispatch')
|
||||||
|
@ -87,6 +116,10 @@ class SpeakerPictureView(CampViewMixin, DetailView):
|
||||||
model = models.Speaker
|
model = models.Speaker
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
# is the speaker public, or owned by current user?
|
||||||
|
if not self.get_object().is_public and self.get_object().user != request.user:
|
||||||
|
raise Http404()
|
||||||
|
|
||||||
# do we have the requested picture?
|
# do we have the requested picture?
|
||||||
if kwargs['picture'] == 'thumbnail':
|
if kwargs['picture'] == 'thumbnail':
|
||||||
if self.get_object().picture_small:
|
if self.get_object().picture_small:
|
||||||
|
@ -118,30 +151,13 @@ class SpeakerDetailView(CampViewMixin, DetailView):
|
||||||
model = models.Speaker
|
model = models.Speaker
|
||||||
template_name = 'speaker_detail.html'
|
template_name = 'speaker_detail.html'
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
if not self.get_object().is_public and self.get_object().user != request.user:
|
|
||||||
raise Http404()
|
|
||||||
else:
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class SpeakerListView(CampViewMixin, ListView):
|
class SpeakerListView(CampViewMixin, ListView):
|
||||||
model = models.Speaker
|
model = models.Speaker
|
||||||
template_name = 'speaker_list.html'
|
template_name = 'speaker_list.html'
|
||||||
|
|
||||||
def get_queryset(self, *args, **kwargs):
|
|
||||||
# get all approved speakers
|
|
||||||
speakers = models.Speaker.objects.filter(
|
|
||||||
camp=self.camp,
|
|
||||||
submission_status=models.Speaker.SUBMISSION_APPROVED
|
|
||||||
)
|
|
||||||
# also get the users own speaker, in case he has an unapproved
|
|
||||||
userspeakers = models.Speaker.objects.filter(
|
|
||||||
camp=self.camp,
|
|
||||||
user=self.request.user
|
|
||||||
).exclude(submission_status=models.Speaker.SUBMISSION_APPROVED)
|
|
||||||
|
|
||||||
return speakers | userspeakers
|
################## events ##############################################
|
||||||
|
|
||||||
|
|
||||||
class EventListView(CampViewMixin, ListView):
|
class EventListView(CampViewMixin, ListView):
|
||||||
|
@ -149,6 +165,14 @@ class EventListView(CampViewMixin, ListView):
|
||||||
template_name = 'event_list.html'
|
template_name = 'event_list.html'
|
||||||
|
|
||||||
|
|
||||||
|
class EventDetailView(CampViewMixin, DetailView):
|
||||||
|
model = models.Event
|
||||||
|
template_name = 'schedule_event_detail.html'
|
||||||
|
|
||||||
|
|
||||||
|
################## schedule #############################################
|
||||||
|
|
||||||
|
|
||||||
class ScheduleView(CampViewMixin, TemplateView):
|
class ScheduleView(CampViewMixin, TemplateView):
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
if 'day' in self.kwargs:
|
if 'day' in self.kwargs:
|
||||||
|
@ -227,11 +251,6 @@ class ScheduleView(CampViewMixin, TemplateView):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class EventDetailView(CampViewMixin, DetailView):
|
|
||||||
model = models.Event
|
|
||||||
template_name = 'schedule_event_detail.html'
|
|
||||||
|
|
||||||
|
|
||||||
class CallForSpeakersView(CampViewMixin, TemplateView):
|
class CallForSpeakersView(CampViewMixin, TemplateView):
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
return '%s_call_for_speakers.html' % self.camp.slug
|
return '%s_call_for_speakers.html' % self.camp.slug
|
||||||
|
|
Loading…
Reference in a new issue