rename models and views from submission to proposal

This commit is contained in:
Thomas Steen Rasmussen 2017-03-12 19:06:03 +01:00
parent 89bec82880
commit a51b795bce
17 changed files with 378 additions and 243 deletions

View file

@ -134,33 +134,33 @@ urlpatterns = [
name='schedule_index' name='schedule_index'
), ),
url( url(
r'^submissions/', include([ r'^proposals/', include([
url( url(
r'^$', r'^$',
SubmissionListView.as_view(), ProposalListView.as_view(),
name='submission_list', name='proposal_list',
), ),
url( url(
r'^speakers/', include([ r'^speakers/', include([
url( url(
r'^create/$', r'^create/$',
SpeakerSubmissionCreateView.as_view(), SpeakerProposalCreateView.as_view(),
name='speakersubmission_create' name='speakerproposal_create'
), ),
url( url(
r'^(?P<pk>[a-f0-9-]+)/$', r'^(?P<pk>[a-f0-9-]+)/$',
SpeakerSubmissionDetailView.as_view(), SpeakerProposalDetailView.as_view(),
name='speakersubmission_detail' name='speakerproposal_detail'
), ),
url( url(
r'^(?P<pk>[a-f0-9-]+)/edit/$', r'^(?P<pk>[a-f0-9-]+)/edit/$',
SpeakerSubmissionUpdateView.as_view(), SpeakerProposalUpdateView.as_view(),
name='speakersubmission_update' name='speakerproposal_update'
), ),
url( url(
r'^(?P<pk>[a-f0-9-]+)/pictures/(?P<picture>[-_\w+]+)/$', r'^(?P<pk>[a-f0-9-]+)/pictures/(?P<picture>[-_\w+]+)/$',
SpeakerSubmissionPictureView.as_view(), SpeakerProposalPictureView.as_view(),
name='speakersubmission_picture', name='speakerproposal_picture',
), ),
]) ])
), ),
@ -168,18 +168,18 @@ urlpatterns = [
r'^events/', include([ r'^events/', include([
url( url(
r'^create/$', r'^create/$',
EventSubmissionCreateView.as_view(), EventProposalCreateView.as_view(),
name='eventsubmission_create' name='eventproposal_create'
), ),
url( url(
r'^(?P<pk>[a-f0-9-]+)/$', r'^(?P<pk>[a-f0-9-]+)/$',
EventSubmissionDetailView.as_view(), EventProposalDetailView.as_view(),
name='eventsubmission_detail' name='eventproposal_detail'
), ),
url( url(
r'^(?P<pk>[a-f0-9-]+)/edit/$', r'^(?P<pk>[a-f0-9-]+)/edit/$',
EventSubmissionUpdateView.as_view(), EventProposalUpdateView.as_view(),
name='eventsubmission_update' name='eventproposal_update'
), ),
]) ])
), ),

View file

@ -33,6 +33,7 @@
Credit Notes Credit Notes
</a> </a>
{% endif %} {% endif %}
{% endif %} {% endif %}
</p> </p>
<hr /> <hr />

View file

@ -1,15 +1,15 @@
from django.contrib import admin from django.contrib import admin
from .models import Event, Speaker, EventType, EventInstance, EventLocation, SpeakerSubmission, EventSubmission from .models import Event, Speaker, EventType, EventInstance, EventLocation, SpeakerProposal, EventProposal
@admin.register(SpeakerSubmission) @admin.register(SpeakerProposal)
class SpeakerSubmissionAdmin(admin.ModelAdmin): class SpeakerProposalAdmin(admin.ModelAdmin):
pass pass
@admin.register(EventSubmission) @admin.register(EventProposal)
class EventSubmissionAdmin(admin.ModelAdmin): class EventProposalAdmin(admin.ModelAdmin):
pass pass

View file

@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-12 17:57
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', '0032_auto_20170312_1556'),
]
operations = [
migrations.CreateModel(
name='EventProposal',
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)),
('proposal_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='eventproposals', to='camps.Camp')),
('event_type', models.ForeignKey(help_text='The type of event', on_delete=django.db.models.deletion.CASCADE, to='program.EventType')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='SpeakerProposal',
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)),
('proposal_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_speakerproposal_picture_upload_path)),
('picture_small', models.ImageField(blank=True, help_text='A thumbnail of the speaker picture', null=True, upload_to=program.models.get_speakerproposal_picture_upload_path)),
('camp', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='speakerproposals', 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='eventsubmission',
name='camp',
),
migrations.RemoveField(
model_name='eventsubmission',
name='event_type',
),
migrations.RemoveField(
model_name='eventsubmission',
name='speakers',
),
migrations.RemoveField(
model_name='eventsubmission',
name='user',
),
migrations.RemoveField(
model_name='speakersubmission',
name='camp',
),
migrations.RemoveField(
model_name='speakersubmission',
name='user',
),
migrations.RemoveField(
model_name='speaker',
name='submission',
),
migrations.DeleteModel(
name='EventSubmission',
),
migrations.DeleteModel(
name='SpeakerSubmission',
),
migrations.AddField(
model_name='eventproposal',
name='speakers',
field=models.ManyToManyField(blank=True, help_text='Pick the speaker(s) for this event', to='program.SpeakerProposal'),
),
migrations.AddField(
model_name='eventproposal',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='speaker',
name='proposal',
field=models.OneToOneField(blank=True, help_text='The speaker proposal object this speaker was created from', null=True, on_delete=django.db.models.deletion.CASCADE, to='program.SpeakerProposal'),
),
]

View file

@ -2,34 +2,46 @@ from django.views.generic.detail import SingleObjectMixin
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from . import models from . import models
from django.contrib import messages
class CreateUserSubmissionMixin(SingleObjectMixin): class CreateUserProposalMixin(SingleObjectMixin):
def form_valid(self, form): def form_valid(self, form):
# set camp and user before saving # set camp and user before saving
form.instance.camp = self.camp form.instance.camp = self.camp
form.instance.user = self.request.user form.instance.user = self.request.user
speaker = form.save() speaker = form.save()
return redirect(reverse('submission_list', kwargs={'camp_slug': self.camp.slug})) return redirect(reverse('proposal_list', kwargs={'camp_slug': self.camp.slug}))
class EnsureUnpprovedSubmissionMixin(SingleObjectMixin): class EnsureUnpprovedProposalMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
# do not permit editing if the submission is already approved # do not permit editing if the proposal is already approved
if self.get_object().submission_status == models.UserSubmittedModel.SUBMISSION_APPROVED: if self.get_object().proposal_status == models.UserSubmittedModel.PROPOSAL_APPROVED:
messages.error(request, "This submission has already been approved. Please contact the organisers if you need to modify something." % self.camp.title) messages.error(request, "This proposal 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})) return redirect(reverse('proposal_list', kwargs={'camp_slug': self.camp.slug}))
# alright, continue with the request # alright, continue with the request
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
class EnsureUserOwnsSubmissionMixin(SingleObjectMixin): class EnsureWritableCampMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
# make sure that this submission belongs to the logged in user # 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}))
# alright, continue with the request
return super().dispatch(request, *args, **kwargs)
class EnsureUserOwnsProposalMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs):
# make sure that this proposal belongs to the logged in user
if self.get_object().user.username != request.user.username: if self.get_object().user.username != request.user.username:
messages.error(request, "No thanks") messages.error(request, "No thanks")
return redirect(reverse('submissions_list', kwargs={'camp_slug': self.camp.slug})) return redirect(reverse('proposal_list', kwargs={'camp_slug': self.camp.slug}))
# alright, continue with the request # alright, continue with the request
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)

View file

@ -13,7 +13,7 @@ import uuid
class UserSubmittedModel(CampRelatedModel): class UserSubmittedModel(CampRelatedModel):
""" """
An abstract model containing the stuff that is shared An abstract model containing the stuff that is shared
between the SpeakerSubmission and EventSubmission models. between the SpeakerProposal and EventProposal models.
""" """
class Meta: class Meta:
@ -29,50 +29,59 @@ class UserSubmittedModel(CampRelatedModel):
'auth.User', 'auth.User',
) )
SUBMISSION_DRAFT = 'draft' PROPOSAL_DRAFT = 'draft'
SUBMISSION_PENDING = 'pending' PROPOSAL_PENDING = 'pending'
SUBMISSION_APPROVED = 'approved' PROPOSAL_APPROVED = 'approved'
SUBMISSION_REJECTED = 'rejected' PROPOSAL_REJECTED = 'rejected'
SUBMISSION_STATUSES = [ PROPOSAL_STATUSES = [
SUBMISSION_DRAFT, PROPOSAL_DRAFT,
SUBMISSION_PENDING, PROPOSAL_PENDING,
SUBMISSION_APPROVED, PROPOSAL_APPROVED,
SUBMISSION_REJECTED PROPOSAL_REJECTED
] ]
SUBMISSION_STATUS_CHOICES = [ PROPOSAL_STATUS_CHOICES = [
(SUBMISSION_DRAFT, 'Draft'), (PROPOSAL_DRAFT, 'Draft'),
(SUBMISSION_PENDING, 'Pending approval'), (PROPOSAL_PENDING, 'Pending approval'),
(SUBMISSION_APPROVED, 'Approved'), (PROPOSAL_APPROVED, 'Approved'),
(SUBMISSION_REJECTED, 'Rejected'), (PROPOSAL_REJECTED, 'Rejected'),
] ]
submission_status = models.CharField( proposal_status = models.CharField(
max_length=50, max_length=50,
choices=SUBMISSION_STATUS_CHOICES, choices=PROPOSAL_STATUS_CHOICES,
default=SUBMISSION_DRAFT, default=PROPOSAL_DRAFT,
) )
def __str__(self): def __str__(self):
return '%s (submitted by: %s, status: %s)' % (self.headline, self.user, self.submission_status) return '%s (submitted by: %s, status: %s)' % (self.headline, self.user, self.proposal_status)
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,
'proposaluuidd': instance.uuid,
'filename': filename
}
def get_speakersubmission_picture_upload_path(instance, filename): def get_speakersubmission_picture_upload_path(instance, filename):
""" We want speakersubmission pictures saved as MEDIA_ROOT/public/speakersubmissions/camp-slug/submission-uuid/filename """ """ We want speakerproposal pictures saved as MEDIA_ROOT/public/speakerproposals/camp-slug/proposal-uuid/filename """
return 'public/speakersubmissions/%(campslug)s/%(submissionuuid)s/%(filename)s' % { return 'public/speakerproposals/%(campslug)s/%(proposaluuid)s/%(filename)s' % {
'campslug': instance.camp.slug, 'campslug': instance.camp.slug,
'submissionuuidd': instance.uuid, 'proposaluuidd': instance.uuid,
'filename': filename 'filename': filename
} }
class SpeakerSubmission(UserSubmittedModel):
""" A speaker submission """ class SpeakerProposal(UserSubmittedModel):
""" A speaker proposal """
camp = models.ForeignKey( camp = models.ForeignKey(
'camps.Camp', 'camps.Camp',
related_name='speakersubmissions' related_name='speakerproposals'
) )
name = models.CharField( name = models.CharField(
@ -87,14 +96,14 @@ class SpeakerSubmission(UserSubmittedModel):
picture_large = models.ImageField( picture_large = models.ImageField(
null=True, null=True,
blank=True, blank=True,
upload_to=get_speakersubmission_picture_upload_path, upload_to=get_speakerproposal_picture_upload_path,
help_text='A picture of the speaker' help_text='A picture of the speaker'
) )
picture_small = models.ImageField( picture_small = models.ImageField(
null=True, null=True,
blank=True, blank=True,
upload_to=get_speakersubmission_picture_upload_path, upload_to=get_speakerproposal_picture_upload_path,
help_text='A thumbnail of the speaker picture' help_text='A thumbnail of the speaker picture'
) )
@ -103,15 +112,15 @@ class SpeakerSubmission(UserSubmittedModel):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy('speakersubmission_detail', kwargs={'camp_slug': self.camp.slug, 'pk': self.uuid}) return reverse_lazy('speakerproposal_detail', kwargs={'camp_slug': self.camp.slug, 'pk': self.uuid})
class EventSubmission(UserSubmittedModel): class EventProposal(UserSubmittedModel):
""" An event submission """ """ An event proposal """
camp = models.ForeignKey( camp = models.ForeignKey(
'camps.Camp', 'camps.Camp',
related_name='eventsubmissions' related_name='eventproposals'
) )
title = models.CharField( title = models.CharField(
@ -129,7 +138,7 @@ class EventSubmission(UserSubmittedModel):
) )
speakers = models.ManyToManyField( speakers = models.ManyToManyField(
'program.SpeakerSubmission', 'program.SpeakerProposal',
blank=True, blank=True,
help_text='Pick the speaker(s) for this event', help_text='Pick the speaker(s) for this event',
) )
@ -139,7 +148,7 @@ class EventSubmission(UserSubmittedModel):
return self.title return self.title
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy('eventsubmission_detail', kwargs={'camp_slug': self.camp.slug, 'pk': self.uuid}) return reverse_lazy('eventproposal_detail', kwargs={'camp_slug': self.camp.slug, 'pk': self.uuid})
############################################################################################# #############################################################################################
@ -379,11 +388,11 @@ class Speaker(CampRelatedModel):
help_text='The event(s) this speaker is anchoring', help_text='The event(s) this speaker is anchoring',
) )
submission = models.OneToOneField( proposal = models.OneToOneField(
'program.SpeakerSubmission', 'program.SpeakerProposal',
null=True, null=True,
blank=True, blank=True,
help_text='The speaker submission object this speaker was created from', help_text='The speaker proposal object this speaker was created from',
) )
class Meta: class Meta:

