rework speaker and talk proposal stuff

This commit is contained in:
Thomas Steen Rasmussen 2017-03-12 15:43:41 +01:00
parent 1162a72627
commit ad3b826844
15 changed files with 714 additions and 136 deletions

View File

@ -133,6 +133,58 @@ urlpatterns = [
ScheduleView.as_view(),
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(
r'^speakers/', include([
url(
@ -140,21 +192,11 @@ urlpatterns = [
SpeakerListView.as_view(),
name='speaker_index'
),
url(
r'^create/$',
SpeakerCreateView.as_view(),
name='speaker_create'
),
url(
r'^(?P<slug>[-_\w+]+)/$',
SpeakerDetailView.as_view(),
name='speaker_detail'
),
url(
r'^(?P<slug>[-_\w+]+)/edit/$',
SpeakerEditView.as_view(),
name='speaker_edit'
),
url(
r'^(?P<slug>[-_\w+]+)/pictures/(?P<picture>[-_\w+]+)/$',
SpeakerPictureView.as_view(),

View File

@ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404
class CampViewMixin(object):
def dispatch(self, request, *args, **kwargs):
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):
queryset = super(CampViewMixin, self).get_queryset()

View File

@ -1,6 +1,16 @@
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)

View 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')]),
),
]

View 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
View 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)

View File

@ -7,12 +7,28 @@ from utils.models import CreatedUpdatedModel, CampRelatedModel
from django.core.exceptions import ValidationError
from datetime import timedelta
from django.core.urlresolvers import reverse_lazy
import uuid
class UserSubmittedModel(CampRelatedModel):
"""
An abstract model containing the stuff that is shared
between the SpeakerSubmission and EventSubmission models.
"""
class Meta:
abstract = True
uuid = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False,
)
user = models.ForeignKey(
'auth.User',
)
SUBMISSION_DRAFT = 'draft'
SUBMISSION_PENDING = 'pending'
SUBMISSION_APPROVED = 'approved'
@ -38,20 +54,114 @@ class UserSubmittedModel(CampRelatedModel):
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
def is_public(self):
if self.submission_status == self.SUBMISSION_APPROVED:
return True
else:
return False
def headline(self):
return self.name
def get_absolute_url(self):
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):
""" The places where stuff happens """
name = models.CharField(max_length=100)
name = models.CharField(
max_length=100
)
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):
return self.name
@ -62,33 +172,68 @@ class EventLocation(CampRelatedModel):
class EventType(CreatedUpdatedModel):
""" 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()
color = models.CharField(max_length=50)
light_text = models.BooleanField(default=False)
notifications = models.BooleanField(default=False)
color = models.CharField(
max_length=50
)
light_text = models.BooleanField(
default=False
)
notifications = models.BooleanField(
default=False
)
def __str__(self):
return self.name
class Event(UserSubmittedModel):
class Event(CampRelatedModel):
""" Something that is on the program one or more times. """
title = models.CharField(max_length=255)
slug = models.SlugField(blank=True, max_length=255)
abstract = models.TextField()
event_type = models.ForeignKey(EventType)
camp = models.ForeignKey('camps.Camp', null=True, related_name="events")
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 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(
max_length=1000,
null=True,
blank=True,
help_text=_('URL to the recording.')
help_text='URL to the recording'
)
video_recording = models.BooleanField(
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:
@ -115,10 +260,22 @@ class Event(UserSubmittedModel):
class EventInstance(CampRelatedModel):
""" An instance of an event """
event = models.ForeignKey('program.event', related_name='instances')
event = models.ForeignKey(
'program.event',
related_name='instances'
)
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:
ordering = ['when']
@ -168,41 +325,61 @@ def get_speaker_picture_upload_path(instance, filename):
}
class Speaker(UserSubmittedModel):
""" A Person anchoring an event. """
name = models.CharField(max_length=150)
class Speaker(CampRelatedModel):
""" A Person (co)anchoring one or more events on a camp. """
name = models.CharField(
max_length=150,
help_text='Name or alias of the speaker',
)
biography = models.TextField(
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 your picture'
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 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(
Event,
blank=True,
help_text='The event(s) this speaker is anchoring',
)
user = models.ForeignKey(
'auth.User',
on_delete=models.PROTECT,
submission = models.OneToOneField(
'program.SpeakerSubmission',
null=True,
blank=True
blank=True,
help_text='The speaker submission object this speaker was created from',
)
class Meta:
ordering = ['name']
unique_together = (('camp', 'name'), ('camp', 'slug'), ('camp', 'user'))
unique_together = (('camp', 'name'), ('camp', 'slug'))
def __str__(self):
return '%s (%s)' % (self.name, self.camp)
@ -215,8 +392,4 @@ class Speaker(UserSubmittedModel):
def get_absolute_url(self):
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'})

View File

@ -2,12 +2,12 @@
{% load bootstrap3 %}
{% 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">
{% csrf_token %}
{% bootstrap_form form %}
{% 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>
{% endblock program_content %}

View File

@ -36,6 +36,11 @@
<option value="{{ loc.slug }}" {% if location and location == loc %}selected{% endif %}>{{ loc.name }}</option>
{% endfor %}
</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>
</form>
</div>

View File

@ -3,7 +3,7 @@
{% block program_content %}
<h3>{{ speaker.name }}</h3>
h3>{{ speaker.name }}</h3>
{% if speaker.picture_large and speaker.picture_small %}
<div class="row">

View File

@ -11,6 +11,7 @@
{% for speaker in speaker_list %}
<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 }})
{% if not speaker.is_public %}(unpublished, {{ speaker.submission_status }}){% endif %}
</a>
{% endfor %}
</div>

View 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 %}

View 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 %}

View 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 %}

View File

@ -1,85 +1,114 @@
from collections import OrderedDict
import datetime
from django.views.generic import ListView, TemplateView, DetailView
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.views import View
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.http import HttpResponse
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.shortcuts import redirect
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):
model = models.Speaker
############## submissions ########################################################
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']
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):
# first make sure we don't already have a speaker for this user for this camp
try:
speaker = models.Speaker.objects.get(user=request.user, camp=self.camp)
except models.Speaker.DoesNotExist:
# no speaker exists, just show the create speaker form
return super(SpeakerCreateView, self).get(request, *args, **kwargs)
# is the speaker public, or owned by current user?
if not self.get_object().user != request.user:
raise Http404()
# speaker already exists, where do we want to redirect?
if speaker.submission_status == models.Speaker.SUBMISSION_DRAFT:
messages.info(request, "You already have a draft speaker profile for %s, you can modify and submit it here" % self.camp.title)
return redirect('speaker_edit', camp_slug=self.camp.slug, slug=speaker.slug)
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)
# do we have the requested picture?
if kwargs['picture'] == 'thumbnail':
if self.get_object().picture_small:
picture = self.get_object().picture_small
else:
raise Http404()
elif kwargs['picture'] == 'large':
if self.get_object().picture_large:
picture = self.get_object().picture_large
else:
raise Http404()
else:
# unknown submission status!
return
raise Http404()
def form_valid(self, form):
# set camp before saving
form.instance.camp = self.camp
form.instance.user = self.request.user
speaker = form.save()
return redirect(reverse('speaker_detail', kwargs={'camp_slug': speaker.camp.slug, 'slug': speaker.slug}))
# make nginx return the picture using X-Accel-Redirect
# (this works for nginx only, other webservers use x-sendfile),
# TODO: what about runserver mode here?
response = HttpResponse()
response['X-Accel-Redirect'] = '/public/speakersubmissions/%(campslug)s/%(submissionuuid)s/%(filename)s' % {
'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):
model = models.Speaker
fields = ['name', 'biography', 'picture_small', 'picture_large']
template_name = 'speaker_form.html'
class EventSubmissionCreateView(LoginRequiredMixin, CampViewMixin, CreateUserSubmissionMixin, CreateView):
model = models.EventSubmission
fields = ['title', 'abstract', 'event_type', 'speakers']
template_name = 'eventsubmission_form.html'
def dispatch(self, request, *args, **kwargs):
# call super dispatch now because it ets self.camp which is needed below
response = super(SpeakerEditView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**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:
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)
elif self.get_object().submission_status == models.Speaker.SUBMISSION_REJECTED:
messages.info(request, "When you are done editing you will have to resubmit your speaker profile." % self.get_object().camp.title)
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}))
class EventSubmissionUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, EnsureUnpprovedSubmissionMixin, UpdateView):
model = models.EventSubmission
fields = ['title', 'abstract', 'event_tyoe', 'speakers']
template_name = 'eventsubmission_form.html'
# 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')
@ -87,6 +116,10 @@ class SpeakerPictureView(CampViewMixin, DetailView):
model = models.Speaker
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?
if kwargs['picture'] == 'thumbnail':
if self.get_object().picture_small:
@ -118,30 +151,13 @@ class SpeakerDetailView(CampViewMixin, DetailView):
model = models.Speaker
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):
model = models.Speaker
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):
@ -149,6 +165,14 @@ class EventListView(CampViewMixin, ListView):
template_name = 'event_list.html'
class EventDetailView(CampViewMixin, DetailView):
model = models.Event
template_name = 'schedule_event_detail.html'
################## schedule #############################################
class ScheduleView(CampViewMixin, TemplateView):
def get_template_names(self):
if 'day' in self.kwargs:
@ -227,11 +251,6 @@ class ScheduleView(CampViewMixin, TemplateView):
return context
class EventDetailView(CampViewMixin, DetailView):
model = models.Event
template_name = 'schedule_event_detail.html'
class CallForSpeakersView(CampViewMixin, TemplateView):
def get_template_names(self):
return '%s_call_for_speakers.html' % self.camp.slug