Browse files

Merge branch 'dev'

  • Loading branch information...
2 parents 1307d32 + 4d5ab9c commit 612a9c63ac4007b5b8c74c27cac9815b686909b6 @brosner brosner committed Feb 8, 2011
Showing with 1,541 additions and 122 deletions.
  1. +9 −7 pycon_project/apps/review/models.py
  2. +15 −4 pycon_project/apps/schedule/admin.py
  3. +56 −0 pycon_project/apps/schedule/forms.py
  4. 0 pycon_project/apps/schedule/management/__init__.py
  5. 0 pycon_project/apps/schedule/management/commands/__init__.py
  6. +439 −0 pycon_project/apps/schedule/management/commands/load_sessions.py
  7. +125 −20 pycon_project/apps/schedule/models.py
  8. +13 −2 pycon_project/apps/schedule/urls.py
  9. +276 −22 pycon_project/apps/schedule/views.py
  10. +12 −12 pycon_project/apps/user_mailer/user_lists.py
  11. +21 −0 pycon_project/migrations/009_refactor_slot.sql
  12. +29 −0 pycon_project/migrations/010_rename_session.sql
  13. +1 −0 pycon_project/migrations/011_add_slot_unique.sql
  14. +87 −0 pycon_project/migrations/012_create_new_sessions_slots.py
  15. +13 −0 pycon_project/migrations/013_session_role_models.sql
  16. +29 −0 pycon_project/migrations/014_add_plenary_and_recess.sql
  17. +1 −1 pycon_project/requirements/project.txt
  18. +1 −1 pycon_project/settings.py
  19. +33 −0 pycon_project/templates/schedule/_grid.html
  20. +15 −0 pycon_project/templates/schedule/_presentation.html
  21. +0 −15 pycon_project/templates/schedule/_session.html
  22. +137 −0 pycon_project/templates/schedule/conference_edit.html
  23. +1 −1 pycon_project/templates/schedule/list_posters.html
  24. +1 −1 pycon_project/templates/schedule/list_talks.html
  25. +1 −1 pycon_project/templates/schedule/list_tutorials.html
  26. +31 −0 pycon_project/templates/schedule/presentation_detail.html
  27. +2 −2 pycon_project/templates/schedule/schedule_list.html
  28. +57 −19 pycon_project/templates/schedule/session_detail.html
  29. +14 −0 pycon_project/templates/schedule/session_list.html
  30. +7 −0 pycon_project/templates/schedule/slot_place.html
  31. +10 −0 pycon_project/templates/schedule/slot_remove.html
  32. +38 −0 pycon_project/templates/schedule/track_detail.html
  33. +38 −0 pycon_project/templates/schedule/track_detail_none.html
  34. +15 −0 pycon_project/templates/schedule/track_list.html
  35. +13 −13 pycon_project/templates/schedule/tutorials.html
  36. +1 −1 pycon_project/templates/speakers/speaker_profile.html
