rename models and views from submission to proposal
This commit is contained in:
parent
89bec82880
commit
a51b795bce
|
@ -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'
|
||||
),
|
||||
])
|
||||
),
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
Credit Notes
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</p>
|
||||
<hr />
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
104
src/program/migrations/0033_auto_20170312_1857.py
Normal file
104
src/program/migrations/0033_auto_20170312_1857.py
Normal 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'),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
24
src/program/templates/eventproposal_detail.html
Normal file
24
src/program/templates/eventproposal_detail.html
Normal 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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
83
src/program/templates/proposal_list.html
Normal file
83
src/program/templates/proposal_list.html
Normal 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 %}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
37
src/program/templates/speakerproposal_detail.html
Normal file
37
src/program/templates/speakerproposal_detail.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
{% extends 'program_base.html' %}
|
||||
{% load commonmark %}
|
||||
|
||||
{% block program_content %}
|
||||
|
||||
<h2>{{ camp.title }} Speaker Proposal Details</h2>
|
||||
|
||||
<ul>
|
||||
<li class="list">Status: <span class="badge">{{ 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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 ###############################################
|
||||
|
|
Loading…
Reference in a new issue