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…
	
	Add table
		
		Reference in a new issue
	
	 Luke Hatcher
						Luke Hatcher