View
16 pycon_project/apps/review/models.py
@@ -12,7 +12,8 @@
from proposals.models import Proposal
-from schedule.models import Session
+from schedule.models import Presentation
+
class ProposalScoreExpression(object):
@@ -292,12 +293,12 @@ def create_proposal_result(sender, instance=None, **kwargs):
def promote_proposal(proposal):
- session, created = Session.objects.get_or_create(
+ presentation, created = Presentation.objects.get_or_create(
pk=proposal.pk,
defaults=dict(
title=proposal.title,
description=proposal.description,
- session_type=proposal.session_type,
+ presentation_type=proposal.session_type,
abstract=proposal.abstract,
audience_level=proposal.audience_level,
submitted=proposal.submitted,
@@ -307,11 +308,12 @@ def promote_proposal(proposal):
)
)
- for speaker in proposal.additional_speakers.all():
- session.additional_speakers.add(speaker)
- session.save()
+ if created:
+ for speaker in proposal.additional_speakers.all():
+ presentation.additional_speakers.add(speaker)
+ presentation.save()
- return session
+ return presentation
def accepted_proposal(sender, instance=None, **kwargs):
View
19 pycon_project/apps/schedule/admin.py
@@ -1,12 +1,23 @@
from django.contrib import admin
-from schedule.models import Session, Slot
+from schedule.models import SessionRole, Presentation, Slot, Session, Track
+admin.site.register(Session)
+admin.site.register(SessionRole,
+ list_display = ["session", "user", "role", "status", "submitted"],
+ raw_id_fields = ["user"],
+)
+
+admin.site.register(Track,
+ list_display = ["pk", "name"]
+)
+
admin.site.register(Slot,
- list_display = ["start", "end", "title"]
+ list_display = ["pk", "start", "end", "track"]
)
-admin.site.register(Session,
- list_display = ["track", "plenary", "title", "session_type", "audience_level", "cancelled"]
+admin.site.register(Presentation,
+ list_display = ["title", "slot", "presentation_type", "audience_level", "cancelled"],
+ raw_id_fields = ["speaker"]
)
View
56 pycon_project/apps/schedule/forms.py
@@ -0,0 +1,56 @@
+from django import forms
+from django.db.models import Q
+
+from schedule.models import Plenary, Recess, Presentation
+
+
+class PlenaryForm(forms.ModelForm):
+ class Meta:
+ model = Plenary
+ exclude = ["slot", "additional_speakers"]
+
+
+class RecessForm(forms.ModelForm):
+ class Meta:
+ model = Recess
+ exclude = ["slot"]
+
+
+def presentation_queryset(include=None):
+ qs = Presentation.objects.all()
+ qs = qs.filter(
+ Q(presentation_type=Presentation.PRESENTATION_TYPE_TALK) |
+ Q(presentation_type=Presentation.PRESENTATION_TYPE_PANEL)
+ )
+ if include:
+ qs = qs.filter(Q(slot=None) | Q(pk=include.pk))
+ else:
+ qs = qs.filter(slot=None)
+ qs = qs.order_by("pk")
+ return qs
+
+
+class PresentationModelChoiceField(forms.ModelChoiceField):
+
+ def __init__(self, *args, **kwargs):
+ kwargs["queryset"] = Presentation.objects.none()
+ super(PresentationModelChoiceField, self).__init__(*args, **kwargs)
+
+ def label_from_instance(self, obj):
+ return u"%d: %s" % (obj.pk, obj.title)
+
+
+class PresentationForm(forms.Form):
+
+ presentation = PresentationModelChoiceField()
+
+ def __init__(self, *args, **kwargs):
+ presentation = kwargs.get("initial", {}).get("presentation", None)
+ super(PresentationForm, self).__init__(*args, **kwargs)
+ if presentation:
+ self.fields["presentation"].queryset = presentation_queryset(include=presentation)
+ else:
+ self.fields["presentation"].queryset = presentation_queryset()
+
+ def save(self, commit=True):
+ return self.cleaned_data["presentation"]
View
0 pycon_project/apps/schedule/management/__init__.py
No changes.
View
0 pycon_project/apps/schedule/management/commands/__init__.py
No changes.
View
439 pycon_project/apps/schedule/management/commands/load_sessions.py
@@ -0,0 +1,439 @@
+from datetime import datetime
+
+from django.core.management.base import BaseCommand
+
+from schedule.models import Track, Session, Slot
+
+
+friday_plenaries = [
+ [
+ {
+ "start": datetime(2011, 3, 11, 7, 0),
+ "end": datetime(2011, 3, 11, 8, 0),
+ },
+ {
+ "start": datetime(2011, 3, 11, 8, 0),
+ "end": datetime(2011, 3, 11, 9, 0),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 11, 9, 0),
+ "end": datetime(2011, 3, 11, 9, 10),
+ },
+ {
+ "start": datetime(2011, 3, 11, 9, 10),
+ "end": datetime(2011, 3, 11, 9, 40),
+ },
+ {
+ "start": datetime(2011, 3, 11, 9, 40),
+ "end": datetime(2011, 3, 11, 10, 5),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 11, 10, 5),
+ "end": datetime(2011, 3, 11, 10, 25),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 11, 17, 30),
+ "end": datetime(2011, 3, 11, 18, 0),
+ },
+ ],
+]
+
+
+friday_slots_type_1 = [
+ [
+ {
+ "start": datetime(2011, 3, 11, 10, 25),
+ "end": datetime(2011, 3, 11, 11, 5),
+ },
+ {
+ "start": datetime(2011, 3, 11, 11, 5),
+ "end": datetime(2011, 3, 11, 11, 45),
+ },
+ {
+ "start": datetime(2011, 3, 11, 11, 45),
+ "end": datetime(2011, 3, 11, 12, 30),
+ },
+ ],
+ [
+ {
+ "title": "Lunch",
+ "start": datetime(2011, 3, 11, 12, 30),
+ "end": datetime(2011, 3, 11, 13, 35),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 11, 13, 35),
+ "end": datetime(2011, 3, 11, 14, 15),
+ },
+ {
+ "start": datetime(2011, 3, 11, 14, 15),
+ "end": datetime(2011, 3, 11, 14, 55),
+ },
+ {
+ "start": datetime(2011, 3, 11, 14, 55),
+ "end": datetime(2011, 3, 11, 15, 40),
+ },
+ ],
+ [
+ {
+ "title": "Afternoon Break with Snacks in Expo Hall",
+ "start": datetime(2011, 3, 11, 15, 40),
+ "end": datetime(2011, 3, 11, 16, 15),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 11, 16, 15),
+ "end": datetime(2011, 3, 11, 16, 55),
+ },
+ {
+ "start": datetime(2011, 3, 11, 16, 55),
+ "end": datetime(2011, 3, 11, 17, 30),
+ },
+ ]
+]
+
+friday_slots_type_2 = [
+ [
+ {
+ "start": datetime(2011, 3, 11, 10, 25),
+ "end": datetime(2011, 3, 11, 11, 5),
+ },
+ {
+ "start": datetime(2011, 3, 11, 11, 5),
+ "end": datetime(2011, 3, 11, 11, 45),
+ },
+ {
+ "start": datetime(2011, 3, 11, 11, 45),
+ "end": datetime(2011, 3, 11, 12, 15),
+ },
+ ],
+ [
+ {
+ "title": "Lunch",
+ "start": datetime(2011, 3, 11, 12, 15),
+ "end": datetime(2011, 3, 11, 13, 20),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 11, 13, 20),
+ "end": datetime(2011, 3, 11, 14, 15),
+ },
+ {
+ "start": datetime(2011, 3, 11, 14, 15),
+ "end": datetime(2011, 3, 11, 14, 55),
+ },
+ {
+ "start": datetime(2011, 3, 11, 14, 55),
+ "end": datetime(2011, 3, 11, 15, 25),
+ },
+ ],
+ [
+ {
+ "title": "Afternoon Break with Snacks in Expo Hall",
+ "start": datetime(2011, 3, 11, 15, 25),
+ "end": datetime(2011, 3, 11, 16, 0),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 11, 16, 0),
+ "end": datetime(2011, 3, 11, 16, 55),
+ },
+ {
+ "start": datetime(2011, 3, 11, 16, 55),
+ "end": datetime(2011, 3, 11, 17, 30),
+ },
+ ]
+]
+
+
+saturday_plenaries = [
+ [
+ {
+ "start": datetime(2011, 3, 12, 7, 0),
+ "end": datetime(2011, 3, 12, 8, 0),
+ },
+ {
+ "start": datetime(2011, 3, 12, 8, 0),
+ "end": datetime(2011, 3, 12, 8, 30),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 12, 8, 30),
+ "end": datetime(2011, 3, 12, 9, 0),
+ },
+ {
+ "start": datetime(2011, 3, 12, 9, 0),
+ "end": datetime(2011, 3, 12, 9, 5),
+ },
+ {
+ "start": datetime(2011, 3, 12, 9, 5),
+ "end": datetime(2011, 3, 12, 9, 20),
+ },
+ {
+ "start": datetime(2011, 3, 12, 9, 20),
+ "end": datetime(2011, 3, 12, 9, 35),
+ },
+ {
+ "start": datetime(2011, 3, 12, 9, 35),
+ "end": datetime(2011, 3, 12, 10, 5),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 12, 10, 5),
+ "end": datetime(2011, 3, 12, 10, 25),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 12, 17, 30),
+ "end": datetime(2011, 3, 12, 18, 0),
+ },
+ ],
+]
+
+
+saturday_slots_type_1 = [
+ [
+ {
+ "start": datetime(2011, 3, 12, 10, 25),
+ "end": datetime(2011, 3, 12, 11, 5),
+ },
+ {
+ "start": datetime(2011, 3, 12, 11, 5),
+ "end": datetime(2011, 3, 12, 11, 45),
+ },
+ {
+ "start": datetime(2011, 3, 12, 11, 45),
+ "end": datetime(2011, 3, 12, 12, 30),
+ },
+ ],
+ [
+ {
+ "title": "Lunch",
+ "start": datetime(2011, 3, 12, 12, 30),
+ "end": datetime(2011, 3, 12, 13, 35),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 12, 13, 35),
+ "end": datetime(2011, 3, 12, 14, 15),
+ },
+ {
+ "start": datetime(2011, 3, 12, 14, 15),
+ "end": datetime(2011, 3, 12, 14, 55),
+ },
+ {
+ "start": datetime(2011, 3, 12, 14, 55),
+ "end": datetime(2011, 3, 12, 15, 40),
+ },
+ ],
+ [
+ {
+ "title": "Afternoon Break with Snacks in Expo Hall",
+ "start": datetime(2011, 3, 12, 15, 40),
+ "end": datetime(2011, 3, 12, 16, 15),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 12, 16, 15),
+ "end": datetime(2011, 3, 12, 16, 55),
+ },
+ {
+ "start": datetime(2011, 3, 12, 16, 55),
+ "end": datetime(2011, 3, 12, 17, 30),
+ },
+ ]
+]
+
+saturday_slots_type_2 = [
+ [
+ {
+ "start": datetime(2011, 3, 12, 10, 25),
+ "end": datetime(2011, 3, 12, 11, 5),
+ },
+ {
+ "start": datetime(2011, 3, 12, 11, 5),
+ "end": datetime(2011, 3, 12, 11, 45),
+ },
+ {
+ "start": datetime(2011, 3, 12, 11, 45),
+ "end": datetime(2011, 3, 12, 12, 15),
+ },
+ ],
+ [
+ {
+ "title": "Lunch",
+ "start": datetime(2011, 3, 12, 12, 15),
+ "end": datetime(2011, 3, 12, 13, 20),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 12, 13, 20),
+ "end": datetime(2011, 3, 12, 14, 15),
+ },
+ {
+ "start": datetime(2011, 3, 12, 14, 15),
+ "end": datetime(2011, 3, 12, 14, 55),
+ },
+ {
+ "start": datetime(2011, 3, 12, 14, 55),
+ "end": datetime(2011, 3, 12, 15, 25),
+ },
+ ],
+ [
+ {
+ "title": "Afternoon Break with Snacks in Expo Hall",
+ "start": datetime(2011, 3, 12, 15, 25),
+ "end": datetime(2011, 3, 12, 16, 0),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 12, 16, 0),
+ "end": datetime(2011, 3, 12, 16, 55),
+ },
+ {
+ "start": datetime(2011, 3, 12, 16, 55),
+ "end": datetime(2011, 3, 12, 17, 30),
+ },
+ ]
+]
+
+
+sunday_plenaries = [
+ [
+ {
+ "start": datetime(2011, 3, 13, 7, 0),
+ "end": datetime(2011, 3, 13, 8, 30),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 13, 8, 30),
+ "end": datetime(2011, 3, 13, 9, 0),
+ },
+ {
+ "start": datetime(2011, 3, 13, 9, 0),
+ "end": datetime(2011, 3, 13, 9, 5),
+ },
+ {
+ "start": datetime(2011, 3, 13, 9, 5),
+ "end": datetime(2011, 3, 13, 9, 20),
+ },
+ {
+ "start": datetime(2011, 3, 13, 9, 20),
+ "end": datetime(2011, 3, 13, 9, 35),
+ },
+ {
+ "start": datetime(2011, 3, 13, 9, 35),
+ "end": datetime(2011, 3, 13, 10, 5),
+ },
+ ],
+ [
+ {
+ "title": "Break with Snacks in Poster Area",
+ "start": datetime(2011, 3, 13, 10, 5),
+ "end": datetime(2011, 3, 13, 10, 25),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 13, 10, 25),
+ "end": datetime(2011, 3, 13, 11, 55),
+ },
+ ],
+ [
+ {
+ "title": "Lunch",
+ "start": datetime(2011, 3, 13, 12, 25),
+ "end": datetime(2011, 3, 13, 13, 15),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 13, 14, 35),
+ "end": datetime(2011, 3, 13, 15, 35),
+ },
+ {
+ "start": datetime(2011, 3, 13, 15, 35),
+ "end": datetime(2011, 3, 13, 15, 55),
+ },
+ ],
+]
+
+sunday_type_1 = [
+ [
+ {
+ "start": datetime(2011, 3, 13, 11, 55),
+ "end": datetime(2011, 3, 13, 12, 25),
+ },
+ ],
+ [
+ {
+ "start": datetime(2011, 3, 13, 13, 15),
+ "end": datetime(2011, 3, 13, 13, 55),
+ },
+ {
+ "start": datetime(2011, 3, 13, 13, 55),
+ "end": datetime(2011, 3, 13, 14, 35),
+ },
+ ],
+]
+
+
+tracks = [
+ {"Centennial I": [friday_slots_type_1, saturday_slots_type_1, sunday_type_1]},
+ {"Centennial II": [friday_slots_type_2, saturday_slots_type_2, sunday_type_1]},
+ {"Centennial III": [friday_slots_type_1, saturday_slots_type_1, sunday_type_1]},
+ {"Centennial IV": [friday_slots_type_2, saturday_slots_type_2, sunday_type_1]},
+ {"Regency V": [friday_slots_type_1, saturday_slots_type_1, sunday_type_1]}
+]
+
+
+class Command(BaseCommand):
+
+ def handle(self, *args, **options):
+ for track_data in tracks:
+ for track_name, data in track_data.items():
+ track = Track.objects.create(name=track_name)
+ print "Created Track: %s" % track_name
+ for day in data:
+ for session_data in day:
+ session = Session.objects.create(track=track)
+ print "\tCreated session for %s" % track_name
+ for slot_data in session_data:
+ slot = Slot.objects.create(
+ track=track,
+ session=session,
+ start=slot_data.get("start"),
+ end=slot_data.get("end"),
+ title=slot_data.get("title")
+ )
+ print "\t\tCreated slot: %s" % slot
+ print "Plenaries"
+ for data in [friday_plenaries, saturday_plenaries, sunday_plenaries]:
+ for session_data in data:
+ for slot_data in session_data:
+ slot = Slot.objects.create(
+ track=None,
+ session=None,
+ start=slot_data.get("start"),
+ end=slot_data.get("end"),
+ title=slot_data.get("title")
+ )
+ print "\tCreated slot: %s" % slot
View
145 pycon_project/apps/schedule/models.py
@@ -3,36 +3,123 @@
from django.db import models
+from django.contrib.auth.models import User
+from django.contrib.contenttypes.models import ContentType
+
from biblion import creole_parser
+class Track(models.Model):
+
+ name = models.CharField(max_length=65)
+
+ def __unicode__(self):
+ return self.name
+
+
+class Session(models.Model):
+
+ track = models.ForeignKey(Track, null=True, related_name="sessions")
+
+ def sorted_slots(self):
+ return self.slots.order_by("start")
+
+ # @@@ cache?
+ def start(self):
+ slots = self.sorted_slots()
+ if slots:
+ return list(slots)[0].start
+ else:
+ return None
+
+ # @@@ cache?
+ def end(self):
+ slots = self.sorted_slots()
+ if slots:
+ return list(slots)[-1].end
+ else:
+ return None
+
+ def __unicode__(self):
+ start = self.start()
+ end = self.end()
+ return u"[%s] %s: %s%s" % (
+ self.track.name,
+ start.strftime("%a"),
+ start.strftime("%X"),
+ end.strftime("%X")
+ )
+
+
+class SessionRole(models.Model):
+
+ SESSION_ROLE_CHAIR = 1
+ SESSION_ROLE_RUNNER = 2
+
+ SESSION_ROLE_TYPES = [
+ (SESSION_ROLE_CHAIR, "Session Chair"),
+ (SESSION_ROLE_RUNNER, "Session Runner"),
+ ]
+
+ session = models.ForeignKey(Session)
+ user = models.ForeignKey(User)
+ role = models.IntegerField(choices=SESSION_ROLE_TYPES)
+ status = models.NullBooleanField()
+
+ submitted = models.DateTimeField(default = datetime.datetime.now)
+
+ class Meta:
+ unique_together = [("session", "user", "role")]
+
+
# @@@ precreate the Slots with proposal == None and then making the schedule is just updating slot.proposal and/or title/notes
class Slot(models.Model):
+ title = models.CharField(max_length=100, null=True, blank=True)
start = models.DateTimeField()
end = models.DateTimeField()
+ kind = models.ForeignKey(ContentType, null=True)
+ track = models.ForeignKey(Track, null=True, related_name="slots")
+ session = models.ForeignKey(Session, null=True, related_name="slots")
+
+ def content(self):
+ if self.kind_id:
+ return self.kind.get_object_for_this_type(slot=self)
+ else:
+ return None
- title = models.CharField(max_length=255, null=True, blank=True)
+ def assign(self, content, old_content=None):
+ if old_content is not None:
+ old_content.slot = None
+ old_content.save()
+ content.slot = self
+ content.save()
+ self.kind = ContentType.objects.get_for_model(content)
+ self.save()
+
+ def unassign(self):
+ content = self.content()
+ content.slot = None
+ content.save()
+ self.kind = None
+ self.save()
def __unicode__(self):
return u"%s: %s%s" % (self.start.strftime("%a"), self.start.strftime("%X"), self.end.strftime("%X"))
- def sessions(self):
- return self.session_set.all().order_by("track")
-
-class Session(models.Model):
+class Presentation(models.Model):
- SESSION_TYPE_TALK = 1
- SESSION_TYPE_PANEL = 2
- SESSION_TYPE_TUTORIAL = 3
- SESSION_TYPE_POSTER = 4
+ PRESENTATION_TYPE_TALK = 1
+ PRESENTATION_TYPE_PANEL = 2
+ PRESENTATION_TYPE_TUTORIAL = 3
+ PRESENTATION_TYPE_POSTER = 4
- SESSION_TYPES = [
- (SESSION_TYPE_TALK, "Talk"),
- (SESSION_TYPE_PANEL, "Panel"),
- (SESSION_TYPE_TUTORIAL, "Tutorial"),
- (SESSION_TYPE_POSTER, "Poster")
+ PRESENTATION_TYPES = [
+ (PRESENTATION_TYPE_TALK, "Talk"),
+ (PRESENTATION_TYPE_PANEL, "Panel"),
+ (PRESENTATION_TYPE_TUTORIAL, "Tutorial"),
+ (PRESENTATION_TYPE_POSTER, "Poster")
]
AUDIENCE_LEVEL_NOVICE = 1
@@ -43,16 +130,14 @@ class Session(models.Model):
(AUDIENCE_LEVEL_EXPERIENCED, "Experienced"),
]
- slot = models.ForeignKey(Slot, null=True, blank=True)
- track = models.CharField(max_length=10, null=True, blank=True)
- plenary = models.BooleanField(default=False)
+ slot = models.OneToOneField(Slot, null=True, blank=True, related_name="presentation")
title = models.CharField(max_length=100)
description = models.TextField(
max_length = 400, # @@@ need to enforce 400 in UI
help_text = "Brief one paragraph blurb (will be public if accepted). Must be 400 characters or less"
)
- session_type = models.IntegerField(choices=SESSION_TYPES)
+ presentation_type = models.IntegerField(choices=PRESENTATION_TYPES)
abstract = models.TextField(
help_text = "More detailed description (will be public if accepted). You can use <a href='http://wikicreole.org/' target='_blank'>creole</a> markup. <a id='preview' href='#'>Preview</a>",
)
@@ -72,12 +157,32 @@ class Session(models.Model):
def save(self, *args, **kwargs):
self.abstract_html = creole_parser.parse(self.abstract)
- super(Session, self).save(*args, **kwargs)
+ super(Presentation, self).save(*args, **kwargs)
def speakers(self):
yield self.speaker
for speaker in self.additional_speakers.all():
yield speaker
-
+
def __unicode__(self):
return u"%s" % self.title
+
+
+class Plenary(models.Model):
+
+ slot = models.OneToOneField(Slot, null=True, blank=True, related_name="plenary")
+ title = models.CharField(max_length=100)
+ speaker = models.ForeignKey("speakers.Speaker", null=True, blank=True, related_name="+")
+ additional_speakers = models.ManyToManyField("speakers.Speaker", blank=True)
+ description = models.TextField(max_length=400, blank=True)
+
+
+class Recess(models.Model):
+ """
+ We call this recess due to Break resulting in using break (lower-case "b")
+ which is a Python keyword.
+ """
+
+ slot = models.OneToOneField(Slot, null=True, blank=True, related_name="recess")
+ title = models.CharField(max_length=100)
+ description = models.TextField(max_length=400, blank=True)
View
15 pycon_project/apps/schedule/urls.py
@@ -4,7 +4,7 @@
urlpatterns = patterns("schedule.views",
# url(r"^$", "schedule_list", name="schedule_list"),
- # url(r"^sessions/(\d+)/", "schedule_session", name="schedule_session"),
+ # url(r"^presentations/(\d+)/", "schedule_presentation", name="schedule_presentation"),
url(r"^$", direct_to_template, {"template": "schedule/index.html"}, name="schedule_index"),
@@ -13,5 +13,16 @@
url(r"^lists/posters/", "schedule_list_posters", name="schedule_list_posters"),
url(r"^tutorials/", "schedule_tutorials", name="schedule_tutorials"),
- url(r"^sessions/(\d+)/", "schedule_session", name="schedule_session"),
+ url(r"^conference/edit/$", "schedule_conference_edit", name="schedule_conference_edit"),
+ url(r"^presentations/(\d+)/", "schedule_presentation", name="schedule_presentation"),
+
+ url(r"slot/(\d+)/edit/$", "schedule_slot_edit", name="schedule_slot_edit"),
+ url(r"slot/(\d+)/remove/$", "schedule_slot_remove", name="schedule_slot_remove"),
+ url(r"^slot/(\d+)/(\w+)/$", "schedule_slot_add", name="schedule_slot_add"),
+
+ url(r"^tracks/$", "track_list", name="schedule_track_list"),
+ url(r"^sessions/$", "session_list", name="schedule_session_list"),
+ url(r"^track/(\d+)/$", "track_detail", name="schedule_track_detail"),
+ url(r"^track/none/$", "track_detail_none", name="schedule_notrack_detail"),
+ url(r"^session/(\d+)/$", "session_detail", name="schedule_session_detail"),
)
View
298 pycon_project/apps/schedule/views.py
@@ -1,10 +1,29 @@
+import datetime
+import itertools
+
from django.conf import settings
-from django.shortcuts import render_to_response, get_object_or_404
+from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.template import RequestContext
-from proposals.models import Proposal
-from review.models import ProposalResult
-from schedule.models import Slot, Session
+from django.contrib.auth.decorators import login_required
+
+from schedule.forms import PlenaryForm, RecessForm, PresentationForm
+from schedule.models import Slot, Presentation, Track, Session, SessionRole
+
+
+wed_morn_start = datetime.datetime(2011, 3, 9, 9, 0) # 9AM Eastern
+wed_morn_end = datetime.datetime(2011, 3, 9, 12, 20) # 12:20PM Eastern
+wed_after_start = datetime.datetime(2011, 3, 9, 14, 0) # 2PM Eastern
+wed_after_end = datetime.datetime(2011, 3, 9, 16, 40) # 4:40PM Eastern
+thu_morn_start = datetime.datetime(2011, 3, 10, 9, 0) # 9AM Eastern
+thu_morn_end = datetime.datetime(2011, 3, 10, 12, 20) # 12:20PM Eastern
+thu_after_start = datetime.datetime(2011, 3, 10, 14, 0) # 2PM Eastern
+thu_after_end = datetime.datetime(2011, 3, 10, 16, 40) # 4:40PM Eastern
+
+WEDNESDAY_MORNING = (wed_morn_start, wed_morn_end)
+WEDNESDAY_AFTERNOON = (wed_after_start, wed_after_end)
+THURSDAY_MORNING = (thu_morn_start, thu_morn_end)
+THURSDAY_AFTERNOON = (thu_after_start, thu_after_end)
def schedule_list(request, template_name="schedule/schedule_list.html", extra_context=None):
@@ -20,23 +39,23 @@ def schedule_list(request, template_name="schedule/schedule_list.html", extra_co
}, **extra_context), context_instance=RequestContext(request))
-def schedule_session(request, session_id, template_name="schedule/session_detail.html", extra_context=None):
+def schedule_presentation(request, presentation_id, template_name="schedule/presentation_detail.html", extra_context=None):
if extra_context is None:
extra_context = {}
- session = get_object_or_404(Session, id=session_id)
+ presentation = get_object_or_404(Presentation, id=presentation_id)
return render_to_response(template_name, dict({
- "session": session,
+ "presentation": presentation,
"timezone": settings.SCHEDULE_TIMEZONE,
}, **extra_context), context_instance=RequestContext(request))
def schedule_list_talks(request):
- talks = Session.objects.filter(
- session_type__in=[Session.SESSION_TYPE_PANEL, Session.SESSION_TYPE_TALK]
+ talks = Presentation.objects.filter(
+ presentation_type__in=[Presentation.PRESENTATION_TYPE_PANEL, Presentation.PRESENTATION_TYPE_TALK]
)
talks = talks.order_by("pk")
@@ -47,8 +66,8 @@ def schedule_list_talks(request):
def schedule_list_tutorials(request):
- tutorials = Session.objects.filter(
- session_type=Session.SESSION_TYPE_TUTORIAL
+ tutorials = Presentation.objects.filter(
+ presentation_type=Presentation.PRESENTATION_TYPE_TUTORIAL
)
tutorials = tutorials.order_by("pk")
@@ -59,8 +78,8 @@ def schedule_list_tutorials(request):
def schedule_list_posters(request):
- posters = Session.objects.filter(
- session_type=Session.SESSION_TYPE_POSTER
+ posters = Presentation.objects.filter(
+ presentation_type=Presentation.PRESENTATION_TYPE_POSTER
)
posters = posters.order_by("pk")
@@ -74,22 +93,30 @@ def schedule_tutorials(request):
tutorials = {
"wed": {
"morning": {
- "slot": Slot.objects.get(id=1),
- "sessions": Session.objects.filter(slot=1).order_by("pk"),
+ "slot": WEDNESDAY_MORNING,
+ "presentations": Presentation.objects.filter(
+ slot__start=WEDNESDAY_MORNING[0]
+ ).order_by("pk"),
},
"afternoon": {
- "slot": Slot.objects.get(id=2),
- "sessions": Session.objects.filter(slot=2).order_by("pk"),
+ "slot": WEDNESDAY_AFTERNOON,
+ "presentations": Presentation.objects.filter(
+ slot__start=WEDNESDAY_AFTERNOON[0]
+ ).order_by("pk"),
}
- },
+ },
"thurs": {
"morning": {
- "slot": Slot.objects.get(id=3),
- "sessions": Session.objects.filter(slot=3).order_by("pk"),
+ "slot": THURSDAY_MORNING,
+ "presentations": Presentation.objects.filter(
+ slot__start=THURSDAY_MORNING[0]
+ ).order_by("pk"),
},
"afternoon": {
- "slot": Slot.objects.get(id=4),
- "sessions": Session.objects.filter(slot=4).order_by("pk"),
+ "slot": THURSDAY_AFTERNOON,
+ "presentations": Presentation.objects.filter(
+ slot__start=THURSDAY_AFTERNOON[0]
+ ).order_by("pk"),
}
}
}
@@ -99,3 +126,230 @@ def schedule_tutorials(request):
}
ctx = RequestContext(request, ctx)
return render_to_response("schedule/tutorials.html", ctx)
+
+
+class Timetable(object):
+
+ def __init__(self, slots):
+ self.slots = slots
+
+ @property
+ def tracks(self):
+ return Track.objects.filter(
+ pk__in = self.slots.exclude(track=None).values_list("track", flat=True).distinct()
+ ).order_by("name")
+
+ def __iter__(self):
+ times = sorted(set(itertools.chain(*self.slots.values_list("start", "end"))))
+ slots = list(self.slots.order_by("start", "track__name"))
+ row = []
+ for time in times:
+ row = {"time": time, "slots": []}
+ for slot in slots:
+ if slot.start == time:
+ slot.rowspan = Timetable.rowspan(times, slot.start, slot.end)
+ row["slots"].append(slot)
+ if len(row["slots"]) == 1:
+ row["colspan"] = len(self.tracks)
+ else:
+ row["colspan"] = 1
+ yield row
+
+ @staticmethod
+ def rowspan(times, start, end):
+ return times.index(end) - times.index(start)
+
+
+@login_required
+def schedule_conference_edit(request):
+ if not request.user.is_staff:
+ return redirect("/")
+ ctx = {
+ "friday": Timetable(Slot.objects.filter(start__week_day=6)),
+ "saturday": Timetable(Slot.objects.filter(start__week_day=7)),
+ "sunday": Timetable(Slot.objects.filter(start__week_day=1)),
+ }
+ ctx = RequestContext(request, ctx)
+ return render_to_response("schedule/conference_edit.html", ctx)
+
+
+@login_required
+def schedule_slot_add(request, slot_id, kind):
+
+ if not request.user.is_staff:
+ return redirect("/")
+
+ slot = Slot.objects.get(pk=slot_id)
+
+ form_class = {
+ "plenary": PlenaryForm,
+ "break": RecessForm,
+ "presentation": PresentationForm,
+ }[kind]
+
+ if request.method == "POST":
+ form = form_class(request.POST)
+
+ if form.is_valid():
+ slot_content = form.save(commit=False)
+ slot.assign(slot_content)
+ return redirect("schedule_conference_edit")
+ else:
+ form = form_class()
+
+ ctx = {
+ "slot": slot,
+ "kind": kind,
+ "form": form,
+ "add": True,
+ }
+ ctx = RequestContext(request, ctx)
+ return render_to_response("schedule/slot_place.html", ctx)
+
+
+@login_required
+def schedule_slot_edit(request, slot_id):
+
+ if not request.user.is_staff:
+ return redirect("/")
+
+ slot = Slot.objects.get(pk=slot_id)
+ kind = slot.kind.name
+
+ form_tuple = {
+ "plenary": (PlenaryForm, {"instance": slot.content()}),
+ "recess": (RecessForm, {"instance": slot.content()}),
+ "presentation": (PresentationForm, {"initial": {"presentation": slot.content()}}),
+ }[kind]
+
+ if request.method == "POST":
+ form = form_tuple[0](request.POST, **form_tuple[1])
+
+ if form.is_valid():
+ slot_content = form.save(commit=False)
+ slot.assign(slot_content, old_content=slot.content())
+ return redirect("schedule_conference_edit")
+ else:
+ form = form_tuple[0](**form_tuple[1])
+
+ ctx = {
+ "slot": slot,
+ "kind": kind,
+ "form": form,
+ "add": False,
+ }
+ ctx = RequestContext(request, ctx)
+ return render_to_response("schedule/slot_place.html", ctx)
+
+
+@login_required
+def schedule_slot_remove(request, slot_id):
+
+ if not request.user.is_staff:
+ return redirect("/")
+
+ slot = Slot.objects.get(pk=slot_id)
+
+ if request.method == "POST":
+ slot.unassign()
+ return redirect("schedule_conference_edit")
+
+ ctx = {
+ "slot": slot,
+ }
+ ctx = RequestContext(request, ctx)
+ return render_to_response("schedule/slot_remove.html", ctx)
+
+
+def track_list(request):
+
+ tracks = Track.objects.order_by("name")
+
+ return render_to_response("schedule/track_list.html", {
+ "tracks": tracks,
+ }, context_instance=RequestContext(request))
+
+
+def track_detail(request, track_id):
+
+ track = get_object_or_404(Track, id=track_id)
+
+ return render_to_response("schedule/track_detail.html", {
+ "track": track,
+ }, context_instance=RequestContext(request))
+
+
+def track_detail_none(request):
+
+ sessions = Session.objects.filter(track=None)
+ slots = Slot.objects.filter(track=None)
+
+ return render_to_response("schedule/track_detail_none.html", {
+ "sessions": sessions,
+ "slots": slots,
+ }, context_instance=RequestContext(request))
+
+
+def session_list(request):
+
+ sessions = Session.objects.all()
+
+ return render_to_response("schedule/session_list.html", {
+ "sessions": sessions,
+ }, context_instance=RequestContext(request))
+
+
+def session_detail(request, session_id):
+
+ session = get_object_or_404(Session, id=session_id)
+
+ chair = None
+ chair_denied = False
+ chairs = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_CHAIR).exclude(status=False)
+ if chairs:
+ chair = chairs[0].user
+ else:
+ if request.user.is_authenticated():
+ # did the current user previously try to apply and got rejected?
+ if SessionRole.objects.filter(session=session, user=request.user, role=SessionRole.SESSION_ROLE_CHAIR, status=False):
+ chair_denied = True
+
+ runner = None
+ runner_denied = False
+ runners = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_RUNNER).exclude(status=False)
+ if runners:
+ runner = runners[0].user
+ else:
+ if request.user.is_authenticated():
+ # did the current user previously try to apply and got rejected?
+ if SessionRole.objects.filter(session=session, user=request.user, role=SessionRole.SESSION_ROLE_RUNNER, status=False):
+ runner_denied = True
+
+ if request.method == "POST" and request.user.is_authenticated():
+ role = request.POST.get("role")
+ if role == "chair":
+ if chair == None and not chair_denied:
+ SessionRole(session=session, role=SessionRole.SESSION_ROLE_CHAIR, user=request.user).save()
+ elif role == "runner":
+ if runner == None and not runner_denied:
+ SessionRole(session=session, role=SessionRole.SESSION_ROLE_RUNNER, user=request.user).save()
+ elif role == "un-chair":
+ if chair == request.user:
+ session_role = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_CHAIR, user=request.user)
+ if session_role:
+ session_role[0].delete()
+ elif role == "un-runner":
+ if runner == request.user:
+ session_role = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_RUNNER, user=request.user)
+ if session_role:
+ session_role[0].delete()
+
+ return redirect("schedule_session_detail", session_id)
+
+ return render_to_response("schedule/session_detail.html", {
+ "session": session,
+ "chair": chair,
+ "chair_denied": chair_denied,
+ "runner": runner,
+ "runner_denied": runner_denied,
+ }, context_instance=RequestContext(request))
View
24 pycon_project/apps/user_mailer/user_lists.py
@@ -1,6 +1,6 @@
from django.contrib.auth.models import Group, User
-from schedule.models import Session
+from schedule.models import Presentation
# @@@ Would probably be a good idea to consolidate the
@@ -9,41 +9,41 @@
def accepted_speakers():
speakers = set()
- for session in Session.objects.select_related("speaker__user"):
- for speaker in session.speakers():
+ for presentation in Presentation.objects.select_related("speaker__user"):
+ for speaker in presentation.speakers():
if speaker is not None and speaker.user is not None:
speakers.add(speaker.user)
return iter(speakers)
def accepted_talk_speakers():
speakers = set()
- talks = Session.objects.filter(session_type=Session.SESSION_TYPE_TALK)
+ talks = Presentation.objects.filter(presentation_type=Presentation.PRESENTATION_TYPE_TALK)
- for session in talks.select_related("speaker__user"):
- for speaker in session.speakers():
+ for presentation in talks.select_related("speaker__user"):
+ for speaker in presentation.speakers():
if speaker is not None and speaker.user is not None:
speakers.add(speaker.user)
return iter(speakers)
def accepted_panel_speakers():
speakers = set()
- panels = Session.objects.filter(session_type=Session.SESSION_TYPE_PANEL)
+ panels = Presentation.objects.filter(presentation_type=Presentation.PRESENTATION_TYPE_PANEL)
- for session in panels.select_related("speaker__user"):
- for speaker in session.speakers():
+ for presentation in panels.select_related("speaker__user"):
+ for speaker in presentation.speakers():
if speaker is not None and speaker.user is not None:
speakers.add(speaker.user)
return iter(speakers)
def accepted_tutorial_speakers():
speakers = set()
- panels = Session.objects.filter(session_type=Session.SESSION_TYPE_TUTORIAL)
+ panels = Presentation.objects.filter(presentation_type=Presentation.PRESENTATION_TYPE_TUTORIAL)
- for session in panels.select_related("speaker__user"):
- for speaker in session.speakers():
+ for presentation in panels.select_related("speaker__user"):
+ for speaker in presentation.speakers():
if speaker is not None and speaker.user is not None:
speakers.add(speaker.user)
return iter(speakers)
View
21 pycon_project/migrations/009_refactor_slot.sql
@@ -0,0 +1,21 @@
+DROP TABLE "schedule_slot" CASCADE;
+
+### New Model: schedule.Track
+CREATE TABLE "schedule_track" (
+ "id" serial NOT NULL PRIMARY KEY,
+ "name" varchar(65) NOT NULL
+);
+### New Model: schedule.Slot
+CREATE TABLE "schedule_slot" (
+ "id" serial NOT NULL PRIMARY KEY,
+ "start" timestamp with time zone NOT NULL,
+ "end" timestamp with time zone NOT NULL,
+ "kind_id" integer REFERENCES "django_content_type" ("id") DEFERRABLE INITIALLY DEFERRED,
+ "track_id" integer NOT NULL REFERENCES "schedule_track" ("id") DEFERRABLE INITIALLY DEFERRED
+);
+
+UPDATE "schedule_session" SET "slot_id" = NULL;
+
+ALTER TABLE "schedule_session" ADD CONSTRAINT "schedule_session_slot_id_fkey" FOREIGN KEY ("slot_id")
+ REFERENCES "schedule_slot" ("id") MATCH SIMPLE
+ ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED;
View
29 pycon_project/migrations/010_rename_session.sql
@@ -0,0 +1,29 @@
+ALTER TABLE "schedule_session_additional_speakers" RENAME TO "schedule_presentation_additional_speakers";
+ALTER TABLE "schedule_session" RENAME TO "schedule_presentation";
+
+ALTER INDEX "schedule_session_additional_speakers_session_id_key" RENAME TO "schedule_presentation_additional_speakers_presentation_id_key";
+ALTER INDEX "schedule_session_additional_speakers_id_seq" RENAME TO "schedule_presentation_additional_speakers_id_seq";
+ALTER INDEX "schedule_session_id_seq" RENAME TO "schedule_presentation_id_seq";
+ALTER INDEX "schedule_session_additional_speakers_pkey" RENAME TO "schedule_presentation_additional_speakers_pkey";
+ALTER INDEX "schedule_session_pkey" RENAME TO "schedule_presentation_pkey";
+ALTER INDEX "schedule_session_additional_speakers_session_id" RENAME TO "schedule_presentation_additional_speakers_session_id";
+
+ALTER TABLE "schedule_presentation" RENAME COLUMN "session_type" TO "presentation_type";
+ALTER TABLE "schedule_presentation_additional_speakers" RENAME COLUMN "session_id" TO "presentation_id";
+
+ALTER TABLE "schedule_presentation_additional_speakers" ADD CONSTRAINT "presentation_id_refs_id_87e9a6e4" FOREIGN KEY ("presentation_id") REFERENCES "schedule_presentation" ("id") DEFERRABLE INITIALLY DEFERRED;
+
+CREATE INDEX "schedule_presentation_additional_speakers_presentation_id" ON "schedule_presentation_additional_speakers" ("presentation_id");
+CREATE INDEX "schedule_presentation_additional_speakers_speaker_id" ON "schedule_presentation_additional_speakers" ("speaker_id");
+CREATE INDEX "schedule_presentation_slot_id" ON "schedule_presentation" ("slot_id");
+CREATE INDEX "schedule_presentation_speaker_id" ON "schedule_presentation" ("speaker_id");
+
+CREATE TABLE "schedule_session" (
+ "id" serial NOT NULL PRIMARY KEY,
+ "track_id" integer REFERENCES "schedule_track" ("id") DEFERRABLE INITIALLY DEFERRED
+)
+;
+
+ALTER TABLE "schedule_slot" ALTER COLUMN "track_id" DROP NOT NULL;
+ALTER TABLE "schedule_slot" ADD COLUMN "session_id" integer REFERENCES "schedule_session" ("id") DEFERRABLE INITIALLY DEFERRED;
+ALTER TABLE "schedule_slot" ADD COLUMN "title" varchar(100);
View
1 pycon_project/migrations/011_add_slot_unique.sql
@@ -0,0 +1 @@
+ALTER TABLE "schedule_presentation" ADD CONSTRAINT "schedule_presentation_slot_id_key" UNIQUE ("slot_id");
View
87 pycon_project/migrations/012_create_new_sessions_slots.py
@@ -0,0 +1,87 @@
+def migrate():
+ from datetime import datetime
+
+ from django.contrib.contenttypes.models import ContentType
+
+ from schedule.models import Slot, Presentation, Track
+
+ wed_morn_start = datetime(2011, 3, 9, 9, 0) # 9AM Eastern
+ wed_morn_end = datetime(2011, 3, 9, 12, 20) # 12:20PM Eastern
+ wed_after_start = datetime(2011, 3, 9, 14, 0) # 2PM Eastern
+ wed_after_end = datetime(2011, 3, 9, 16, 40) # 4:40PM Eastern
+ thu_morn_start = datetime(2011, 3, 10, 9, 0) # 9AM Eastern
+ thu_morn_end = datetime(2011, 3, 10, 12, 20) # 12:20PM Eastern
+ thu_after_start = datetime(2011, 3, 10, 14, 0) # 2PM Eastern
+ thu_after_end = datetime(2011, 3, 10, 16, 40) # 4:40PM Eastern
+
+ slots = [
+ {
+ "start": wed_morn_start,
+ "end": wed_morn_end,
+ "titles": [
+ "Python 101",
+ "Pinax Solutions",
+ "web2py secrets",
+ "Scientific Python Tools not only for Scientists and Engineers",
+ "Distributed and Cloud computing with Python",
+ "Building your own tile server using OpenStreetMap",
+ "Advanced Python I"
+ ]
+ },
+ {
+ "start": wed_after_start,
+ "end": wed_after_end,
+ "titles": [
+ "Google App Engine workshop",
+ "Python For Total Beginners Using \"Learn Python The Hard Way\"",
+ "Mining and Visualizing Data from the Social Web with Python",
+ "Advanced Python II",
+ "Packet Crafting with Python",
+ "Packaging, Documenting, and Distributing your Python Codebase",
+ "Geospatial Computation and Visualization Cooperative Lab",
+ ]
+ },
+ {
+ "start": thu_morn_start,
+ "end": thu_morn_end,
+ "titles": [
+ "Hands on Beginning Python",
+ "Mastering Python 3 I/O",
+ "Creating GUI Applications in Python using Qt I",
+ "Python/Django deployment workshop",
+ "Applied machine learning in python with scikit-learn",
+ "Tutorial -- Doing Data Structures in Python",
+ "(Re-)Introduction to C for Pythonistas",
+ ]
+ },
+ {
+ "start": thu_after_start,
+ "end": thu_after_end,
+ "titles": [
+ "Hands on Intermediate Python",
+ "Cooking with Python 3",
+ "Creating GUI Applications in Python using Qt II",
+ "Faster Python Programs through Optimization",
+ "Writing Python extensions in C",
+ "Deploying web applications to the cloud",
+ "Documenting Your Project With Sphinx",
+ ]
+ }
+ ]
+
+ for slot in slots:
+ for i, title in enumerate(slot["titles"]):
+ track, _ = Track.objects.get_or_create(name="Tutorial %d" % (i+1))
+ s = Slot.objects.create(
+ start=slot["start"],
+ end=slot["end"],
+ kind=ContentType.objects.get_for_model(Presentation),
+ track=track,
+ )
+ try:
+ presentation = Presentation.objects.get(title=title, presentation_type=3)
+ presentation.slot = s
+ presentation.save()
+ print "Saved", title
+ except Presentation.DoesNotExist:
+ print "Missed", title
View
13 pycon_project/migrations/013_session_role_models.sql
@@ -0,0 +1,13 @@
+### New Model: schedule.SessionRole
+CREATE TABLE "schedule_sessionrole" (
+ "id" serial NOT NULL PRIMARY KEY,
+ "session_id" integer NOT NULL REFERENCES "schedule_session" ("id") DEFERRABLE INITIALLY DEFERRED,
+ "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED,
+ "role" integer NOT NULL,
+ "status" boolean,
+ "submitted" timestamp with time zone NOT NULL,
+ UNIQUE ("session_id", "user_id", "role")
+)
+;
+CREATE INDEX "schedule_sessionrole_session_id" ON "schedule_sessionrole" ("session_id");
+CREATE INDEX "schedule_sessionrole_user_id" ON "schedule_sessionrole" ("user_id");
View
29 pycon_project/migrations/014_add_plenary_and_recess.sql
@@ -0,0 +1,29 @@
+### New Model: schedule.Plenary_additional_speakers
+CREATE TABLE "schedule_plenary_additional_speakers" (
+ "id" serial NOT NULL PRIMARY KEY,
+ "plenary_id" integer NOT NULL,
+ "speaker_id" integer NOT NULL REFERENCES "speakers_speaker" ("id") DEFERRABLE INITIALLY DEFERRED,
+ UNIQUE ("plenary_id", "speaker_id")
+)
+;
+### New Model: schedule.Plenary
+CREATE TABLE "schedule_plenary" (
+ "id" serial NOT NULL PRIMARY KEY,
+ "slot_id" integer UNIQUE REFERENCES "schedule_slot" ("id") DEFERRABLE INITIALLY DEFERRED,
+ "title" varchar(100) NOT NULL,
+ "speaker_id" integer REFERENCES "speakers_speaker" ("id") DEFERRABLE INITIALLY DEFERRED,
+ "description" text NOT NULL
+)
+;
+ALTER TABLE "schedule_plenary_additional_speakers" ADD CONSTRAINT "plenary_id_refs_id_e737e1c0" FOREIGN KEY ("plenary_id") REFERENCES "schedule_plenary" ("id") DEFERRABLE INITIALLY DEFERRED;
+### New Model: schedule.Recess
+CREATE TABLE "schedule_recess" (
+ "id" serial NOT NULL PRIMARY KEY,
+ "slot_id" integer UNIQUE REFERENCES "schedule_slot" ("id") DEFERRABLE INITIALLY DEFERRED,
+ "title" varchar(100) NOT NULL,
+ "description" text NOT NULL
+)
+;
+CREATE INDEX "schedule_plenary_additional_speakers_plenary_id" ON "schedule_plenary_additional_speakers" ("plenary_id");
+CREATE INDEX "schedule_plenary_additional_speakers_speaker_id" ON "schedule_plenary_additional_speakers" ("speaker_id");
+CREATE INDEX "schedule_plenary_speaker_id" ON "schedule_plenary" ("speaker_id");
View
2 pycon_project/requirements/project.txt
@@ -23,4 +23,4 @@ docutils==0.6
django-wakawaka==0.4.dev4
django-markitup==0.6.1
django-fixture-generator==0.2.0
-nashvegas==0.2a1.dev4
+nashvegas==0.5
View
2 pycon_project/settings.py
@@ -100,7 +100,7 @@
"django_openid.consumer.SessionConsumer",
"django.contrib.messages.middleware.MessageMiddleware",
"pinax.middleware.security.HideSensistiveFieldsMiddleware",
- "debug_toolbar.middleware.DebugToolbarMiddleware",
+ #"debug_toolbar.middleware.DebugToolbarMiddleware",
]
ROOT_URLCONF = "pycon_project.urls"
View
33 pycon_project/templates/schedule/_grid.html
@@ -0,0 +1,33 @@
+<table>
+ <tr>
+ <th>&nbsp;</th>
+ {% for track in day.tracks %}
+ <th>{{ track.name }}</th>
+ {% endfor %}
+ </tr>
+ {% for row in day %}
+ <tr>
+ <td class="time">{{ row.time|date:"h:iA" }}</td>
+ {% for slot in row.slots %}
+ <td colspan="{{ row.colspan }}" rowspan="{{ slot.rowspan }}" class="slot {{ slot.kind.name }} rowspan-{{ slot.rowspan }} colspan-{{ row.colspan }}">
+ {% if not slot.content %}
+ <span class="controls"><b>add</b>: <a href="{% url schedule_slot_add slot.pk "plenary" %}" rel="facebox">plenary</a> | <a href="{% url schedule_slot_add slot.pk "break" %}" rel="facebox">break</a> | <a href="{% url schedule_slot_add slot.pk "presentation" %}" rel="facebox">presentation</a></span>
+ {% else %}
+ {% if slot.kind.name == "recess" %}
+ <div class="title">{{ slot.content.title }}</div>
+ {% else %}
+ {% if slot.kind.name == "plenary" %}
+ <div class="title">{{ slot.content.title }}</div>
+ <div class="speaker">{{ slot.content.speaker }}</div>
+ {% else %}
+ <div class="title">{{ slot.content.title }}</div>
+ <div class="speaker">{{ slot.content.speaker }}</div>
+ {% endif %}
+ {% endif %}
+ <span class="controls"><b>actions</b>: <a href="{% url schedule_slot_edit slot.pk %}" rel="facebox">edit</a> | <a href="{% url schedule_slot_remove slot.pk %}" rel="facebox">remove</a></span>
+ {% endif %}
+ </td>
+ {% endfor %}
+ </tr>
+ {% endfor %}
+</table>
View
15 pycon_project/templates/schedule/_presentation.html
@@ -0,0 +1,15 @@
+<div class="presentation">
+ <div class="title">
+ <a href="{% url schedule_presentation presentation.pk %}">{{ presentation.title }}</a>
+ </div>
+ <div class="metadata">
+ {{ presentation.get_audience_level_display }}
+ {{ presentation.get_presentation_type_display }}
+ by
+ {{ presentation.speaker }}
+ {% if presentation.additional_speakers.all %}
+ with
+ {{ presentation.additional_speakers.all|join:", " }}
+ {% endif %}
+ </div>
+</div>
View
15 pycon_project/templates/schedule/_session.html
@@ -1,15 +0,0 @@
-<div class="session">
- <div class="title">
- <a href="{% url schedule_session session.pk %}">{{ session.title }}</a>
- </div>
- <div class="metadata">
- {{ session.get_audience_level_display }}
- {{ session.get_session_type_display }}
- by
- {{ session.speaker }}
- {% if session.additional_speakers.all %}
- with
- {{ session.additional_speakers.all|join:", " }}
- {% endif %}
- </div>
-</div>
View
137 pycon_project/templates/schedule/conference_edit.html
@@ -0,0 +1,137 @@
+{% extends "site_base.html" %}
+
+{% block head_title %}Conference Schedule Edit{% endblock %}
+
+{% block body_class %}full{% endblock %}
+
+{% block right %}
+{% endblock %}
+
+{% block extra_head %}
+ <link rel="stylesheet" href="{{ STATIC_URL }}css/facebox.css" />
+ <style>
+ body {
+ font-family: "Helvetica Neue", Arial, "Helvetica";
+ }
+ #wrapper {
+ width: 960px;
+ }
+ table {
+ border-collapse: collapse;
+ font-weight: normal;
+ line-height: 1.1em;
+ }
+ td {
+ padding: 4px 8px;
+ border: 1px solid #666;
+ vertical-align: top;
+ }
+ td.slot {
+ text-align: center;
+ background: #FFF;
+ font-size: 10pt;
+ width: 180px;
+ }
+ td.slot.recess,
+ td.slot.recess.colspan-1.rowspan-1,
+ td.slot.recess.colspan-1.rowspan-2 {
+ background: #ECFFFF;
+ }
+ td.slot.plenary {
+ background: #DCDCFF;
+ }
+ td.slot div.title {
+ font-weight: bold;
+ }
+ td.slot div.speaker {
+ font-style: italic;
+ font-size: 8pt;
+ }
+ th {
+ font-size: 12pt;
+ background: #FFF;
+ padding: 2px 4px;
+ border: none;
+ border-right: 1px solid #666;
+ }
+ td.time {
+ width: 50px;
+ font-size: 9pt;
+ border: none;
+ border-top: 1px solid #666;
+ }
+ td.colspan-1.rowspan-1 {
+ background: #FFFCC9;
+ }
+ td.colspan-1.rowspan-2 {
+ background: #FDA;
+ }
+ td.slot:hover .controls {
+ display: block;
+ }
+ .controls {
+ display: none;
+ font-size: 8pt;
+ }
+ .controls a {
+ text-decoration: none;
+ color: #000;
+ }
+ .controls a:hover {
+ color: #999;
+ }
+ #facebox h3 {
+ margin-top: 0;
+ }
+ </style>
+{% endblock %}
+
+{% block body %}
+ <h1>Conference Schedule Edit</h1>
+
+ <h2>Friday</h2>
+
+ {% with friday as day %}{% include "schedule/_grid.html" %}{% endwith %}
+
+ <h2>Saturday</h2>
+
+ {% with saturday as day %}{% include "schedule/_grid.html" %}{% endwith %}
+
+ <h2>Sunday</h2>
+
+ {% with sunday as day %}{% include "schedule/_grid.html" %}{% endwith %}
+{% endblock %}
+
+{% block extra_body %}
+<script src="{{ STATIC_URL }}js/facebox.js" type="text/javascript"></script>
+<script type="text/javascript">
+ jQuery(document).ready(function($) {
+ $.facebox.settings.loadingImage = '{{ STATIC_URL }}img/facebox/loading.gif';
+ $.facebox.settings.closeImage = '{{ STATIC_URL }}img/facebox/closelabel.gif';
+ $.facebox.settings.faceboxHtml = '\
+ <div id="facebox" style="display:none;"> \
+ <div class="popup"> \
+ <table> \
+ <tbody> \
+ <tr> \
+ <td class="tl"/><td class="b"/><td class="tr"/> \
+ </tr> \
+ <tr> \
+ <td class="b"/> \
+ <td class="body"> \
+ <div class="content"> \
+ </div> \
+ </td> \
+ <td class="b"/> \
+ </tr> \
+ <tr> \
+ <td class="bl"/><td class="b"/><td class="br"/> \
+ </tr> \
+ </tbody> \
+ </table> \
+ </div> \
+ </div>'
+ $("a[rel*=facebox]").facebox();
+ });
+</script>
+{% endblock %}
View
2 pycon_project/templates/schedule/list_posters.html
@@ -30,7 +30,7 @@
{% for poster in posters %}
<div class="session">
<div class="title">
- <a href="{% url schedule_session poster.pk %}">{{ poster.title }}</a>
+ <a href="{% url schedule_presentation poster.pk %}">{{ poster.title }}</a>
</div>
<div class="metadata">
{{ poster.get_audience_level_display }}
View
2 pycon_project/templates/schedule/list_talks.html
@@ -32,7 +32,7 @@
{% for talk in talks %}
<div class="session">
<div class="title">
- <a href="{% url schedule_session talk.pk %}">{{ talk.title }}</a>
+ <a href="{% url schedule_presentation talk.pk %}">{{ talk.title }}</a>
</div>
<div class="metadata">
{{ talk.get_audience_level_display }}
View
2 pycon_project/templates/schedule/list_tutorials.html
@@ -26,7 +26,7 @@
{% for tutorial in tutorials %}
<div class="session">
<div class="title">
- <a href="{% url schedule_session tutorial.pk %}">{{ tutorial.title }}</a>
+ <a href="{% url schedule_presentation tutorial.pk %}">{{ tutorial.title }}</a>
</div>
<div class="metadata">
{{ tutorial.get_audience_level_display }}
View
31 pycon_project/templates/schedule/presentation_detail.html
@@ -0,0 +1,31 @@
+{% extends "schedule/base.html" %}
+
+{% load timezone_filters %}
+
+{% block subnav %}
+ <a href="{% url schedule_list_talks %}">List of Accepted Talks and Panels</a>
+ |
+ <a href="{% url schedule_tutorials %}">Tutorial Schedule</a>
+{% endblock %}
+
+{% block body %}
+ <h1>{{ presentation.title }}</h1>
+
+ <div class="presentation_types">{{ presentation.get_audience_level_display }} / {{ presentation.get_presentation_type_display }}</div>
+
+ <div class="speakers">
+ {% for speaker in presentation.speakers %}
+ <a href="{% url speaker_profile speaker.id %}">{{ speaker }}</a>
+ {% if not forloop.last %}, {% endif %}{% endfor %}
+ </div>
+
+ {% if presentation.slot %}
+ <div class="slot">{{ presentation.slot.start|localtime:timezone|date:"F jS" }} {{ presentation.slot.start|localtime:timezone|date:"P" }} &ndash; {{ presentation.slot.end|localtime:timezone|date:"P" }}</div>
+ {% endif %}
+
+ <div class="description">{{ presentation.description }}</div>
+
+ <h3>Abstract</h3>
+
+ <div class="abstract">{{ presentation.abstract_html|safe }}</div>
+{% endblock %}
View
4 pycon_project/templates/schedule/schedule_list.html
@@ -15,11 +15,11 @@
{% for session in slot.sessions %}
{% if session.plenary %}
<td colspan="2" class="title">
- <a href="{% url schedule_session session.id %}">{{ session.title }}</a>
+ <a href="{% url schedule_presentation session.id %}">{{ session.title }}</a>
</td>
{% else %}
<td class="title">
- <a href="{% url schedule_session session.id %}">{{ session.title }}</a>
+ <a href="{% url schedule_presentation session.id %}">{{ session.title }}</a>
</td>
{% endif %}
{% endfor %}
View
76 pycon_project/templates/schedule/session_detail.html
@@ -1,31 +1,69 @@
{% extends "schedule/base.html" %}
-{% load timezone_filters %}
-
-{% block subnav %}
- <a href="{% url schedule_list_talks %}">List of Accepted Talks and Panels</a>
- |
- <a href="{% url schedule_tutorials %}">Tutorial Schedule</a>
-{% endblock %}
-
{% block body %}
- <h1>{{ session.title }}</h1>
- <div class="session_types">{{ session.get_audience_level_display }} / {{ session.get_session_type_display }}</div>
+ <p><a href="{% url schedule_track_detail session.track.pk %}">{{ session.track }}</a> &raquo;</p>
+
+ <h1>{{ session }}</h1>
- <div class="speakers">
- {% for speaker in session.speakers %}
- <a href="{% url speaker_profile speaker.id %}">{{ speaker }}</a>
- {% if not forloop.last %}, {% endif %}{% endfor %}
+ <div>
+ {% if chair %}
+ Chair: {{ chair }}
+ {% if request.user == chair %}
+ <form method="POST">
+ {% csrf_token %}
+ <input type="hidden" name="role" value="un-chair" />
+ <input type="submit" value="opt out" />
+ </form>
+ {% endif %}
+ {% else %}
+ {% if user.is_authenticated %}
+ {% if not chair_denied %}
+ <form method="POST">
+ {% csrf_token %}
+ <input type="hidden" name="role" value="chair" />
+ <input type="submit" value="volunteer" /> to be session chair
+ </form>
+ {% endif %}
+ {% else %}
+ Sign up and <a href="{% url acct_login %}?next={{ request.path }}">log in</a> to volunteer to be session chair.
+ {% endif %}
+ {% endif %}
</div>
- {% if session.slot %}
- <div class="slot">{{ session.slot.start|localtime:timezone|date:"F jS" }} {{ session.slot.start|localtime:timezone|date:"P" }} &ndash; {{ session.slot.end|localtime:timezone|date:"P" }}</div>
+ <div>
+ {% if runner %}
+ Runner: {{ runner }}
+ {% if request.user == runner %}
+ <form method="POST">
+ {% csrf_token %}
+ <input type="hidden" name="role" value="un-runner" />
+ <input type="submit" value="opt out" />
+ </form>
+ {% endif %}
+ {% else %}
+ {% if user.is_authenticated %}
+ {% if not runner_denied %}
+ <form method="POST">
+ {% csrf_token %}
+ <input type="hidden" name="role" value="runner" />
+ <input type="submit" value="volunteer" /> to be session runner
+ </form>
+ {% endif %}
+ {% else %}
+ Sign up and <a href="{% url acct_login %}?next={{ request.path }}">log in</a> to volunteer to be session runner.
+ {% endif %}
{% endif %}
+ </div>
- <div class="description">{{ session.description }}</div>
+ <h2>Slots</h2>
- <h3>Abstract</h3>
+ <ul>
+ {% for slot in session.slots.all %}
+ <li>{{ slot }}: {% if slot.content %}<a href="{% url schedule_presentation slot.content.pk %}">{{ slot.content }}</a>{% endif %}</li>
+ {% empty %}
+ <li>No slots in session.</li>
+ {% endfor %}
+ </ul>
- <div class="abstract">{{ session.abstract_html|safe }}</div>
{% endblock %}
View
14 pycon_project/templates/schedule/session_list.html
@@ -0,0 +1,14 @@
+{% extends "schedule/base.html" %}
+
+{% block body %}
+ <h1>Sessions</h1>
+
+ <ul>
+ {% for session in sessions %}
+ <li><a href="{% url schedule_session_detail session.pk %}">{{ session }}</a></li>
+ {% empty %}
+ <li>No sessions defined.</li>
+ {% endfor %}
+ </ul>
+
+{% endblock %}
View
7 pycon_project/templates/schedule/slot_place.html
@@ -0,0 +1,7 @@
+<h3>Place {{ kind.title }}</h3>
+<form method="POST" action="{% if add %}{% url schedule_slot_add slot.pk kind %}{% else %}{% url schedule_slot_edit slot.pk %}{% endif %}">
+ {% csrf_token %}
+ {{ form.as_p }}
+ <input type="submit" value="{% if add %}Add{% else %}Edit{% endif %}" />
+ <input type="button" value="Cancel" onclick="$.facebox.close();" />
+</form>
View
10 pycon_project/templates/schedule/slot_remove.html
@@ -0,0 +1,10 @@
+<div align="center">
+ <p>Are you sure you want to remove <b>{{ slot.content.title }}</b> from the slot?</p>
+</div>
+<div align="center">
+ <form method="POST" action="{% url schedule_slot_remove slot.pk %}">
+ {% csrf_token %}
+ <input type="submit" value="Yes, I am sure" />
+ <input type="button" value="No, do not delete" onclick="$.facebox.close();" />
+ </form>
+</div>
View
38 pycon_project/templates/schedule/track_detail.html
@@ -0,0 +1,38 @@
+{% extends "schedule/base.html" %}
+
+{% block body %}
+
+ <p><a href="{% url schedule_track_list %}">Tracks</a> &raquo;</p>
+
+ <h1>Track: {{ track }}</h1>
+
+ <h2>Sessions</h2>
+
+ <ul>
+ {% for session in track.sessions.all %}
+ <li>
+ <a href="{% url schedule_session_detail session.pk %}">{{ session }}</a>
+ <ul>
+ {% for slot in session.sorted_slots %}
+ <li>{{ slot }}: {% if slot.content %}<a href="{% url schedule_presentation slot.content.pk %}">{{ slot.content }}</a>{% endif %}</li>
+ {% empty %}
+ <li>No slots in session.</li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% empty %}
+ <li>No sessions in track.</li>
+ {% endfor %}
+ </ul>
+
+ <h2>Slots</h2>
+
+ <ul>
+ {% for slot in track.slots.all %}
+ <li>{{ slot }}: {% if slot.content %}<a href="{% url schedule_presentation slot.content.pk %}">{{ slot.content }}</a>{% endif %}</li>
+ {% empty %}
+ <li>No slots in track.</li>
+ {% endfor %}
+ </ul>
+
+{% endblock %}
View
38 pycon_project/templates/schedule/track_detail_none.html
@@ -0,0 +1,38 @@
+{% extends "schedule/base.html" %}
+
+{% block body %}
+
+ <p><a href="{% url schedule_track_list %}">Tracks</a> &raquo;</p>
+
+ <h1>No Track</h1>
+
+ <h2>Sessions</h2>
+
+ <ul>
+ {% for session in sessions %}
+ <li>
+ <a href="{% url schedule_session_detail session.pk %}">{{ session }}</a>
+ <ul>
+ {% for slot in session.sorted_slots %}
+ <li>{{ slot }}: {% if slot.content %}<a href="{% url schedule_presentation slot.content.pk %}">{{ slot.content }}</a>{% endif %}</li>
+ {% empty %}
+ <li>No slots in session.</li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% empty %}
+ <li>No sessions without track.</li>
+ {% endfor %}
+ </ul>
+
+ <h2>Slots</h2>
+
+ <ul>
+ {% for slot in slots %}
+ <li>{{ slot }}: {% if slot.content %}<a href="{% url schedule_presentation slot.content.pk %}">{{ slot.content }}</a>{% endif %}</li>
+ {% empty %}
+ <li>No slots without track.</li>
+ {% endfor %}
+ </ul>
+
+{% endblock %}
View
15 pycon_project/templates/schedule/track_list.html
@@ -0,0 +1,15 @@
+{% extends "schedule/base.html" %}
+
+{% block body %}
+ <h1>Tracks</h1>
+
+ <ul>
+ <li><a href="{% url schedule_notrack_detail %}">No track</a></li>
+ {% for track in tracks %}
+ <li><a href="{% url schedule_track_detail track.pk %}">{{ track }}</a></li>
+ {% empty %}
+ <li>No tracks defined.</li>
+ {% endfor %}
+ </ul>
+
+{% endblock %}
View
26 pycon_project/templates/schedule/tutorials.html
@@ -2,7 +2,7 @@
{% block extra_head %}
<style>
- .session {
+ .presentation {
margin-top: 10px;
}
.title {
@@ -23,30 +23,30 @@
<h2>Wednesday, March 9th</h2>
- <h3>Morning ({{ tutorials.wed.morning.slot.start|time }} &ndash; {{ tutorials.wed.morning.slot.end|time }})</h3>
+ <h3>Morning ({{ tutorials.wed.morning.slot.0|time }} &ndash; {{ tutorials.wed.morning.slot.1|time }})</h3>
- {% for session in tutorials.wed.morning.sessions %}
- {% include "schedule/_session.html" %}
+ {% for presentation in tutorials.wed.morning.presentations %}
+ {% include "schedule/_presentation.html" %}
{% endfor %}
- <h3>Afternoon ({{ tutorials.wed.afternoon.slot.start|time }} &ndash; {{ tutorials.wed.afternoon.slot.end|time }})</h3>
+ <h3>Afternoon ({{ tutorials.wed.afternoon.slot.0|time }} &ndash; {{ tutorials.wed.afternoon.slot.1|time }})</h3>
- {% for session in tutorials.wed.afternoon.sessions %}
- {% include "schedule/_session.html" %}
+ {% for presentation in tutorials.wed.afternoon.presentations %}
+ {% include "schedule/_presentation.html" %}
{% endfor %}
<h2>Thursday, March 10th</h2>
- <h3>Morning ({{ tutorials.thurs.morning.slot.start|time }} &ndash; {{ tutorials.thurs.morning.slot.end|time }})</h3>
+ <h3>Morning ({{ tutorials.thurs.morning.slot.0|time }} &ndash; {{ tutorials.thurs.morning.slot.1|time }})</h3>
- {% for session in tutorials.thurs.morning.sessions %}
- {% include "schedule/_session.html" %}
+ {% for presentation in tutorials.thurs.morning.presentations %}
+ {% include "schedule/_presentation.html" %}
{% endfor %}
- <h3>Afternoon ({{ tutorials.thurs.afternoon.slot.start|time }} &ndash; {{ tutorials.thurs.afternoon.slot.end|time }})</h3>
+ <h3>Afternoon ({{ tutorials.thurs.afternoon.slot.0|time }} &ndash; {{ tutorials.thurs.afternoon.slot.1|time }})</h3>
- {% for session in tutorials.thurs.afternoon.sessions %}
- {% include "schedule/_session.html" %}
+ {% for presentation in tutorials.thurs.afternoon.presentations %}
+ {% include "schedule/_presentation.html" %}
{% endfor %}
View
2 pycon_project/templates/speakers/speaker_profile.html
@@ -21,7 +21,7 @@
<dl class="sessions">
{% for session in sessions %}
<dt>{{ session.slot.start|localtime:timezone|date:"F jS" }} {{ session.slot.start|localtime:timezone|date:"P" }} &ndash; {{ session.slot.end|localtime:timezone|date:"P" }}</dt>
- <dd><a href="{% url schedule_session session.id %}">{{ session.title }}</a></dd>
+ <dd><a href="{% url schedule_presentation session.id %}">{{ session.title }}</a></dd>
{% endfor %}
</dl>
{% endif %}

0 comments on commit 612a9c6

Please sign in to comment.