View file

@ -0,0 +1,24 @@
{% extends 'program_base.html' %}
{% load commonmark %}
{% block program_content %}
<h2>{{ camp.title }} Event Proposal Details</h2>
<ul>
<li class="list">Status: <span class="badge">{{ eventproposal.proposal_status }}</span></li>
<li class="list">ID: <span class="badge">{{ eventproposal.uuid }}</span></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">{{ eventproposal.title }}</div>
<div class="panel-body">
{{ eventproposal.abstract|commonmark }}
</div>
</div>
<p>
<a href="{% url 'proposal_list' camp_slug=camp.slug %}" class="btn btn-primary">Back to List</a>
</p>
{% endblock program_content %}

View file

@ -6,8 +6,7 @@
<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 draft" 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 %}

View file

@ -1,24 +0,0 @@
{% extends 'program_base.html' %}
{% load commonmark %}
{% block program_content %}
<h2>{{ camp.title }} Event Proposal Details</h2>
<ul>
<li class="list">Status: <span class="badge">{{ eventsubmission.submission_status }}</span></li>
<li class="list">ID: <span class="badge">{{ eventsubmission.uuid }}</span></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">{{ eventsubmission.title }}</div>
<div class="panel-body">
{{ eventsubmission.abstract|commonmark }}
</div>
</div>
<p>
<a href="{% url 'submission_list' camp_slug=camp.slug %}" class="btn btn-primary">Back to List</a>
</p>
{% endblock program_content %}

