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

View File

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

View File

@ -1,15 +1,15 @@
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)
class SpeakerSubmissionAdmin(admin.ModelAdmin):
@admin.register(SpeakerProposal)
class SpeakerProposalAdmin(admin.ModelAdmin):
pass
@admin.register(EventSubmission)
class EventSubmissionAdmin(admin.ModelAdmin):
@admin.register(EventProposal)
class EventProposalAdmin(admin.ModelAdmin):
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.urls import reverse
from . import models
from django.contrib import messages
class CreateUserSubmissionMixin(SingleObjectMixin):
class CreateUserProposalMixin(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}))
return redirect(reverse('proposal_list', kwargs={'camp_slug': self.camp.slug}))
class EnsureUnpprovedSubmissionMixin(SingleObjectMixin):
class EnsureUnpprovedProposalMixin(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}))
# do not permit editing if the proposal is already approved
if self.get_object().proposal_status == models.UserSubmittedModel.PROPOSAL_APPROVED:
messages.error(request, "This proposal has already been approved. Please contact the organisers if you need to modify something." % self.camp.title)
return redirect(reverse('proposal_list', kwargs={'camp_slug': self.camp.slug}))
# alright, continue with the request
return super().dispatch(request, *args, **kwargs)
class EnsureUserOwnsSubmissionMixin(SingleObjectMixin):
class EnsureWritableCampMixin(SingleObjectMixin):
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:
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
return super().dispatch(request, *args, **kwargs)

View File

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

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">
{% 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" %}
{% bootstrap_button "Save draft" button_type="submit" button_class="btn-primary" %}
</form>
{% 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>
{% 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 %}
</div>

View File

@ -11,7 +11,6 @@
{% 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">{{ 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">
{% 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" %}
{% bootstrap_button "Save draft" button_type="submit" button_class="btn-primary" %}
</form>
{% 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.urls import reverse
from camps.mixins import CampViewMixin
from .mixins import CreateUserSubmissionMixin, EnsureUnpprovedSubmissionMixin, EnsureUserOwnsSubmissionMixin
from .mixins import CreateUserProposalMixin, EnsureUnpprovedProposalMixin, EnsureUserOwnsProposalMixin, EnsureWritableCampMixin
from . import models
import datetime, os
############## submissions ########################################################
############## proposals ########################################################
class SubmissionListView(LoginRequiredMixin, CampViewMixin, ListView):
model = models.SpeakerSubmission
template_name = 'submission_list.html'
context_object_name = 'speakersubmission_list'
class ProposalListView(LoginRequiredMixin, CampViewMixin, ListView):
model = models.SpeakerProposal
template_name = 'proposal_list.html'
context_object_name = 'speakerproposal_list'
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)
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)
# add eventproposals to the context
context['eventproposal_list'] = models.EventProposal.objects.filter(camp=self.camp, user=self.request.user)
return context
class SpeakerSubmissionCreateView(LoginRequiredMixin, CampViewMixin, CreateUserSubmissionMixin, CreateView):
model = models.SpeakerSubmission
class SpeakerProposalCreateView(LoginRequiredMixin, CampViewMixin, CreateUserProposalMixin, EnsureWritableCampMixin, CreateView):
model = models.SpeakerProposal
fields = ['name', 'biography', 'picture_small', 'picture_large']
template_name = 'speakersubmission_form.html'
template_name = 'speakerproposal_form.html'
class SpeakerSubmissionUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, EnsureUnpprovedSubmissionMixin, UpdateView):
model = models.SpeakerSubmission
class SpeakerProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureUnpprovedProposalMixin, EnsureWritableCampMixin, UpdateView):
model = models.SpeakerProposal
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):
model = models.SpeakerSubmission
template_name = 'speakersubmission_detail.html'
class SpeakerProposalDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView):
model = models.SpeakerProposal
template_name = 'speakerproposal_detail.html'
@method_decorator(require_safe, name='dispatch')
class SpeakerSubmissionPictureView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, DetailView):
model = models.SpeakerSubmission
class SpeakerProposalPictureView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView):
model = models.SpeakerProposal
def get(self, request, *args, **kwargs):
# 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),
# TODO: what about runserver mode here?
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,
'submissionuuid': self.get_object().uuid,
'proposaluuid': self.get_object().uuid,
'filename': os.path.basename(picture.name),
}
response['Content-Type'] = ''
return response
class EventSubmissionCreateView(LoginRequiredMixin, CampViewMixin, CreateUserSubmissionMixin, CreateView):
model = models.EventSubmission
class EventProposalCreateView(LoginRequiredMixin, CampViewMixin, CreateUserProposalMixin, EnsureWritableCampMixin, CreateView):
model = models.EventProposal
fields = ['title', 'abstract', 'event_type', 'speakers']
template_name = 'eventsubmission_form.html'
template_name = 'eventproposal_form.html'
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)
context['form'].fields['speakers'].queryset = models.SpeakerProposal.objects.filter(camp=self.camp, user=self.request.user)
context['form'].fields['event_type'].queryset = models.EventType.objects.filter(public=True)
return context
class EventSubmissionUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, EnsureUnpprovedSubmissionMixin, UpdateView):
model = models.EventSubmission
class EventProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureUnpprovedProposalMixin, EnsureWritableCampMixin, UpdateView):
model = models.EventProposal
fields = ['title', 'abstract', 'event_type', 'speakers']
template_name = 'eventsubmission_form.html'
template_name = 'eventproposal_form.html'
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)
context['form'].fields['event_type'].queryset = models.EventType.objects.filter(public=True)
return context
def get_success_url(self):
return reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
class EventSubmissionDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsSubmissionMixin, DetailView):
model = models.EventSubmission
template_name = 'eventsubmission_detail.html'
class EventProposalDetailView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView):
model = models.EventProposal
template_name = 'eventproposal_detail.html'
################## speakers ###############################################