add proposals app from pycon
This commit is contained in:
parent
2b7f5546a0
commit
7596922f4d
10 changed files with 697 additions and 0 deletions
0
symposion/proposals/__init__.py
Normal file
0
symposion/proposals/__init__.py
Normal file
36
symposion/proposals/actions.py
Normal file
36
symposion/proposals/actions.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import csv
|
||||
|
||||
from django.http import HttpResponse
|
||||
|
||||
|
||||
def export_as_csv_action(description="Export selected objects as CSV file",
|
||||
fields=None, exclude=None, header=True):
|
||||
"""
|
||||
This function returns an export csv action
|
||||
'fields' and 'exclude' work like in django ModelForm
|
||||
'header' is whether or not to output the column names as the first row
|
||||
"""
|
||||
def export_as_csv(modeladmin, request, queryset):
|
||||
"""
|
||||
Generic csv export admin action.
|
||||
based on http://djangosnippets.org/snippets/1697/
|
||||
"""
|
||||
opts = modeladmin.model._meta
|
||||
if fields:
|
||||
fieldset = set(fields)
|
||||
field_names = fieldset
|
||||
elif exclude:
|
||||
excludeset = set(exclude)
|
||||
field_names = field_names - excludeset
|
||||
|
||||
response = HttpResponse(mimetype='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename=%s.csv' % unicode(opts).replace('.', '_')
|
||||
|
||||
writer = csv.writer(response)
|
||||
if header:
|
||||
writer.writerow(list(field_names))
|
||||
for obj in queryset:
|
||||
writer.writerow([unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names])
|
||||
return response
|
||||
export_as_csv.short_description = description
|
||||
return export_as_csv
|
32
symposion/proposals/admin.py
Normal file
32
symposion/proposals/admin.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# from symposion.proposals.actions import export_as_csv_action
|
||||
from symposion.proposals.models import ProposalSection, ProposalKind
|
||||
|
||||
|
||||
# admin.site.register(Proposal,
|
||||
# list_display = [
|
||||
# "id",
|
||||
# "title",
|
||||
# "speaker",
|
||||
# "speaker_email",
|
||||
# "kind",
|
||||
# "audience_level",
|
||||
# "cancelled",
|
||||
# ],
|
||||
# list_filter = [
|
||||
# "kind__name",
|
||||
# "result__accepted",
|
||||
# ],
|
||||
# actions = [export_as_csv_action("CSV Export", fields=[
|
||||
# "id",
|
||||
# "title",
|
||||
# "speaker",
|
||||
# "speaker_email",
|
||||
# "kind",
|
||||
# ])]
|
||||
# )
|
||||
|
||||
|
||||
admin.site.register(ProposalSection)
|
||||
admin.site.register(ProposalKind)
|
41
symposion/proposals/forms.py
Normal file
41
symposion/proposals/forms.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from symposion.proposals.models import SupportingDocument
|
||||
# from markitup.widgets import MarkItUpWidget
|
||||
|
||||
|
||||
# @@@ generic proposal form
|
||||
|
||||
|
||||
class AddSpeakerForm(forms.Form):
|
||||
|
||||
email = forms.EmailField(
|
||||
label="Email address of new speaker (use their email address, not yours)"
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.proposal = kwargs.pop("proposal")
|
||||
super(AddSpeakerForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_email(self):
|
||||
value = self.cleaned_data["email"]
|
||||
exists = self.proposal.additional_speakers.filter(
|
||||
Q(user=None, invite_email=value) |
|
||||
Q(user__email=value)
|
||||
).exists()
|
||||
if exists:
|
||||
raise forms.ValidationError(
|
||||
"This email address has already been invited to your talk proposal"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class SupportingDocumentCreateForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = SupportingDocument
|
||||
fields = [
|
||||
"file",
|
||||
"description",
|
||||
]
|
27
symposion/proposals/managers.py
Normal file
27
symposion/proposals/managers.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
|
||||
class CachingM2MQuerySet(QuerySet):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CachingM2MQuerySet, self).__init__(*args, **kwargs)
|
||||
self.cached_m2m_field = kwargs["m2m_field"]
|
||||
|
||||
def iterator(self):
|
||||
parent_iter = super(CachingM2MQuerySet, self).iterator()
|
||||
m2m_model = getattr(self.model, self.cached_m2m_field).through
|
||||
|
||||
for obj in parent_iter:
|
||||
if obj.id in cached_objects:
|
||||
setattr(obj, "_cached_m2m_%s" % self.cached_m2m_field)
|
||||
yield obj
|
||||
|
||||
|
||||
class ProposalManager(models.Manager):
|
||||
def cache_m2m(self, m2m_field):
|
||||
return CachingM2MQuerySet(self.model, using=self._db, m2m_field=m2m_field)
|
||||
AdditionalSpeaker = queryset.model.additional_speakers.through
|
||||
additional_speakers = collections.defaultdict(set)
|
||||
for additional_speaker in AdditionalSpeaker._default_manager.filter(proposal__in=queryset).select_related("speaker__user"):
|
||||
additional_speakers[additional_speaker.proposal_id].add(additional_speaker.speaker)
|
146
symposion/proposals/models.py
Normal file
146
symposion/proposals/models.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
import datetime
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from markitup.fields import MarkupField
|
||||
|
||||
from model_utils.managers import InheritanceManager
|
||||
|
||||
from symposion.conference.models import Section
|
||||
|
||||
|
||||
class ProposalSection(models.Model):
|
||||
"""
|
||||
configuration of proposal submissions for a specific Section.
|
||||
|
||||
a section is available for proposals iff:
|
||||
* it is after start (if there is one) and
|
||||
* it is before end (if there is one) and
|
||||
* closed is NULL or False
|
||||
"""
|
||||
|
||||
section = models.OneToOneField(Section)
|
||||
|
||||
start = models.DateTimeField(null=True, blank=True)
|
||||
end = models.DateTimeField(null=True, blank=True)
|
||||
closed = models.NullBooleanField()
|
||||
published = models.NullBooleanField()
|
||||
|
||||
@classmethod
|
||||
def available(cls):
|
||||
now = datetime.datetime.now()
|
||||
return cls._default_manager.filter(
|
||||
Q(start__lt=now) | Q(start=None),
|
||||
Q(end__gt=now) | Q(end=None),
|
||||
Q(closed=False) | Q(closed=None),
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.section.name
|
||||
|
||||
|
||||
class ProposalKind(models.Model):
|
||||
"""
|
||||
e.g. talk vs panel vs tutorial vs poster
|
||||
|
||||
Note that if you have different deadlines, reviewers, etc. you'll want
|
||||
to distinguish the section as well as the kind.
|
||||
"""
|
||||
|
||||
section = models.ForeignKey(Section, related_name="proposal_kinds")
|
||||
|
||||
name = models.CharField("name", max_length=100)
|
||||
slug = models.SlugField()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ProposalBase(models.Model):
|
||||
|
||||
objects = InheritanceManager()
|
||||
|
||||
kind = models.ForeignKey(ProposalKind)
|
||||
|
||||
title = models.CharField(max_length=100)
|
||||
description = models.TextField(
|
||||
max_length=400, # @@@ need to enforce 400 in UI
|
||||
help_text="If your talk is accepted this will be made public and printed in the program. Should be one paragraph, maximum 400 characters."
|
||||
)
|
||||
abstract = MarkupField(
|
||||
help_text="Detailed description and outline. Will be made public if your talk is accepted. Edit using <a href='http://warpedvisions.org/projects/markdown-cheat-sheet/' target='_blank'>Markdown</a>."
|
||||
)
|
||||
additional_notes = MarkupField(
|
||||
blank=True,
|
||||
help_text="Anything else you'd like the program committee to know when making their selection: your past speaking experience, open source community experience, etc. Edit using <a href='http://warpedvisions.org/projects/markdown-cheat-sheet/' target='_blank'>Markdown</a>."
|
||||
)
|
||||
submitted = models.DateTimeField(
|
||||
default=datetime.datetime.now,
|
||||
editable=False,
|
||||
)
|
||||
speaker = models.ForeignKey("speakers.Speaker", related_name="proposals")
|
||||
additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker", blank=True)
|
||||
cancelled = models.BooleanField(default=False)
|
||||
|
||||
def can_edit(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def speaker_email(self):
|
||||
return self.speaker.email
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
return str(self.pk).zfill(3)
|
||||
|
||||
def speakers(self):
|
||||
yield self.speaker
|
||||
for speaker in self.additional_speakers.exclude(additionalspeaker__status=AdditionalSpeaker.SPEAKING_STATUS_DECLINED):
|
||||
yield speaker
|
||||
|
||||
|
||||
class AdditionalSpeaker(models.Model):
|
||||
|
||||
SPEAKING_STATUS_PENDING = 1
|
||||
SPEAKING_STATUS_ACCEPTED = 2
|
||||
SPEAKING_STATUS_DECLINED = 3
|
||||
|
||||
SPEAKING_STATUS = [
|
||||
(SPEAKING_STATUS_PENDING, "Pending"),
|
||||
(SPEAKING_STATUS_ACCEPTED, "Accepted"),
|
||||
(SPEAKING_STATUS_DECLINED, "Declined"),
|
||||
]
|
||||
|
||||
speaker = models.ForeignKey("speakers.Speaker")
|
||||
proposalbase = models.ForeignKey(ProposalBase)
|
||||
status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING)
|
||||
|
||||
class Meta:
|
||||
db_table = "proposals_proposalbase_additional_speakers"
|
||||
unique_together = ("speaker", "proposalbase")
|
||||
|
||||
|
||||
def uuid_filename(instance, filename):
|
||||
ext = filename.split(".")[-1]
|
||||
filename = "%s.%s" % (uuid.uuid4(), ext)
|
||||
return os.path.join("document", filename)
|
||||
|
||||
|
||||
class SupportingDocument(models.Model):
|
||||
|
||||
proposal = models.ForeignKey(ProposalBase, related_name="supporting_documents")
|
||||
|
||||
uploaded_by = models.ForeignKey(User)
|
||||
created_at = models.DateTimeField(default=datetime.datetime.now)
|
||||
|
||||
file = models.FileField(upload_to=uuid_filename)
|
||||
description = models.CharField(max_length=140)
|
||||
|
||||
def download_url(self):
|
||||
return reverse("proposal_document_download", args=[self.pk, os.path.basename(self.file.name).lower()])
|
0
symposion/proposals/templatetags/__init__.py
Normal file
0
symposion/proposals/templatetags/__init__.py
Normal file
73
symposion/proposals/templatetags/proposal_tags.py
Normal file
73
symposion/proposals/templatetags/proposal_tags.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
from django import template
|
||||
|
||||
from symposion.proposals.models import AdditionalSpeaker
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
class AssociatedProposalsNode(template.Node):
|
||||
|
||||
@classmethod
|
||||
def handle_token(cls, parser, token):
|
||||
bits = token.split_contents()
|
||||
if len(bits) == 3 and bits[1] == "as":
|
||||
return cls(bits[2])
|
||||
else:
|
||||
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
|
||||
|
||||
def __init__(self, context_var):
|
||||
self.context_var = context_var
|
||||
|
||||
def render(self, context):
|
||||
request = context["request"]
|
||||
if request.user.speaker_profile:
|
||||
pending = AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED
|
||||
speaker = request.user.speaker_profile
|
||||
queryset = AdditionalSpeaker.objects.filter(speaker=speaker, status=pending)
|
||||
context[self.context_var] = [item.proposalbase for item in queryset]
|
||||
else:
|
||||
context[self.context_var] = None
|
||||
return u""
|
||||
|
||||
|
||||
class PendingProposalsNode(template.Node):
|
||||
|
||||
@classmethod
|
||||
def handle_token(cls, parser, token):
|
||||
bits = token.split_contents()
|
||||
if len(bits) == 3 and bits[1] == "as":
|
||||
return cls(bits[2])
|
||||
else:
|
||||
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
|
||||
|
||||
def __init__(self, context_var):
|
||||
self.context_var = context_var
|
||||
|
||||
def render(self, context):
|
||||
request = context["request"]
|
||||
if request.user.speaker_profile:
|
||||
pending = AdditionalSpeaker.SPEAKING_STATUS_PENDING
|
||||
speaker = request.user.speaker_profile
|
||||
queryset = AdditionalSpeaker.objects.filter(speaker=speaker, status=pending)
|
||||
context[self.context_var] = [item.proposalbase for item in queryset]
|
||||
else:
|
||||
context[self.context_var] = None
|
||||
return u""
|
||||
|
||||
|
||||
@register.tag
|
||||
def pending_proposals(parser, token):
|
||||
"""
|
||||
{% pending_proposals as pending_proposals %}
|
||||
"""
|
||||
return PendingProposalsNode.handle_token(parser, token)
|
||||
|
||||
|
||||
@register.tag
|
||||
def associated_proposals(parser, token):
|
||||
"""
|
||||
{% associated_proposals as associated_proposals %}
|
||||
"""
|
||||
return AssociatedProposalsNode.handle_token(parser, token)
|
||||
|
18
symposion/proposals/urls.py
Normal file
18
symposion/proposals/urls.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from django.conf.urls.defaults import *
|
||||
|
||||
|
||||
urlpatterns = patterns("symposion.proposals.views",
|
||||
url(r"^submit/$", "proposal_submit", name="proposal_submit"),
|
||||
url(r"^submit/(\w+)/$", "proposal_submit_kind", name="proposal_submit_kind"),
|
||||
url(r"^(\d+)/$", "proposal_detail", name="proposal_detail"),
|
||||
url(r"^(\d+)/edit/$", "proposal_edit", name="proposal_edit"),
|
||||
url(r"^(\d+)/speakers/$", "proposal_speaker_manage", name="proposal_speaker_manage"),
|
||||
url(r"^(\d+)/cancel/$", "proposal_cancel", name="proposal_cancel"),
|
||||
url(r"^(\d+)/leave/$", "proposal_leave", name="proposal_leave"),
|
||||
url(r"^(\d+)/join/$", "proposal_pending_join", name="proposal_pending_join"),
|
||||
url(r"^(\d+)/decline/$", "proposal_pending_decline", name="proposal_pending_decline"),
|
||||
|
||||
url(r"^(\d+)/document/create/$", "document_create", name="proposal_document_create"),
|
||||
url(r"^document/(\d+)/delete/$", "document_delete", name="proposal_document_delete"),
|
||||
url(r"^document/(\d+)/([^/]+)$", "document_download", name="proposal_document_download"),
|
||||
)
|
324
symposion/proposals/views.py
Normal file
324
symposion/proposals/views.py
Normal file
|
@ -0,0 +1,324 @@
|
|||
import random
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import Q
|
||||
from django.http import Http404, HttpResponse, HttpResponseForbidden
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.utils.hashcompat import sha_constructor
|
||||
from django.views import static
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from account.models import EmailAddress
|
||||
from symposion.proposals.models import ProposalBase, ProposalSection, ProposalKind
|
||||
from symposion.proposals.models import SupportingDocument, AdditionalSpeaker
|
||||
from symposion.speakers.models import Speaker
|
||||
from symposion.utils.mail import send_email
|
||||
|
||||
from symposion.proposals.forms import AddSpeakerForm, SupportingDocumentCreateForm
|
||||
|
||||
|
||||
def get_form(name):
|
||||
dot = name.rindex('.')
|
||||
mod_name, form_name = name[:dot], name[dot + 1:]
|
||||
__import__(mod_name)
|
||||
return getattr(sys.modules[mod_name], form_name)
|
||||
|
||||
|
||||
def proposal_submit(request):
|
||||
if not request.user.is_authenticated():
|
||||
return redirect("home") # @@@ unauth'd speaker info page?
|
||||
else:
|
||||
try:
|
||||
request.user.speaker_profile
|
||||
except ObjectDoesNotExist:
|
||||
return redirect("dashboard")
|
||||
|
||||
kinds = []
|
||||
for proposal_section in ProposalSection.available():
|
||||
for kind in proposal_section.section.proposal_kinds.all():
|
||||
kinds.append(kind)
|
||||
|
||||
return render(request, "proposals/proposal_submit.html", {
|
||||
"kinds": kinds,
|
||||
})
|
||||
|
||||
|
||||
def proposal_submit_kind(request, kind_slug):
|
||||
|
||||
kind = get_object_or_404(ProposalKind, slug=kind_slug)
|
||||
|
||||
if not request.user.is_authenticated():
|
||||
return redirect("home") # @@@ unauth'd speaker info page?
|
||||
else:
|
||||
try:
|
||||
speaker_profile = request.user.speaker_profile
|
||||
except ObjectDoesNotExist:
|
||||
return redirect("dashboard")
|
||||
|
||||
form_class = get_form(settings.PROPOSAL_FORMS[kind_slug])
|
||||
|
||||
if request.method == "POST":
|
||||
form = form_class(request.POST)
|
||||
if form.is_valid():
|
||||
proposal = form.save(commit=False)
|
||||
proposal.kind = kind
|
||||
proposal.speaker = speaker_profile
|
||||
proposal.save()
|
||||
form.save_m2m()
|
||||
messages.success(request, "Proposal submitted.")
|
||||
if "add-speakers" in request.POST:
|
||||
return redirect("proposal_speaker_manage", proposal.pk)
|
||||
return redirect("dashboard")
|
||||
else:
|
||||
form = form_class()
|
||||
|
||||
return render(request, "proposals/proposal_submit_kind.html", {
|
||||
"kind": kind,
|
||||
"form": form,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def proposal_speaker_manage(request, pk):
|
||||
queryset = ProposalBase.objects.select_related("speaker")
|
||||
proposal = get_object_or_404(queryset, pk=pk)
|
||||
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
|
||||
|
||||
if proposal.speaker != request.user.speaker_profile:
|
||||
raise Http404()
|
||||
|
||||
if request.method == "POST":
|
||||
add_speaker_form = AddSpeakerForm(request.POST, proposal=proposal)
|
||||
if add_speaker_form.is_valid():
|
||||
message_ctx = {
|
||||
"proposal": proposal,
|
||||
}
|
||||
|
||||
def create_speaker_token(email_address):
|
||||
# create token and look for an existing speaker to prevent
|
||||
# duplicate tokens and confusing the pending speaker
|
||||
try:
|
||||
pending = Speaker.objects.get(
|
||||
Q(user=None, invite_email=email_address)
|
||||
)
|
||||
except Speaker.DoesNotExist:
|
||||
salt = sha_constructor(str(random.random())).hexdigest()[:5]
|
||||
token = sha_constructor(salt + email_address).hexdigest()
|
||||
pending = Speaker.objects.create(
|
||||
invite_email=email_address,
|
||||
invite_token=token,
|
||||
)
|
||||
else:
|
||||
token = pending.invite_token
|
||||
return pending, token
|
||||
email_address = add_speaker_form.cleaned_data["email"]
|
||||
# check if email is on the site now
|
||||
users = EmailAddress.objects.get_users_for(email_address)
|
||||
if users:
|
||||
# should only be one since we enforce unique email
|
||||
user = users[0]
|
||||
message_ctx["user"] = user
|
||||
# look for speaker profile
|
||||
try:
|
||||
speaker = user.speaker_profile
|
||||
except ObjectDoesNotExist:
|
||||
speaker, token = create_speaker_token(email_address)
|
||||
message_ctx["token"] = token
|
||||
# fire off email to user to create profile
|
||||
send_email(
|
||||
[email_address], "speaker_no_profile",
|
||||
context = message_ctx
|
||||
)
|
||||
else:
|
||||
# fire off email to user letting them they are loved.
|
||||
send_email(
|
||||
[email_address], "speaker_addition",
|
||||
context = message_ctx
|
||||
)
|
||||
else:
|
||||
speaker, token = create_speaker_token(email_address)
|
||||
message_ctx["token"] = token
|
||||
# fire off email letting user know about site and to create
|
||||
# account and speaker profile
|
||||
send_email(
|
||||
[email_address], "speaker_invite",
|
||||
context = message_ctx
|
||||
)
|
||||
invitation, created = AdditionalSpeaker.objects.get_or_create(proposalbase=proposal.proposalbase_ptr, speaker=speaker)
|
||||
messages.success(request, "Speaker invited to proposal.")
|
||||
return redirect("proposal_speaker_manage", proposal.pk)
|
||||
else:
|
||||
add_speaker_form = AddSpeakerForm(proposal=proposal)
|
||||
ctx = {
|
||||
"proposal": proposal,
|
||||
"speakers": proposal.speakers(),
|
||||
"add_speaker_form": add_speaker_form,
|
||||
}
|
||||
return render(request, "proposals/proposal_speaker_manage.html", ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
def proposal_edit(request, pk):
|
||||
queryset = ProposalBase.objects.select_related("speaker")
|
||||
proposal = get_object_or_404(queryset, pk=pk)
|
||||
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
|
||||
|
||||
if request.user != proposal.speaker.user:
|
||||
raise Http404()
|
||||
|
||||
if not proposal.can_edit():
|
||||
ctx = {
|
||||
"title": "Proposal editing closed",
|
||||
"body": "Proposal editing is closed for this session type."
|
||||
}
|
||||
return render(request, "proposals/proposal_error.html", ctx)
|
||||
|
||||
form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug])
|
||||
|
||||
if request.method == "POST":
|
||||
form = form_class(request.POST, instance=proposal)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, "Proposal updated.")
|
||||
return redirect("proposal_detail", proposal.pk)
|
||||
else:
|
||||
form = form_class(instance=proposal)
|
||||
|
||||
return render(request, "proposals/proposal_edit.html", {
|
||||
"proposal": proposal,
|
||||
"form": form,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def proposal_detail(request, pk):
|
||||
queryset = ProposalBase.objects.select_related("speaker", "speaker__user")
|
||||
proposal = get_object_or_404(queryset, pk=pk)
|
||||
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
|
||||
|
||||
if request.user not in [p.user for p in proposal.speakers()]:
|
||||
raise Http404()
|
||||
|
||||
return render(request, "proposals/proposal_detail.html", {
|
||||
"proposal": proposal,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def proposal_cancel(request, pk):
|
||||
queryset = ProposalBase.objects.select_related("speaker")
|
||||
proposal = get_object_or_404(queryset, pk=pk)
|
||||
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
|
||||
|
||||
if proposal.speaker.user != request.user:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == "POST":
|
||||
proposal.cancelled = True
|
||||
proposal.save()
|
||||
# @@@ fire off email to submitter and other speakers
|
||||
messages.success(request, "%s has been cancelled" % proposal.title)
|
||||
return redirect("dashboard")
|
||||
|
||||
return render(request, "proposals/proposal_cancel.html", {
|
||||
"proposal": proposal,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def proposal_leave(request, pk):
|
||||
queryset = ProposalBase.objects.select_related("speaker")
|
||||
proposal = get_object_or_404(queryset, pk=pk)
|
||||
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
|
||||
|
||||
try:
|
||||
speaker = proposal.additional_speakers.get(user=request.user)
|
||||
except ObjectDoesNotExist:
|
||||
return HttpResponseForbidden()
|
||||
if request.method == "POST":
|
||||
proposal.additional_speakers.remove(speaker)
|
||||
# @@@ fire off email to submitter and other speakers
|
||||
messages.success(request, "You are no longer speaking on %s" % proposal.title)
|
||||
return redirect("speaker_dashboard")
|
||||
ctx = {
|
||||
"proposal": proposal,
|
||||
}
|
||||
return render(request, "proposals/proposal_leave.html", ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
def proposal_pending_join(request, pk):
|
||||
proposal = get_object_or_404(ProposalBase, pk=pk)
|
||||
speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, proposalbase=proposal)
|
||||
if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING:
|
||||
speaking.status = AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED
|
||||
speaking.save()
|
||||
messages.success(request, "You have accepted the invitation to join %s" % proposal.title)
|
||||
return redirect("dashboard")
|
||||
else:
|
||||
return redirect("dashboard")
|
||||
|
||||
|
||||
@login_required
|
||||
def proposal_pending_decline(request, pk):
|
||||
proposal = get_object_or_404(ProposalBase, pk=pk)
|
||||
speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, proposalbase=proposal)
|
||||
if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING:
|
||||
speaking.status = AdditionalSpeaker.SPEAKING_STATUS_DECLINED
|
||||
speaking.save()
|
||||
messages.success(request, "You have declined to speak on %s" % proposal.title)
|
||||
return redirect("dashboard")
|
||||
else:
|
||||
return redirect("dashboard")
|
||||
|
||||
|
||||
@login_required
|
||||
def document_create(request, proposal_pk):
|
||||
queryset = ProposalBase.objects.select_related("speaker")
|
||||
proposal = get_object_or_404(queryset, pk=proposal_pk)
|
||||
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
|
||||
|
||||
if request.method == "POST":
|
||||
form = SupportingDocumentCreateForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
document = form.save(commit=False)
|
||||
document.proposal = proposal
|
||||
document.uploaded_by = request.user
|
||||
document.save()
|
||||
return redirect("proposal_detail", proposal.pk)
|
||||
else:
|
||||
form = SupportingDocumentCreateForm()
|
||||
|
||||
return render(request, "proposals/document_create.html", {
|
||||
"proposal": proposal,
|
||||
"form": form,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def document_download(request, pk, *args):
|
||||
document = get_object_or_404(SupportingDocument, pk=pk)
|
||||
if settings.USE_X_ACCEL_REDIRECT:
|
||||
response = HttpResponse()
|
||||
response["X-Accel-Redirect"] = document.file.url
|
||||
# delete content-type to allow Gondor to determine the filetype and
|
||||
# we definitely don't want Django's crappy default :-)
|
||||
del response["content-type"]
|
||||
else:
|
||||
response = static.serve(request, document.file.name, document_root=settings.MEDIA_ROOT)
|
||||
return response
|
||||
|
||||
|
||||
@login_required
|
||||
def document_delete(request, pk):
|
||||
document = get_object_or_404(SupportingDocument, pk=pk, uploaded_by=request.user)
|
||||
proposal_pk = document.proposal.pk
|
||||
|
||||
if request.method == "POST":
|
||||
document.delete()
|
||||
|
||||
return redirect("proposal_detail", proposal_pk)
|
Loading…
Reference in a new issue