View file

@ -0,0 +1,83 @@
{% extends 'program_base.html' %}
{% block title %}
Proposals | {{ block.super }}
{% endblock %}
{% block program_content %}
<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>
<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 %}
{% if not camp.read_only %}
<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>
<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 %}
<a href="{% url 'eventproposal_create' camp_slug=camp.slug %}" class="btn btn-primary btn-sm">Propose New Event</a>
{% endif %}
{% endblock %}

View file

@ -38,7 +38,7 @@
</select> </select>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<a href="{% url 'submission_list' camp_slug=camp.slug %}" class="btn btn-default">Manage My Proposals</a> <a href="{% url 'proposal_list' camp_slug=camp.slug %}" class="btn btn-default">Manage My Proposals</a>
{% endif %} {% endif %}
</div> </div>

View file

@ -11,7 +11,6 @@
{% 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>

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">{{ speakerproposal.proposal_status }}</span></li>
<li class="list">ID: <span class="badge">{{ speakerproposal.uuid }}</span></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">{{ speakerproposal.name }}</div>
<div class="panel-body">
{% if speakerproposal.picture_large and speakerproposal.picture_small %}
<div class="row">
<div class="col-md-8 text-container">
{{ 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>
</div>
</div>
{% else %}
{{ speakerproposal.biography|commonmark }}
{% endif %}
</div>
</div>
<p>
<a href="{% url 'proposal_list' camp_slug=camp.slug %}" class="btn btn-primary">Back to List</a>
</p>
{% endblock program_content %}

View file

@ -6,8 +6,7 @@
<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 draft" 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 %}

View file

@ -1,37 +0,0 @@
{% 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>
<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>
<p>
<a href="{% url 'submission_list' camp_slug=camp.slug %}" class="btn btn-primary">Back to List</a>
</p>
{% endblock program_content %}

View file

@ -1,71 +0,0 @@
{% 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_detail' camp_slug=camp.slug pk=speakersubmission.uuid %}" class="btn btn-primary btn-xs">Details</a>
<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 btn-disables" disabled>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_detail' camp_slug=camp.slug pk=eventsubmission.uuid %}" class="btn btn-primary btn-xs">Details</a>
<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 btn-disabled" disabled>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

@ -9,50 +9,53 @@ 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 camps.mixins import CampViewMixin
from .mixins import CreateUserSubmissionMixin, EnsureUnpprovedSubmissionMixin, EnsureUserOwnsSubmissionMixin from .mixins import CreateUserProposalMixin, EnsureUnpprovedProposalMixin, EnsureUserOwnsProposalMixin, EnsureWritableCampMixin
from . import models from . import models
import datetime, os import datetime, os
############## submissions ######################################################## ############## proposals ########################################################
class SubmissionListView(LoginRequiredMixin, CampViewMixin, ListView): class ProposalListView(LoginRequiredMixin, CampViewMixin, ListView):
model = models.SpeakerSubmission model = models.SpeakerProposal
template_name = 'submission_list.html' template_name = 'proposal_list.html'
context_object_name = 'speakersubmission_list' context_object_name = 'speakerproposal_list'
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
# only show speaker submissions for the current user # only show speaker proposals for the current user
return super().get_queryset().filter(user=self.request.user) return super().get_queryset().filter(user=self.request.user)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
# add eventsubmissions to the context # add eventproposals to the context
context['eventsubmission_list'] = models.EventSubmission.objects.filter(camp=self.camp, user=self.request.user) context['eventproposal_list'] = models.EventProposal.objects.filter(camp=self.camp, user=self.request.user)
return context return context
class SpeakerSubmissionCreateView(LoginRequiredMixin, CampViewMixin, CreateUserSubmissionMixin, CreateView): class SpeakerProposalCreateView(LoginRequiredMixin, CampViewMixin, CreateUserProposalMixin, EnsureWritableCampMixin, CreateView):
model = models.SpeakerSubmission model = models.SpeakerProposal
fields = ['name', 'biography', 'picture_small', 'picture_large'] fields = ['name', 'biography', 'picture_small', 'picture_large']
template_name = 'speakersubmission_form.html' template_name = 'speakerproposal_form.html'
class SpeakerSubmissionUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, EnsureUnpprovedSubmissionMixin, UpdateView): class SpeakerProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureUnpprovedProposalMixin, EnsureWritableCampMixin, UpdateView):
model = models.SpeakerSubmission model = models.SpeakerProposal
fields = ['name', 'biography', 'picture_small', 'picture_large'] fields = ['name', 'biography', 'picture_small', 'picture_large']
template_name = 'speakersubmission_form.html' template_name = 'speakerproposal_form.html'
def get_success_url(self):
return reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
class SpeakerSubmissionDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, DetailView): class SpeakerProposalDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView):
model = models.SpeakerSubmission model = models.SpeakerProposal
template_name = 'speakersubmission_detail.html' template_name = 'speakerproposal_detail.html'
@method_decorator(require_safe, name='dispatch') @method_decorator(require_safe, name='dispatch')
class SpeakerSubmissionPictureView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, DetailView): class SpeakerProposalPictureView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView):
model = models.SpeakerSubmission model = models.SpeakerProposal
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
# is the speaker public, or owned by current user? # is the speaker public, or owned by current user?
@ -77,42 +80,39 @@ class SpeakerSubmissionPictureView(LoginRequiredMixin, CampViewMixin, EnsureUser
# (this works for nginx only, other webservers use x-sendfile), # (this works for nginx only, other webservers use x-sendfile),
# TODO: what about runserver mode here? # TODO: what about runserver mode here?
response = HttpResponse() response = HttpResponse()
response['X-Accel-Redirect'] = '/public/speakersubmissions/%(campslug)s/%(submissionuuid)s/%(filename)s' % { response['X-Accel-Redirect'] = '/public/speakerproposals/%(campslug)s/%(proposaluuid)s/%(filename)s' % {
'campslug': self.camp.slug, 'campslug': self.camp.slug,
'submissionuuid': self.get_object().uuid, 'proposaluuid': self.get_object().uuid,
'filename': os.path.basename(picture.name), 'filename': os.path.basename(picture.name),
} }
response['Content-Type'] = '' response['Content-Type'] = ''
return response return response
class EventSubmissionCreateView(LoginRequiredMixin, CampViewMixin, CreateUserSubmissionMixin, CreateView): class EventProposalCreateView(LoginRequiredMixin, CampViewMixin, CreateUserProposalMixin, EnsureWritableCampMixin, CreateView):
model = models.EventSubmission model = models.EventProposal
fields = ['title', 'abstract', 'event_type', 'speakers'] fields = ['title', 'abstract', 'event_type', 'speakers']
template_name = 'eventsubmission_form.html' template_name = 'eventproposal_form.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['form'].fields['speakers'].queryset = models.SpeakerSubmission.objects.filter(camp=self.camp, user=self.request.user) 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['form'].fields['event_type'].queryset = models.EventType.objects.filter(public=True)
return context return context
class EventSubmissionUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, EnsureUnpprovedSubmissionMixin, UpdateView): class EventProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureUnpprovedProposalMixin, EnsureWritableCampMixin, UpdateView):
model = models.EventSubmission model = models.EventProposal
fields = ['title', 'abstract', 'event_type', 'speakers'] fields = ['title', 'abstract', 'event_type', 'speakers']
template_name = 'eventsubmission_form.html' template_name = 'eventproposal_form.html'
def get_context_data(self, **kwargs): def get_success_url(self):
context = super().get_context_data(**kwargs) return reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
context['form'].fields['speakers'].queryset = models.SpeakerSubmission.objects.filter(camp=self.camp, user=self.request.user)
context['form'].fields['event_type'].queryset = models.EventType.objects.filter(public=True)
return context
class EventSubmissionDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, DetailView): class EventProposalDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView):
model = models.EventSubmission model = models.EventProposal
template_name = 'eventsubmission_detail.html' template_name = 'eventproposal_detail.html'
################## speakers ############################################### ################## speakers ###############################################