commit
bfd2925c60
19 changed files with 152 additions and 206 deletions
|
@ -1,7 +1,4 @@
|
|||
import datetime
|
||||
|
||||
from django.db import models
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
import reversion
|
||||
|
@ -10,16 +7,16 @@ from markitup.fields import MarkupField
|
|||
|
||||
|
||||
class Box(models.Model):
|
||||
|
||||
|
||||
label = models.CharField(max_length=100, db_index=True)
|
||||
content = MarkupField(blank=True)
|
||||
|
||||
|
||||
created_by = models.ForeignKey(User, related_name="boxes")
|
||||
last_updated_by = models.ForeignKey(User, related_name="updated_boxes")
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return self.label
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "boxes"
|
||||
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
from django import template
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.template.defaulttags import kwarg_re
|
||||
|
||||
from symposion.boxes.models import Box
|
||||
from symposion.boxes.forms import BoxForm
|
||||
|
@ -16,22 +11,22 @@ register = template.Library()
|
|||
|
||||
@register.inclusion_tag("boxes/box.html", takes_context=True)
|
||||
def box(context, label, show_edit=True, *args, **kwargs):
|
||||
|
||||
|
||||
request = context["request"]
|
||||
can_edit = load_can_edit()(request, *args, **kwargs)
|
||||
|
||||
|
||||
try:
|
||||
box = Box.objects.get(label=label)
|
||||
except Box.DoesNotExist:
|
||||
box = None
|
||||
|
||||
|
||||
if can_edit and show_edit:
|
||||
form = BoxForm(instance=box, prefix=label)
|
||||
form_action = reverse("box_edit", args=[label])
|
||||
else:
|
||||
form = None
|
||||
form_action = None
|
||||
|
||||
|
||||
return {
|
||||
"request": request,
|
||||
"label": label,
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
from django.conf import settings
|
||||
|
||||
from appconf import AppConf
|
||||
|
||||
|
||||
class SymposionAppConf(AppConf):
|
||||
|
||||
|
||||
VOTE_THRESHOLD = 3
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns("symposion.conference.views",
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import html5lib
|
||||
from html5lib import html5parser, sanitizer
|
||||
|
||||
import markdown
|
||||
|
||||
|
||||
def parse(text):
|
||||
|
||||
|
||||
# First run through the Markdown parser
|
||||
text = markdown.markdown(text, extensions=["extra"], safe_mode=False)
|
||||
|
||||
|
||||
# Sanitize using html5lib
|
||||
bits = []
|
||||
parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer)
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
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)
|
|
@ -1,4 +1,4 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns("symposion.proposals.views",
|
||||
|
@ -11,7 +11,7 @@ urlpatterns = patterns("symposion.proposals.views",
|
|||
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"),
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from symposion.proposals.models import ProposalSection
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
import csv
|
||||
import os
|
||||
import random
|
||||
|
||||
from django.contrib.auth import models
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from symposion.reviews.models import ReviewAssignment
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from symposion.reviews.models import ProposalResult
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
|
||||
def handle(self, *args, **options):
|
||||
ProposalResult.full_calculate()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django import template
|
||||
|
||||
from symposion.reviews.models import Review, ReviewAssignment
|
||||
from symposion.reviews.models import ReviewAssignment
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import re
|
||||
|
||||
from django.core.mail import send_mass_mail
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponseBadRequest, HttpResponseNotAllowed
|
||||
|
@ -27,18 +25,18 @@ def access_not_permitted(request):
|
|||
|
||||
|
||||
def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
|
||||
|
||||
|
||||
for obj in queryset:
|
||||
# @@@ this sucks; we can do better
|
||||
if check_speaker:
|
||||
if request.user in [s.user for s in obj.speakers()]:
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
obj.result
|
||||
except ProposalResult.DoesNotExist:
|
||||
ProposalResult.objects.get_or_create(proposal=obj)
|
||||
|
||||
|
||||
obj.comment_count = obj.result.comment_count
|
||||
obj.total_votes = obj.result.vote_count
|
||||
obj.plus_one = obj.result.plus_one
|
||||
|
@ -46,19 +44,19 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
|
|||
obj.minus_zero = obj.result.minus_zero
|
||||
obj.minus_one = obj.result.minus_one
|
||||
lookup_params = dict(proposal=obj)
|
||||
|
||||
|
||||
if user_pk:
|
||||
lookup_params["user__pk"] = user_pk
|
||||
else:
|
||||
lookup_params["user"] = request.user
|
||||
|
||||
|
||||
try:
|
||||
obj.user_vote = LatestVote.objects.get(**lookup_params).vote
|
||||
obj.user_vote_css = LatestVote.objects.get(**lookup_params).css_class()
|
||||
except LatestVote.DoesNotExist:
|
||||
obj.user_vote = None
|
||||
obj.user_vote_css = "no-vote"
|
||||
|
||||
|
||||
yield obj
|
||||
|
||||
|
||||
|
@ -66,17 +64,17 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
|
|||
# depending on the link user clicks in dashboard
|
||||
@login_required
|
||||
def review_section(request, section_slug, assigned=False, reviewed="all"):
|
||||
|
||||
|
||||
if not request.user.has_perm("reviews.can_review_%s" % section_slug):
|
||||
return access_not_permitted(request)
|
||||
|
||||
|
||||
section = get_object_or_404(ProposalSection, section__slug=section_slug)
|
||||
queryset = ProposalBase.objects.filter(kind__section=section)
|
||||
|
||||
|
||||
if assigned:
|
||||
assignments = ReviewAssignment.objects.filter(user=request.user).values_list("proposal__id")
|
||||
queryset = queryset.filter(id__in=assignments)
|
||||
|
||||
|
||||
# passing reviewed in from reviews.urls and out to review_list for
|
||||
# appropriate template header rendering
|
||||
if reviewed == "all":
|
||||
|
@ -88,35 +86,35 @@ def review_section(request, section_slug, assigned=False, reviewed="all"):
|
|||
else:
|
||||
queryset = queryset.exclude(reviews__user=request.user).exclude(speaker=request.user)
|
||||
reviewed = "user_not_reviewed"
|
||||
|
||||
|
||||
proposals = proposals_generator(request, queryset)
|
||||
|
||||
|
||||
ctx = {
|
||||
"proposals": proposals,
|
||||
"section": section,
|
||||
"reviewed": reviewed,
|
||||
}
|
||||
|
||||
|
||||
return render(request, "reviews/review_list.html", ctx)
|
||||
|
||||
@login_required
|
||||
def review_list(request, section_slug, user_pk):
|
||||
|
||||
|
||||
# if they're not a reviewer admin and they aren't the person whose
|
||||
# review list is being asked for, don't let them in
|
||||
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
|
||||
if not request.user.pk == user_pk:
|
||||
return access_not_permitted(request)
|
||||
|
||||
|
||||
queryset = ProposalBase.objects.select_related("speaker__user", "result")
|
||||
reviewed = LatestVote.objects.filter(user__pk=user_pk).values_list("proposal", flat=True)
|
||||
queryset = queryset.filter(pk__in=reviewed)
|
||||
proposals = queryset.order_by("submitted")
|
||||
|
||||
|
||||
admin = request.user.has_perm("reviews.can_manage_%s" % section_slug)
|
||||
|
||||
|
||||
proposals = proposals_generator(request, proposals, user_pk=user_pk, check_speaker=not admin)
|
||||
|
||||
|
||||
ctx = {
|
||||
"proposals": proposals,
|
||||
}
|
||||
|
@ -125,20 +123,20 @@ def review_list(request, section_slug, user_pk):
|
|||
|
||||
@login_required
|
||||
def review_admin(request, section_slug):
|
||||
|
||||
|
||||
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
|
||||
return access_not_permitted(request)
|
||||
|
||||
|
||||
def reviewers():
|
||||
already_seen = set()
|
||||
|
||||
|
||||
for team in Team.objects.filter(permissions__codename="can_review_%s" % section_slug):
|
||||
for membership in team.memberships.filter(Q(state="member") | Q(state="manager")):
|
||||
user = membership.user
|
||||
if user.pk in already_seen:
|
||||
continue
|
||||
already_seen.add(user.pk)
|
||||
|
||||
|
||||
user.comment_count = Review.objects.filter(user=user).count()
|
||||
user.total_votes = LatestVote.objects.filter(user=user).count()
|
||||
user.plus_one = LatestVote.objects.filter(
|
||||
|
@ -157,9 +155,9 @@ def review_admin(request, section_slug):
|
|||
user = user,
|
||||
vote = LatestVote.VOTES.MINUS_ONE
|
||||
).count()
|
||||
|
||||
|
||||
yield user
|
||||
|
||||
|
||||
ctx = {
|
||||
"section_slug": section_slug,
|
||||
"reviewers": reviewers(),
|
||||
|
@ -169,50 +167,50 @@ def review_admin(request, section_slug):
|
|||
|
||||
@login_required
|
||||
def review_detail(request, pk):
|
||||
|
||||
|
||||
proposals = ProposalBase.objects.select_related("result").select_subclasses()
|
||||
proposal = get_object_or_404(proposals, pk=pk)
|
||||
|
||||
|
||||
if not request.user.has_perm("reviews.can_review_%s" % proposal.kind.section.slug):
|
||||
return access_not_permitted(request)
|
||||
|
||||
|
||||
speakers = [s.user for s in proposal.speakers()]
|
||||
|
||||
|
||||
if not request.user.is_superuser and request.user in speakers:
|
||||
return access_not_permitted(request)
|
||||
|
||||
|
||||
admin = request.user.is_staff
|
||||
|
||||
|
||||
try:
|
||||
latest_vote = LatestVote.objects.get(proposal=proposal, user=request.user)
|
||||
except LatestVote.DoesNotExist:
|
||||
latest_vote = None
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
if request.user in speakers:
|
||||
return access_not_permitted(request)
|
||||
|
||||
|
||||
if "vote_submit" in request.POST:
|
||||
review_form = ReviewForm(request.POST)
|
||||
if review_form.is_valid():
|
||||
|
||||
|
||||
review = review_form.save(commit=False)
|
||||
review.user = request.user
|
||||
review.proposal = proposal
|
||||
review.save()
|
||||
|
||||
|
||||
return redirect(request.path)
|
||||
else:
|
||||
message_form = SpeakerCommentForm()
|
||||
elif "message_submit" in request.POST:
|
||||
message_form = SpeakerCommentForm(request.POST)
|
||||
if message_form.is_valid():
|
||||
|
||||
|
||||
message = message_form.save(commit=False)
|
||||
message.user = request.user
|
||||
message.proposal = proposal
|
||||
message.save()
|
||||
|
||||
|
||||
for speaker in speakers:
|
||||
if speaker and speaker.email:
|
||||
ctx = {
|
||||
|
@ -224,7 +222,7 @@ def review_detail(request, pk):
|
|||
[speaker.email], "proposal_new_message",
|
||||
context = ctx
|
||||
)
|
||||
|
||||
|
||||
return redirect(request.path)
|
||||
else:
|
||||
initial = {}
|
||||
|
@ -237,7 +235,7 @@ def review_detail(request, pk):
|
|||
elif "result_submit" in request.POST:
|
||||
if admin:
|
||||
result = request.POST["result_submit"]
|
||||
|
||||
|
||||
if result == "accept":
|
||||
proposal.result.status = "accepted"
|
||||
proposal.result.save()
|
||||
|
@ -250,7 +248,7 @@ def review_detail(request, pk):
|
|||
elif result == "standby":
|
||||
proposal.result.status = "standby"
|
||||
proposal.result.save()
|
||||
|
||||
|
||||
return redirect(request.path)
|
||||
else:
|
||||
initial = {}
|
||||
|
@ -261,17 +259,17 @@ def review_detail(request, pk):
|
|||
else:
|
||||
review_form = ReviewForm(initial=initial)
|
||||
message_form = SpeakerCommentForm()
|
||||
|
||||
|
||||
proposal.comment_count = proposal.result.comment_count
|
||||
proposal.total_votes = proposal.result.vote_count
|
||||
proposal.plus_one = proposal.result.plus_one
|
||||
proposal.plus_zero = proposal.result.plus_zero
|
||||
proposal.minus_zero = proposal.result.minus_zero
|
||||
proposal.minus_one = proposal.result.minus_one
|
||||
|
||||
|
||||
reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at")
|
||||
messages = proposal.messages.order_by("submitted_at")
|
||||
|
||||
|
||||
return render(request, "reviews/review_detail.html", {
|
||||
"proposal": proposal,
|
||||
"latest_vote": latest_vote,
|
||||
|
@ -287,33 +285,33 @@ def review_detail(request, pk):
|
|||
def review_delete(request, pk):
|
||||
review = get_object_or_404(Review, pk=pk)
|
||||
section_slug = review.section.slug
|
||||
|
||||
|
||||
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
|
||||
return access_not_permitted(request)
|
||||
|
||||
|
||||
review = get_object_or_404(Review, pk=pk)
|
||||
review.delete()
|
||||
|
||||
|
||||
return redirect("review_detail", pk=review.proposal.pk)
|
||||
|
||||
|
||||
@login_required
|
||||
def review_status(request, section_slug=None, key=None):
|
||||
|
||||
|
||||
if not request.user.has_perm("reviews.can_review_%s" % section_slug):
|
||||
return access_not_permitted(request)
|
||||
|
||||
|
||||
VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD
|
||||
|
||||
|
||||
ctx = {
|
||||
"section_slug": section_slug,
|
||||
"vote_threshold": VOTE_THRESHOLD,
|
||||
}
|
||||
|
||||
|
||||
queryset = ProposalBase.objects.select_related("speaker__user", "result").select_subclasses()
|
||||
if section_slug:
|
||||
queryset = queryset.filter(kind__section__slug=section_slug)
|
||||
|
||||
|
||||
proposals = {
|
||||
# proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by the 'score'
|
||||
"positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one=0).order_by("-result__score"),
|
||||
|
@ -326,14 +324,14 @@ def review_status(request, section_slug=None, key=None):
|
|||
# proposals with fewer than VOTE_THRESHOLD reviews
|
||||
"too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD).order_by("result__vote_count"),
|
||||
}
|
||||
|
||||
|
||||
admin = request.user.has_perm("reviews.can_manage_%s" % section_slug)
|
||||
|
||||
|
||||
for status in proposals:
|
||||
if key and key != status:
|
||||
continue
|
||||
proposals[status] = list(proposals_generator(request, proposals[status], check_speaker=not admin))
|
||||
|
||||
|
||||
if key:
|
||||
ctx.update({
|
||||
"key": key,
|
||||
|
@ -341,7 +339,7 @@ def review_status(request, section_slug=None, key=None):
|
|||
})
|
||||
else:
|
||||
ctx["proposals"] = proposals
|
||||
|
||||
|
||||
return render(request, "reviews/review_stats.html", ctx)
|
||||
|
||||
|
||||
|
@ -387,7 +385,7 @@ def review_bulk_accept(request, section_slug):
|
|||
return redirect("review_section", section_slug=section_slug)
|
||||
else:
|
||||
form = BulkPresentationForm()
|
||||
|
||||
|
||||
return render(request, "reviews/review_bulk_accept.html", {
|
||||
"form": form,
|
||||
})
|
||||
|
@ -397,10 +395,10 @@ def review_bulk_accept(request, section_slug):
|
|||
def result_notification(request, section_slug, status):
|
||||
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
|
||||
return access_not_permitted(request)
|
||||
|
||||
|
||||
proposals = ProposalBase.objects.filter(kind__section__slug=section_slug, result__status=status).select_related("speaker__user", "result").select_subclasses()
|
||||
notification_templates = NotificationTemplate.objects.all()
|
||||
|
||||
|
||||
ctx = {
|
||||
"section_slug": section_slug,
|
||||
"status": status,
|
||||
|
@ -414,10 +412,10 @@ def result_notification(request, section_slug, status):
|
|||
def result_notification_prepare(request, section_slug, status):
|
||||
if request.method != "POST":
|
||||
return HttpResponseNotAllowed(["POST"])
|
||||
|
||||
|
||||
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
|
||||
return access_not_permitted(request)
|
||||
|
||||
|
||||
proposal_pks = []
|
||||
try:
|
||||
for pk in request.POST.getlist("_selected_action"):
|
||||
|
@ -431,13 +429,13 @@ def result_notification_prepare(request, section_slug, status):
|
|||
proposals = proposals.filter(pk__in=proposal_pks)
|
||||
proposals = proposals.select_related("speaker__user", "result")
|
||||
proposals = proposals.select_subclasses()
|
||||
|
||||
|
||||
notification_template_pk = request.POST.get("notification_template", "")
|
||||
if notification_template_pk:
|
||||
notification_template = NotificationTemplate.objects.get(pk=notification_template_pk)
|
||||
else:
|
||||
notification_template = None
|
||||
|
||||
|
||||
ctx = {
|
||||
"section_slug": section_slug,
|
||||
"status": status,
|
||||
|
@ -452,18 +450,18 @@ def result_notification_prepare(request, section_slug, status):
|
|||
def result_notification_send(request, section_slug, status):
|
||||
if request.method != "POST":
|
||||
return HttpResponseNotAllowed(["POST"])
|
||||
|
||||
|
||||
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
|
||||
return access_not_permitted(request)
|
||||
|
||||
|
||||
if not all([k in request.POST for k in ["proposal_pks", "from_address", "subject", "body"]]):
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
try:
|
||||
proposal_pks = [int(pk) for pk in request.POST["proposal_pks"].split(",")]
|
||||
except ValueError:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
proposals = ProposalBase.objects.filter(
|
||||
kind__section__slug=section_slug,
|
||||
result__status=status,
|
||||
|
@ -471,15 +469,15 @@ def result_notification_send(request, section_slug, status):
|
|||
proposals = proposals.filter(pk__in=proposal_pks)
|
||||
proposals = proposals.select_related("speaker__user", "result")
|
||||
proposals = proposals.select_subclasses()
|
||||
|
||||
|
||||
notification_template_pk = request.POST.get("notification_template", "")
|
||||
if notification_template_pk:
|
||||
notification_template = NotificationTemplate.objects.get(pk=notification_template_pk)
|
||||
else:
|
||||
notification_template = None
|
||||
|
||||
|
||||
emails = []
|
||||
|
||||
|
||||
for proposal in proposals:
|
||||
rn = ResultNotification()
|
||||
rn.proposal = proposal
|
||||
|
@ -494,7 +492,7 @@ def result_notification_send(request, section_slug, status):
|
|||
)
|
||||
rn.save()
|
||||
emails.append(rn.email_args)
|
||||
|
||||
|
||||
send_mass_mail(emails)
|
||||
|
||||
|
||||
return redirect("result_notification", section_slug=section_slug, status=status)
|
||||
|
|
|
@ -2,44 +2,43 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||
from django.db import models
|
||||
|
||||
from markitup.fields import MarkupField
|
||||
from model_utils.managers import InheritanceManager
|
||||
|
||||
from symposion.proposals.models import ProposalBase
|
||||
from symposion.conference.models import Section
|
||||
|
||||
|
||||
class Schedule(models.Model):
|
||||
|
||||
|
||||
section = models.OneToOneField(Section)
|
||||
published = models.BooleanField(default=True)
|
||||
hidden = models.BooleanField("Hide schedule from overall conference view", default=False)
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s Schedule" % self.section
|
||||
|
||||
|
||||
class Meta:
|
||||
ordering = ["section"]
|
||||
|
||||
|
||||
class Day(models.Model):
|
||||
|
||||
|
||||
schedule = models.ForeignKey(Schedule)
|
||||
date = models.DateField()
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s" % self.date
|
||||
|
||||
|
||||
class Meta:
|
||||
unique_together = [("schedule", "date")]
|
||||
ordering = ["date"]
|
||||
|
||||
|
||||
class Room(models.Model):
|
||||
|
||||
|
||||
schedule = models.ForeignKey(Schedule)
|
||||
name = models.CharField(max_length=65)
|
||||
order = models.PositiveIntegerField()
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
|
@ -49,22 +48,22 @@ class SlotKind(models.Model):
|
|||
A slot kind represents what kind a slot is. For example, a slot can be a
|
||||
break, lunch, or X-minute talk.
|
||||
"""
|
||||
|
||||
|
||||
schedule = models.ForeignKey(Schedule)
|
||||
label = models.CharField(max_length=50)
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return self.label
|
||||
|
||||
|
||||
class Slot(models.Model):
|
||||
|
||||
|
||||
day = models.ForeignKey(Day)
|
||||
kind = models.ForeignKey(SlotKind)
|
||||
start = models.TimeField()
|
||||
end = models.TimeField()
|
||||
content_override = MarkupField(blank=True)
|
||||
|
||||
|
||||
def assign(self, content):
|
||||
"""
|
||||
Assign the given content to this slot and if a previous slot content
|
||||
|
@ -73,7 +72,7 @@ class Slot(models.Model):
|
|||
self.unassign()
|
||||
content.slot = self
|
||||
content.save()
|
||||
|
||||
|
||||
def unassign(self):
|
||||
"""
|
||||
Unassign the associated content with this slot.
|
||||
|
@ -81,7 +80,7 @@ class Slot(models.Model):
|
|||
if self.content and self.content.slot_id:
|
||||
self.content.slot = None
|
||||
self.content.save()
|
||||
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
"""
|
||||
|
@ -92,14 +91,14 @@ class Slot(models.Model):
|
|||
return self.content_ptr
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
@property
|
||||
def rooms(self):
|
||||
return Room.objects.filter(pk__in=self.slotroom_set.values("room"))
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s %s (%s - %s)" % (self.day, self.kind, self.start, self.end)
|
||||
|
||||
|
||||
class Meta:
|
||||
ordering = ["day", "start", "end"]
|
||||
|
||||
|
@ -108,20 +107,20 @@ class SlotRoom(models.Model):
|
|||
"""
|
||||
Links a slot with a room.
|
||||
"""
|
||||
|
||||
|
||||
slot = models.ForeignKey(Slot)
|
||||
room = models.ForeignKey(Room)
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s %s" % (self.room, self.slot)
|
||||
|
||||
|
||||
class Meta:
|
||||
unique_together = [("slot", "room")]
|
||||
ordering = ["slot", "room__order"]
|
||||
|
||||
|
||||
class Presentation(models.Model):
|
||||
|
||||
|
||||
slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr")
|
||||
title = models.CharField(max_length=100)
|
||||
description = MarkupField()
|
||||
|
@ -131,25 +130,25 @@ class Presentation(models.Model):
|
|||
cancelled = models.BooleanField(default=False)
|
||||
proposal_base = models.OneToOneField(ProposalBase, related_name="presentation")
|
||||
section = models.ForeignKey(Section, related_name="presentations")
|
||||
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
return self.proposal.number
|
||||
|
||||
|
||||
@property
|
||||
def proposal(self):
|
||||
if self.proposal_base_id is None:
|
||||
return None
|
||||
return ProposalBase.objects.get_subclass(pk=self.proposal_base_id)
|
||||
|
||||
|
||||
def speakers(self):
|
||||
yield self.speaker
|
||||
for speaker in self.additional_speakers.all():
|
||||
if speaker.user:
|
||||
yield speaker
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return "#%s %s (%s)" % (self.number, self.title, self.speaker)
|
||||
|
||||
|
||||
class Meta:
|
||||
ordering = ["slot"]
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import itertools
|
||||
import operator
|
||||
|
||||
from django.db.models import Count, Min
|
||||
|
||||
|
@ -7,22 +6,22 @@ from symposion.schedule.models import Room, Slot, SlotRoom
|
|||
|
||||
|
||||
class TimeTable(object):
|
||||
|
||||
|
||||
def __init__(self, day):
|
||||
self.day = day
|
||||
|
||||
|
||||
def slots_qs(self):
|
||||
qs = Slot.objects.all()
|
||||
qs = qs.filter(day=self.day)
|
||||
return qs
|
||||
|
||||
|
||||
def rooms(self):
|
||||
qs = Room.objects.all()
|
||||
qs = qs.filter(schedule=self.day.schedule)
|
||||
qs = qs.filter(pk__in=SlotRoom.objects.filter(slot__in=self.slots_qs().values("pk")).values("room"))
|
||||
qs = qs.order_by("order")
|
||||
return qs
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
times = sorted(set(itertools.chain(*self.slots_qs().values_list("start", "end"))))
|
||||
slots = Slot.objects.filter(pk__in=self.slots_qs().values("pk"))
|
||||
|
@ -38,7 +37,7 @@ class TimeTable(object):
|
|||
row["slots"].append(slot)
|
||||
if row["slots"] or next_time is None:
|
||||
yield row
|
||||
|
||||
|
||||
@staticmethod
|
||||
def rowspan(times, start, end):
|
||||
return times.index(end) - times.index(start)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.template import loader, Context
|
||||
|
@ -12,7 +11,7 @@ from symposion.schedule.timetable import TimeTable
|
|||
|
||||
def fetch_schedule(slug):
|
||||
qs = Schedule.objects.all()
|
||||
|
||||
|
||||
if slug is None:
|
||||
if qs.count() > 1:
|
||||
raise Http404()
|
||||
|
@ -21,14 +20,14 @@ def fetch_schedule(slug):
|
|||
raise Http404()
|
||||
else:
|
||||
schedule = get_object_or_404(qs, section__slug=slug)
|
||||
|
||||
|
||||
return schedule
|
||||
|
||||
|
||||
def schedule_conference(request):
|
||||
|
||||
|
||||
schedules = Schedule.objects.filter(published=True, hidden=False)
|
||||
|
||||
|
||||
sections = []
|
||||
for schedule in schedules:
|
||||
days_qs = Day.objects.filter(schedule=schedule)
|
||||
|
@ -37,7 +36,7 @@ def schedule_conference(request):
|
|||
"schedule": schedule,
|
||||
"days": days,
|
||||
})
|
||||
|
||||
|
||||
ctx = {
|
||||
"sections": sections,
|
||||
}
|
||||
|
@ -45,14 +44,14 @@ def schedule_conference(request):
|
|||
|
||||
|
||||
def schedule_detail(request, slug=None):
|
||||
|
||||
|
||||
schedule = fetch_schedule(slug)
|
||||
if not schedule.published and not request.user.is_staff:
|
||||
raise Http404()
|
||||
|
||||
|
||||
days_qs = Day.objects.filter(schedule=schedule)
|
||||
days = [TimeTable(day) for day in days_qs]
|
||||
|
||||
|
||||
ctx = {
|
||||
"schedule": schedule,
|
||||
"days": days,
|
||||
|
@ -62,10 +61,10 @@ def schedule_detail(request, slug=None):
|
|||
|
||||
def schedule_list(request, slug=None):
|
||||
schedule = fetch_schedule(slug)
|
||||
|
||||
|
||||
presentations = Presentation.objects.filter(section=schedule.section)
|
||||
presentations = presentations.exclude(cancelled=True)
|
||||
|
||||
|
||||
ctx = {
|
||||
"schedule": schedule,
|
||||
"presentations": presentations,
|
||||
|
@ -75,32 +74,32 @@ def schedule_list(request, slug=None):
|
|||
|
||||
def schedule_list_csv(request, slug=None):
|
||||
schedule = fetch_schedule(slug)
|
||||
|
||||
|
||||
presentations = Presentation.objects.filter(section=schedule.section)
|
||||
presentations = presentations.exclude(cancelled=True).order_by("id")
|
||||
|
||||
|
||||
response = HttpResponse(mimetype="text/csv")
|
||||
if slug:
|
||||
file_slug = slug
|
||||
else:
|
||||
file_slug = "presentations"
|
||||
response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug
|
||||
|
||||
|
||||
response.write(loader.get_template("schedule/schedule_list.csv").render(Context({
|
||||
"presentations": presentations,
|
||||
|
||||
|
||||
})))
|
||||
return response
|
||||
|
||||
|
||||
@login_required
|
||||
def schedule_edit(request, slug=None):
|
||||
|
||||
|
||||
if not request.user.is_staff:
|
||||
raise Http404()
|
||||
|
||||
|
||||
schedule = fetch_schedule(slug)
|
||||
|
||||
|
||||
days_qs = Day.objects.filter(schedule=schedule)
|
||||
days = [TimeTable(day) for day in days_qs]
|
||||
ctx = {
|
||||
|
@ -112,12 +111,12 @@ def schedule_edit(request, slug=None):
|
|||
|
||||
@login_required
|
||||
def schedule_slot_edit(request, slug, slot_pk):
|
||||
|
||||
|
||||
if not request.user.is_staff:
|
||||
raise Http404()
|
||||
|
||||
|
||||
slot = get_object_or_404(Slot, day__schedule__section__slug=slug, pk=slot_pk)
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
form = SlotEditForm(request.POST, slot=slot)
|
||||
if form.is_valid():
|
||||
|
@ -145,13 +144,13 @@ def schedule_slot_edit(request, slug, slot_pk):
|
|||
|
||||
|
||||
def schedule_presentation_detail(request, pk):
|
||||
|
||||
|
||||
presentation = get_object_or_404(Presentation, pk=pk)
|
||||
if presentation.slot:
|
||||
schedule = presentation.slot.day.schedule
|
||||
else:
|
||||
schedule = None
|
||||
|
||||
|
||||
ctx = {
|
||||
"presentation": presentation,
|
||||
"schedule": schedule,
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import csv
|
||||
import os
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
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"),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns("symposion.speakers.views",
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from symposion.sponsorship.models import Sponsor, SponsorBenefit
|
||||
from symposion.sponsorship.models import Sponsor, SponsorBenefit, SponsorLevel
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for sponsor in Sponsor.objects.all():
|
||||
level = None
|
||||
level = None
|
||||
try:
|
||||
level = sponsor.level
|
||||
except SponsorLevel.DoesNotExist:
|
||||
|
@ -19,17 +17,17 @@ class Command(BaseCommand):
|
|||
# Create all needed benefits if they don't exist already
|
||||
sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
|
||||
sponsor=sponsor, benefit=benefit_level.benefit)
|
||||
|
||||
|
||||
if created:
|
||||
print "created", sponsor_benefit, "for", sponsor
|
||||
|
||||
|
||||
# and set to default limits for this level.
|
||||
sponsor_benefit.max_words = benefit_level.max_words
|
||||
sponsor_benefit.other_limits = benefit_level.other_limits
|
||||
|
||||
|
||||
# and set to active
|
||||
sponsor_benefit.active = True
|
||||
|
||||
|
||||
# @@@ We don't call sponsor_benefit.clean here. This means
|
||||
# that if the sponsorship level for a sponsor is adjusted
|
||||
# downwards, an existing too-long text entry can remain,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
|
||||
urlpatterns = patterns("symposion.teams.views",
|
||||
|
@ -7,7 +7,7 @@ urlpatterns = patterns("symposion.teams.views",
|
|||
url(r"^(?P<slug>[\w\-]+)/join/$", "team_join", name="team_join"),
|
||||
url(r"^(?P<slug>[\w\-]+)/leave/$", "team_leave", name="team_leave"),
|
||||
url(r"^(?P<slug>[\w\-]+)/apply/$", "team_apply", name="team_apply"),
|
||||
|
||||
|
||||
# membership specific
|
||||
url(r"^promote/(?P<pk>\d+)/$", "team_promote", name="team_promote"),
|
||||
url(r"^demote/(?P<pk>\d+)/$", "team_demote", name="team_demote"),
|
||||
|
|
Loading…
Reference in a new issue