diff --git a/symposion/speakers/__init__.py b/symposion/speakers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/speakers/admin.py b/symposion/speakers/admin.py new file mode 100644 index 00000000..9c1174b1 --- /dev/null +++ b/symposion/speakers/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from symposion.speakers.models import Speaker + + +admin.site.register(Speaker, + list_display = ["name", "email", "twitter_username", "sessions_preference", "created"], + search_fields = ["name"], +) \ No newline at end of file diff --git a/symposion/speakers/fixture_gen.py b/symposion/speakers/fixture_gen.py new file mode 100644 index 00000000..26993fcb --- /dev/null +++ b/symposion/speakers/fixture_gen.py @@ -0,0 +1,31 @@ +from django.contrib.auth.models import User + +from fixture_generator import fixture_generator + +from symposion.speakers.models import Speaker + + +@fixture_generator(Speaker, User) +def speakers(): + guido = User.objects.create_user("guido", "guido@python.org", "pythonisawesome") + matz = User.objects.create_user("matz", "matz@ruby.org", "pythonsucks") + larry = User.objects.create_user("larryw", "larry@perl.org", "linenoisehere") + + Speaker.objects.create( + user=guido, + name="Guido van Rossum", + biography="I wrote Python, and named it after Monty Python", + twitter_username="gvanrossum", + ) + Speaker.objects.create( + user=matz, + name="Yukihiro Matsumoto", + biography="I wrote Ruby, and named it after the rare gem Ruby, a pun " + "on Perl/pearl.", + twitter_username="yukihiro_matz" + ) + Speaker.objects.create( + user=larry, + name="Larry Wall", + biography="I wrote Perl, and named it after the Parable of the Pearl", + ) diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py new file mode 100644 index 00000000..c0dd1257 --- /dev/null +++ b/symposion/speakers/forms.py @@ -0,0 +1,63 @@ +from django import forms + +from django.contrib import messages + +from markitup.widgets import MarkItUpWidget + +from symposion.speakers.models import Speaker + + +class SpeakerForm(forms.ModelForm): + + sessions_preference = forms.ChoiceField( + widget=forms.RadioSelect(), + choices=Speaker.SESSION_COUNT_CHOICES, + required=False, + help_text="If you've submitted multiple proposals, please let us know if you only want to give one or if you'd like to give two talks." + ) + + class Meta: + model = Speaker + fields = [ + "name", + "biography", + "photo", + "twitter_username", + "sessions_preference" + ] + widgets = { + "biography": MarkItUpWidget(), + } + + def clean_twitter_username(self): + value = self.cleaned_data["twitter_username"] + if value.startswith("@"): + value = value[1:] + return value + + def clean_sessions_preference(self): + value = self.cleaned_data["sessions_preference"] + if not value: + return None + return int(value) + + +# class SignupForm(PinaxSignupForm): + +# def save(self, speaker, request=None): +# # don't assume a username is available. it is a common removal if +# # site developer wants to use email authentication. +# username = self.cleaned_data.get("username") +# email = self.cleaned_data["email"] +# new_user = self.create_user(username) +# if speaker.invite_email == new_user.email: +# # already verified so can just create +# EmailAddress(user=new_user, email=email, verified=True, primary=True).save() +# else: +# if request: +# messages.info(request, u"Confirmation email sent to %(email)s" % {"email": email}) +# EmailAddress.objects.add_email(new_user, email) +# new_user.is_active = False +# new_user.save() +# self.after_signup(new_user) +# return new_user diff --git a/symposion/speakers/management/__init__.py b/symposion/speakers/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/speakers/management/commands/__init__.py b/symposion/speakers/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/speakers/management/commands/export_speaker_data.py b/symposion/speakers/management/commands/export_speaker_data.py new file mode 100644 index 00000000..5f9f565d --- /dev/null +++ b/symposion/speakers/management/commands/export_speaker_data.py @@ -0,0 +1,19 @@ +import csv +import os + +from django.core.management.base import BaseCommand, CommandError + +from symposion.speakers.models import Speaker + + +class Command(BaseCommand): + + def handle(self, *args, **options): + csv_file = csv.writer(open(os.path.join(os.getcwd(), "speakers.csv"), "wb")) + csv_file.writerow(["Name", "Bio"]) + + for speaker in Speaker.objects.all(): + csv_file.writerow([ + speaker.name.encode("utf-8"), + speaker.biography.encode("utf-8"), + ]) diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py new file mode 100644 index 00000000..8cc4d16d --- /dev/null +++ b/symposion/speakers/models.py @@ -0,0 +1,55 @@ +import datetime + +from django.db import models +from django.core.urlresolvers import reverse + +from django.contrib.auth.models import User + +from markitup.fields import MarkupField + + +class Speaker(models.Model): + + SESSION_COUNT_CHOICES = [ + (1, "One"), + (2, "Two") + ] + + user = models.OneToOneField(User, null=True, related_name="speaker_profile") + name = models.CharField(max_length=100, help_text="As you would like it to appear in the conference program.") + biography = MarkupField(help_text="A little bit about you. Edit using Markdown.") + photo = models.ImageField(upload_to="speaker_photos", blank=True) + twitter_username = models.CharField( + max_length = 15, + blank = True, + help_text = "Your Twitter account" + ) + annotation = models.TextField() # staff only + invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True) + invite_token = models.CharField(max_length=40, db_index=True) + created = models.DateTimeField( + default = datetime.datetime.now, + editable = False + ) + sessions_preference = models.IntegerField( + choices=SESSION_COUNT_CHOICES, + null=True, + blank=True, + help_text="If you've submitted multiple proposals, please let us know if you only want to give one or if you'd like to give two talks. You may submit more than two proposals." + ) + + def __unicode__(self): + if self.user: + return self.name + else: + return "?" + + def get_absolute_url(self): + return reverse("speaker_edit") + + @property + def email(self): + if self.user is not None: + return self.user.email + else: + return self.invite_email diff --git a/symposion/speakers/urls.py b/symposion/speakers/urls.py new file mode 100644 index 00000000..2dc083a8 --- /dev/null +++ b/symposion/speakers/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import * + + +urlpatterns = patterns("symposion.speakers.views", + url(r"^create/$", "speaker_create", name="speaker_create"), + url(r"^create/(\w+)/$", "speaker_create_token", name="speaker_create_token"), + url(r"^edit/(?:(?P\d+)/)?$", "speaker_edit", name="speaker_edit"), + url(r"^profile/(?P\d+)/$", "speaker_profile", name="speaker_profile"), +) diff --git a/symposion/speakers/views.py b/symposion/speakers/views.py new file mode 100644 index 00000000..54716e88 --- /dev/null +++ b/symposion/speakers/views.py @@ -0,0 +1,121 @@ +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Q +from django.http import Http404, HttpResponse +from django.shortcuts import render, redirect, get_object_or_404 +from django.template import RequestContext + +from django.contrib import messages +from django.contrib.auth.decorators import login_required + +from symposion.proposals.models import ProposalBase +from symposion.speakers.forms import SpeakerForm #, SignupForm +from symposion.speakers.models import Speaker + + +@login_required +def speaker_create(request): + try: + return redirect(request.user.speaker_profile) + except ObjectDoesNotExist: + pass + + if request.method == "POST": + try: + speaker = Speaker.objects.get(invite_email=request.user.email) + found = True + except Speaker.DoesNotExist: + speaker = None + found = False + form = SpeakerForm(request.POST, request.FILES, instance=speaker) + + if form.is_valid(): + speaker = form.save(commit=False) + speaker.user = request.user + if not found: + speaker.invite_email = None + speaker.save() + messages.success(request, "Speaker profile created.") + return redirect("dashboard") + else: + form = SpeakerForm(initial = {"name": request.user.get_full_name()}) + + return render(request, "speakers/speaker_create.html", { + "form": form, + }) + + +def speaker_create_token(request, token): + speaker = get_object_or_404(Speaker, invite_token=token) + request.session["pending-token"] = token + if request.user.is_authenticated(): + # check for speaker profile + try: + existing_speaker = request.user.speaker_profile + except ObjectDoesNotExist: + pass + else: + del request.session["pending-token"] + additional_speakers = ProposalBase.additional_speakers.through + additional_speakers._default_manager.filter( + speaker = speaker + ).update( + speaker = existing_speaker + ) + messages.info(request, "You have been associated with all pending " + "talk proposals") + return redirect("dashboard") + else: + if not request.user.is_authenticated(): + return redirect("account_login") + return redirect("speaker_create") + + +@login_required +def speaker_edit(request, pk=None): + if pk is None: + try: + speaker = request.user.speaker_profile + except Speaker.DoesNotExist: + return redirect("speaker_create") + else: + if request.user.groups.filter(name="organizer").exists(): # @@@ + speaker = get_object_or_404(Speaker, pk=pk) + else: + raise Http404() + + if request.method == "POST": + form = SpeakerForm(request.POST, request.FILES, instance=speaker) + if form.is_valid(): + form.save() + messages.success(request, "Speaker profile updated.") + return redirect("dashboard") + else: + form = SpeakerForm(instance=speaker) + + return render(request, "speakers/speaker_edit.html", { + "form": form, + }) + + +def speaker_profile(request, pk, template_name="speakers/speaker_profile.html", extra_context=None): + + if extra_context is None: + extra_context = {} + + speaker = get_object_or_404(Speaker, pk=pk) + + # schedule may not be installed so we need to check for sessions + if hasattr(speaker, "sessions"): + sessions = speaker.sessions.exclude(slot=None).order_by("slot__start") + else: + sessions = [] + + if not sessions: + raise Http404() + + return render_to_response(template_name, dict({ + "speaker": speaker, + "sessions": sessions, + "timezone": settings.SCHEDULE_TIMEZONE, + }, **extra_context), context_instance=RequestContext(request))