Changeset - ffd428d49720
symposion/boxes/models.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.db import models
 
from django.contrib.auth.models import User
 
from django.utils.encoding import python_2_unicode_compatible
 

	
 
import reversion
 

	
 
from markitup.fields import MarkupField
 

	
 

	
 
@python_2_unicode_compatible
 
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):
 
    def __str__(self):
 
        return self.label
 

	
 
    class Meta:
 
        verbose_name_plural = "boxes"
 

	
 

	
 
reversion.register(Box)
symposion/cms/models.py
Show inline comments
 
import datetime
 
import os
 
import re
 

	
 
from django.conf import settings
 
from django.core.urlresolvers import reverse
 
from django.core.exceptions import ValidationError
 
from django.db import models
 
from django.utils.encoding import python_2_unicode_compatible
 
from django.utils.translation import ugettext_lazy as _
 

	
 
from markitup.fields import MarkupField
 

	
 
from taggit.managers import TaggableManager
 

	
 
import reversion
 

	
 
from .managers import PublishedPageManager
 

	
 

	
 
@python_2_unicode_compatible
 
class Page(models.Model):
 

	
 
    STATUS_CHOICES = (
 
        (1, _("Draft")),
 
        (2, _("Public")),
 
    )
 

	
 
    title = models.CharField(max_length=100)
 
    path = models.CharField(max_length=100, unique=True)
 
    body = MarkupField()
 
    status = models.IntegerField(choices=STATUS_CHOICES, default=2)
 
    publish_date = models.DateTimeField(default=datetime.datetime.now)
 
    created = models.DateTimeField(editable=False, default=datetime.datetime.now)
 
    updated = models.DateTimeField(editable=False, default=datetime.datetime.now)
 
    tags = TaggableManager(blank=True)
 

	
 
    published = PublishedPageManager()
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        return self.title
 

	
 
    @models.permalink
 
    def get_absolute_url(self):
 
        return ("cms_page", [self.path])
 

	
 
    @property
 
    def is_community(self):
 
        return self.path.lower().startswith("community/")
 

	
 
    def save(self, *args, **kwargs):
 
        self.updated = datetime.datetime.now()
 
        super(Page, self).save(*args, **kwargs)
 

	
 
    def clean_fields(self, exclude=None):
 
        super(Page, self).clean_fields(exclude)
 
        if not re.match(settings.SYMPOSION_PAGE_REGEX, self.path):
 
            raise ValidationError(
 
                {"path": [_("Path can only contain letters, numbers and hyphens and end with /")]})
 

	
 

	
 
reversion.register(Page)
 

	
 

	
 
def generate_filename(instance, filename):
 
    return filename
 

	
 

	
 
class File(models.Model):
 

	
 
    file = models.FileField(upload_to=generate_filename)
 
    created = models.DateTimeField(default=datetime.datetime.now)
 

	
 
    def download_url(self):
 
        return reverse("file_download", args=[self.pk, os.path.basename(self.file.name).lower()])
symposion/conference/apps.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.apps import AppConfig
 

	
 

	
 
class ConferenceConfig(AppConfig):
 
    name = "symposion.conference"
 
    label = "symposion_conference"
 
    verbose_name = "Symposion Conference"
symposion/conference/models.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.db import models
 
from django.utils.translation import ugettext_lazy as _
 
from django.utils.encoding import python_2_unicode_compatible
 

	
 
from timezones.fields import TimeZoneField
 

	
 

	
 
CONFERENCE_CACHE = {}
 

	
 

	
 
@python_2_unicode_compatible
 
class Conference(models.Model):
 
    """
 
    the full conference for a specific year, e.g. US PyCon 2012.
 
    """
 

	
 
    title = models.CharField(_("title"), max_length=100)
 

	
 
    # when the conference runs
 
    start_date = models.DateField(_("start date"), null=True, blank=True)
 
    end_date = models.DateField(_("end date"), null=True, blank=True)
 

	
 
    # timezone the conference is in
 
    timezone = TimeZoneField(_("timezone"), blank=True)
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        return self.title
 

	
 
    def save(self, *args, **kwargs):
 
        super(Conference, self).save(*args, **kwargs)
 
        if self.id in CONFERENCE_CACHE:
 
            del CONFERENCE_CACHE[self.id]
 

	
 
    def delete(self):
 
        pk = self.pk
 
        super(Conference, self).delete()
 
        try:
 
            del CONFERENCE_CACHE[pk]
 
        except KeyError:
 
            pass
 

	
 
    class Meta(object):
 
        verbose_name = _("conference")
 
        verbose_name_plural = _("conferences")
 

	
 

	
 
@python_2_unicode_compatible
 
class Section(models.Model):
 
    """
 
    a section of the conference such as "Tutorials", "Workshops",
 
    "Talks", "Expo", "Sprints", that may have its own review and
 
    scheduling process.
 
    """
 

	
 
    conference = models.ForeignKey(Conference, verbose_name=_("conference"))
 

	
 
    name = models.CharField(_("name"), max_length=100)
 
    slug = models.SlugField()
 

	
 
    # when the section runs
 
    start_date = models.DateField(_("start date"), null=True, blank=True)
 
    end_date = models.DateField(_("end date"), null=True, blank=True)
 

	
 
    def __unicode__(self):
 
        return u"%s %s" % (self.conference, self.name)
 
    def __str__(self):
 
        return "%s %s" % (self.conference, self.name)
 

	
 
    class Meta(object):
 
        verbose_name = _("section")
 
        verbose_name_plural = _("sections")
 
        ordering = ["start_date"]
 

	
 

	
 
def current_conference():
 
    from django.conf import settings
 
    try:
 
        conf_id = settings.CONFERENCE_ID
 
    except AttributeError:
 
        from django.core.exceptions import ImproperlyConfigured
 
        raise ImproperlyConfigured("You must set the CONFERENCE_ID setting.")
 
    try:
 
        current_conf = CONFERENCE_CACHE[conf_id]
 
    except KeyError:
 
        current_conf = Conference.objects.get(pk=conf_id)
 
        CONFERENCE_CACHE[conf_id] = current_conf
 
    return current_conf
symposion/markdown_parser.py
Show inline comments
 
from __future__ import unicode_literals
 
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)
 
    for token in parser.parseFragment(text).childNodes:
 
        bits.append(token.toxml())
 
    return "".join(bits)
symposion/proposals/actions.py
Show inline comments
 
from __future__ import unicode_literals
 
import csv
 

	
 
from django.http import HttpResponse
 

	
 

	
 
def export_as_csv_action(description="Export selected objects as CSV file",
 
                         fields=None, exclude=None, header=True):
 
    """
 
    This function returns an export csv action
 
    'fields' and 'exclude' work like in Django ModelForm
 
    'header' is whether or not to output the column names as the first row
 
    """
 
    def export_as_csv(modeladmin, request, queryset):
 
        """
 
        Generic csv export admin action.
 
        based on http://djangosnippets.org/snippets/1697/
 
        """
 
        opts = modeladmin.model._meta
 
        if fields:
 
            fieldset = set(fields)
 
            field_names = fieldset
 
        elif exclude:
 
            excludeset = set(exclude)
 
            field_names = field_names - excludeset
 
        response = HttpResponse(content_type="text/csv")
 
        response["Content-Disposition"] = "attachment; filename=%s.csv" % unicode(opts).replace(".", "_")
 
        writer = csv.writer(response)
 
        if header:
 
            writer.writerow(list(field_names))
 
        for obj in queryset:
 
            writer.writerow(
 
                [unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names])
 
        return response
 
    export_as_csv.short_description = description
 
    return export_as_csv
symposion/proposals/apps.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.apps import AppConfig
 

	
 

	
 
class ProposalsConfig(AppConfig):
 
    name = "symposion.proposals"
 
    label = "symposion_proposals"
 
    verbose_name = "Symposion Proposals"
symposion/proposals/forms.py
Show inline comments
 
from __future__ import unicode_literals
 
from django import forms
 
from django.db.models import Q
 

	
 
from symposion.proposals.models import SupportingDocument
 
# from markitup.widgets import MarkItUpWidget
 

	
 

	
 
# @@@ generic proposal form
 

	
 

	
 
class AddSpeakerForm(forms.Form):
 

	
 
    email = forms.EmailField(
 
        label="Email address of new speaker (use their email address, not yours)"
 
    )
 

	
 
    def __init__(self, *args, **kwargs):
 
        self.proposal = kwargs.pop("proposal")
 
        super(AddSpeakerForm, self).__init__(*args, **kwargs)
 

	
 
    def clean_email(self):
 
        value = self.cleaned_data["email"]
 
        exists = self.proposal.additional_speakers.filter(
 
            Q(user=None, invite_email=value) |
 
            Q(user__email=value)
 
        ).exists()
 
        if exists:
 
            raise forms.ValidationError(
 
                "This email address has already been invited to your talk proposal"
 
            )
 
        return value
 

	
 

	
 
class SupportingDocumentCreateForm(forms.ModelForm):
 

	
 
    class Meta:
 
        model = SupportingDocument
 
        fields = [
 
            "file",
 
            "description",
 
        ]
symposion/proposals/models.py
Show inline comments
 
from __future__ import unicode_literals
 
import os
 
import uuid
 

	
 
from django.core.urlresolvers import reverse
 
from django.db import models
 
from django.db.models import Q
 
from django.utils.encoding import python_2_unicode_compatible
 
from django.utils.translation import ugettext_lazy as _
 
from django.utils.timezone import now
 

	
 
from django.contrib.auth.models import User
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.core.exceptions import ValidationError
 

	
 
import reversion
 

	
 
from markitup.fields import MarkupField
 

	
 
from model_utils.managers import InheritanceManager
 

	
 
from symposion.conference.models import Section
 
from symposion.speakers.models import Speaker
 

	
 

	
 
@python_2_unicode_compatible
 
class ProposalSection(models.Model):
 
    """
 
    configuration of proposal submissions for a specific Section.
 

	
 
    a section is available for proposals iff:
 
      * it is after start (if there is one) and
 
      * it is before end (if there is one) and
 
      * closed is NULL or False
 
    """
 

	
 
    section = models.OneToOneField(Section)
 

	
 
    start = models.DateTimeField(null=True, blank=True)
 
    end = models.DateTimeField(null=True, blank=True)
 
    closed = models.NullBooleanField()
 
    published = models.NullBooleanField()
 

	
 
    @classmethod
 
    def available(cls):
 
        return cls._default_manager.filter(
 
            Q(start__lt=now()) | Q(start=None),
 
            Q(end__gt=now()) | Q(end=None),
 
            Q(closed=False) | Q(closed=None),
 
        )
 

	
 
    def is_available(self):
 
        if self.closed:
 
            return False
 
        if self.start and self.start > now():
 
            return False
 
        if self.end and self.end < now():
 
            return False
 
        return True
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        return self.section.name
 

	
 

	
 
@python_2_unicode_compatible
 
class ProposalKind(models.Model):
 
    """
 
    e.g. talk vs panel vs tutorial vs poster
 

	
 
    Note that if you have different deadlines, reviewers, etc. you'll want
 
    to distinguish the section as well as the kind.
 
    """
 

	
 
    section = models.ForeignKey(Section, related_name="proposal_kinds")
 

	
 
    name = models.CharField(_("Name"), max_length=100)
 
    slug = models.SlugField()
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        return self.name
 

	
 

	
 
@python_2_unicode_compatible
 
class ProposalBase(models.Model):
 

	
 
    objects = InheritanceManager()
 

	
 
    kind = models.ForeignKey(ProposalKind)
 

	
 
    title = models.CharField(max_length=100)
 
    description = models.TextField(
 
        _("Brief Description"),
 
        max_length=400,  # @@@ need to enforce 400 in UI
 
        help_text=_("If your proposal is accepted this will be made public and printed in the "
 
                    "program. Should be one paragraph, maximum 400 characters.")
 
    )
 
    abstract = MarkupField(
 
        _("Detailed Abstract"),
 
        help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit "
 
                    "using <a href='http://daringfireball.net/projects/markdown/basics' "
 
                    "target='_blank'>Markdown</a>.")
 
    )
 
    additional_notes = MarkupField(
 
        blank=True,
 
        help_text=_("Anything else you'd like the program committee to know when making their "
 
                    "selection: your past experience, etc. This is not made public. Edit using "
 
                    "<a href='http://daringfireball.net/projects/markdown/basics' "
 
                    "target='_blank'>Markdown</a>.")
 
    )
 
    submitted = models.DateTimeField(
 
        default=now,
 
        editable=False,
 
    )
 
    speaker = models.ForeignKey(Speaker, related_name="proposals")
 

	
 
    def additional_speaker_validator(self, a_speaker):
 
        if a_speaker.speaker.email == self.speaker.email:
 
            raise ValidationError(_("%s is same as primary speaker.") % a_speaker.speaker.email)
 
        if a_speaker in [self.additional_speakers]:
 
            raise ValidationError(_("%s has already been in speakers.") % a_speaker.speaker.email)
 

	
 
    additional_speakers = models.ManyToManyField(Speaker, through="AdditionalSpeaker",
 
                                                 blank=True, validators=[additional_speaker_validator])
 
    cancelled = models.BooleanField(default=False)
 

	
 
    def can_edit(self):
 
        return True
 

	
 
    @property
 
    def section(self):
 
        return self.kind.section
 

	
 
    @property
 
    def speaker_email(self):
 
        return self.speaker.email
 

	
 
    @property
 
    def number(self):
 
        return str(self.pk).zfill(3)
 

	
 
    @property
 
    def status(self):
 
        try:
 
            return self.result.status
 
        except ObjectDoesNotExist:
 
            return _('Undecided')
 

	
 
    def speakers(self):
 
        yield self.speaker
 
        speakers = self.additional_speakers.exclude(
 
            additionalspeaker__status=AdditionalSpeaker.SPEAKING_STATUS_DECLINED)
 
        for speaker in speakers:
 
            yield speaker
 

	
 
    def notification_email_context(self):
 
        return {
 
            "title": self.title,
 
            "speaker": self.speaker.name,
 
            "speakers": ', '.join([x.name for x in self.speakers()]),
 
            "kind": self.kind.name,
 
        }
 

	
 
    def __str__(self):
 
        return self.title
 

	
 
reversion.register(ProposalBase)
 

	
 

	
 
@python_2_unicode_compatible
 
class AdditionalSpeaker(models.Model):
 

	
 
    SPEAKING_STATUS_PENDING = 1
 
    SPEAKING_STATUS_ACCEPTED = 2
 
    SPEAKING_STATUS_DECLINED = 3
 

	
 
    SPEAKING_STATUS = [
 
        (SPEAKING_STATUS_PENDING, _("Pending")),
 
        (SPEAKING_STATUS_ACCEPTED, _("Accepted")),
 
        (SPEAKING_STATUS_DECLINED, _("Declined")),
 
    ]
 

	
 
    speaker = models.ForeignKey(Speaker)
 
    proposalbase = models.ForeignKey(ProposalBase)
 
    status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING)
 

	
 
    class Meta:
 
        unique_together = ("speaker", "proposalbase")
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        if self.status is self.SPEAKING_STATUS_PENDING:
 
            return _(u"pending speaker (%s)") % self.speaker.email
 
        elif self.status is self.SPEAKING_STATUS_DECLINED:
 
            return _(u"declined speaker (%s)") % self.speaker.email
 
        else:
 
            return self.speaker.name
 

	
 

	
 
def uuid_filename(instance, filename):
 
    ext = filename.split(".")[-1]
 
    filename = "%s.%s" % (uuid.uuid4(), ext)
 
    return os.path.join("document", filename)
 

	
 

	
 
class SupportingDocument(models.Model):
 

	
 
    proposal = models.ForeignKey(ProposalBase, related_name="supporting_documents")
 

	
 
    uploaded_by = models.ForeignKey(User)
 

	
 
    created_at = models.DateTimeField(default=now)
 

	
 
    file = models.FileField(upload_to=uuid_filename)
 
    description = models.CharField(max_length=140)
 

	
 
    def download_url(self):
 
        return reverse("proposal_document_download",
 
                       args=[self.pk, os.path.basename(self.file.name).lower()])
symposion/proposals/views.py
Show inline comments
 
from __future__ import unicode_literals
 
import hashlib
 
import random
 
import sys
 

	
 
from django.conf import settings
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.core.urlresolvers import reverse
 
from django.db.models import Q
 
from django.http import Http404, HttpResponse, HttpResponseForbidden
 
from django.shortcuts import render, redirect, get_object_or_404
 
from django.views import static
 

	
 
from django.contrib import messages
 
from django.contrib.auth.models import User
 
from django.contrib.auth.decorators import login_required
 

	
 
from django.utils.translation import ugettext_lazy as _
 

	
 
from account.models import EmailAddress
 
from symposion.proposals.models import (
 
    ProposalBase, ProposalSection, ProposalKind
 
)
 
from symposion.proposals.models import SupportingDocument, AdditionalSpeaker
 
from symposion.speakers.models import Speaker
 
from symposion.utils.mail import send_email
 

	
 
from symposion.proposals.forms import (
 
    AddSpeakerForm, SupportingDocumentCreateForm
 
)
 

	
 

	
 
def get_form(name):
 
    dot = name.rindex(".")
 
    mod_name, form_name = name[:dot], name[dot + 1:]
 
    __import__(mod_name)
 
    return getattr(sys.modules[mod_name], form_name)
 

	
 

	
 
def proposal_submit(request):
 
    if not request.user.is_authenticated():
 
        messages.info(request, _("To submit a proposal, please "
 
                                 "<a href='{0}'>log in</a> and create a speaker profile "
 
                                 "via the dashboard.".format(settings.LOGIN_URL)))
 
        return redirect("home")  # @@@ unauth'd speaker info page?
 
    else:
 
        try:
 
            request.user.speaker_profile
 
        except ObjectDoesNotExist:
symposion/reviews/apps.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.apps import AppConfig
 

	
 

	
 
class ReviewsConfig(AppConfig):
 
    name = "symposion.reviews"
 
    label = "symposion_reviews"
 
    verbose_name = "Symposion Reviews"
symposion/reviews/forms.py
Show inline comments
 
from __future__ import unicode_literals
 
from django import forms
 

	
 
from markitup.widgets import MarkItUpWidget
 

	
 
from symposion.reviews.models import Review, Comment, ProposalMessage, VOTES
 

	
 

	
 
class ReviewForm(forms.ModelForm):
 
    class Meta:
 
        model = Review
 
        fields = ["vote", "comment"]
 
        widgets = {"comment": MarkItUpWidget()}
 

	
 
    def __init__(self, *args, **kwargs):
 
        super(ReviewForm, self).__init__(*args, **kwargs)
 
        self.fields["vote"] = forms.ChoiceField(
 
            widget=forms.RadioSelect(),
 
            choices=VOTES.CHOICES
 
        )
 

	
 

	
 
class ReviewCommentForm(forms.ModelForm):
 
    class Meta:
 
        model = Comment
 
        fields = ["text"]
 
        widgets = {"text": MarkItUpWidget()}
 

	
 

	
 
class SpeakerCommentForm(forms.ModelForm):
 
    class Meta:
 
        model = ProposalMessage
 
        fields = ["message"]
 
        widgets = {"message": MarkItUpWidget()}
 

	
 

	
 
class BulkPresentationForm(forms.Form):
 
    talk_ids = forms.CharField(
 
        max_length=500,
 
        help_text="Provide a comma seperated list of talk ids to accept."
 
    )
symposion/reviews/models.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
from __future__ import unicode_literals
 
from datetime import datetime
 
from decimal import Decimal
 

	
 
from django.db import models
 
from django.db.models import Q
 
from django.db.models.signals import post_save
 

	
 
from django.contrib.auth.models import User
 

	
 
from markitup.fields import MarkupField
 

	
 
from symposion.proposals.models import ProposalBase
 
from symposion.schedule.models import Presentation
 

	
 

	
 
class ProposalScoreExpression(object):
 

	
 
    def as_sql(self, qn, connection=None):
 
        sql = "((3 * plus_one + plus_zero) - (minus_zero + 3 * minus_one))"
 
        return sql, []
 

	
 
    def prepare_database_save(self, unused):
 
        return self
 

	
 

	
 
class Votes(object):
 
    PLUS_ONE = "+1"
 
    PLUS_ZERO = "+0"
 
    MINUS_ZERO = u"−0"
 
    MINUS_ONE = u"−1"
 
    MINUS_ZERO = "−0"
 
    MINUS_ONE = "−1"
 

	
 
    CHOICES = [
 
        (PLUS_ONE, u"+1 — Good proposal and I will argue for it to be accepted."),
 
        (PLUS_ZERO, u"+0 — OK proposal, but I will not argue for it to be accepted."),
 
        (MINUS_ZERO, u"−0 — Weak proposal, but I will not argue strongly against acceptance."),
 
        (MINUS_ONE, u"−1 — Serious issues and I will argue to reject this proposal."),
 
    ]
 
VOTES = Votes()
 

	
 

	
 
class ReviewAssignment(models.Model):
 
    AUTO_ASSIGNED_INITIAL = 0
 
    OPT_IN = 1
 
    AUTO_ASSIGNED_LATER = 2
 

	
 
    NUM_REVIEWERS = 3
 

	
 
    ORIGIN_CHOICES = [
 
        (AUTO_ASSIGNED_INITIAL, "auto-assigned, initial"),
 
        (OPT_IN, "opted-in"),
 
        (AUTO_ASSIGNED_LATER, "auto-assigned, later"),
 
    ]
 

	
 
    proposal = models.ForeignKey(ProposalBase)
 
    user = models.ForeignKey(User)
 

	
 
    origin = models.IntegerField(choices=ORIGIN_CHOICES)
 

	
 
    assigned_at = models.DateTimeField(default=datetime.now)
 
    opted_out = models.BooleanField(default=False)
 

	
 
    @classmethod
 
    def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL):
 
        speakers = [proposal.speaker] + list(proposal.additional_speakers.all())
 
        reviewers = User.objects.exclude(
 
            pk__in=[
 
                speaker.user_id
 
                for speaker in speakers
 
                if speaker.user_id is not None
 
            ] + [
 
                assignment.user_id
 
                for assignment in ReviewAssignment.objects.filter(
 
                    proposal_id=proposal.id)]
 
        ).filter(
 
            groups__name="reviewers",
 
        ).filter(
 
            Q(reviewassignment__opted_out=False) | Q(reviewassignment=None)
 
        ).annotate(
symposion/reviews/utils.py
Show inline comments
 
from __future__ import unicode_literals
 

	
 

	
 
def has_permission(user, proposal, speaker=False, reviewer=False):
 
    """
 
    Returns whether or not ther user has permission to review this proposal,
 
    with the specified requirements.
 

	
 
    If ``speaker`` is ``True`` then the user can be one of the speakers for the
 
    proposal.  If ``reviewer`` is ``True`` the speaker can be a part of the
 
    reviewer group.
 
    """
 
    if user.is_superuser:
 
        return True
 
    if speaker:
 
        if user == proposal.speaker.user or \
 
           proposal.additional_speakers.filter(user=user).exists():
 
            return True
 
    if reviewer:
 
        if user.groups.filter(name="reviewers").exists():
 
            return True
 
    return False
symposion/schedule/admin.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.contrib import admin
 

	
 
from symposion.schedule.models import Schedule, Day, Room, SlotKind, Slot, SlotRoom, Presentation, Session, SessionRole
 

	
 

	
 
class DayInline(admin.StackedInline):
 
    model = Day
 
    extra = 2
 

	
 

	
 
class SlotKindInline(admin.StackedInline):
 
    model = SlotKind
 

	
 

	
 
class ScheduleAdmin(admin.ModelAdmin):
 
    model = Schedule
 
    inlines = [DayInline, SlotKindInline, ]
 

	
 

	
 
class SlotRoomInline(admin.TabularInline):
 
    model = SlotRoom
 
    extra = 1
 

	
 

	
 
class SlotAdmin(admin.ModelAdmin):
 
    list_filter = ("day", "kind")
 
    list_display = ("day", "start", "end", "kind", "content")
 
    inlines = [SlotRoomInline]
 

	
 
class RoomAdmin(admin.ModelAdmin):
 
    list_display = ["name", "order", "schedule"]
 
    list_filter = ["schedule"]
 
    inlines = [SlotRoomInline]
 

	
 

	
 
class PresentationAdmin(admin.ModelAdmin):
 
    model = Presentation
 
    list_filter = ("section", "cancelled", "slot")
 

	
 

	
 
admin.site.register(Day)
 
admin.site.register(
 
    SlotKind,
 
    list_display=["label", "schedule"],
 
)
 
admin.site.register(
 
    SlotRoom,
 
    list_display=["slot", "room"]
symposion/schedule/apps.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.apps import AppConfig
 

	
 

	
 
class ScheduleConfig(AppConfig):
 
    name = "symposion.schedule"
 
    label = "symposion_schedule"
 
    verbose_name = "Symposion Schedule"
symposion/schedule/forms.py
Show inline comments
 
from __future__ import unicode_literals
 
import csv
 
import time
 

	
 
from datetime import datetime
 

	
 
from django import forms
 
from django.contrib import messages
 
from django.db import IntegrityError, transaction
 
from django.db.models import Q
 

	
 
from markitup.widgets import MarkItUpWidget
 

	
 
from symposion.schedule.models import (Day, Presentation, Room, SlotKind, Slot,
 
                                       SlotRoom)
 

	
 

	
 
class SlotEditForm(forms.Form):
 

	
 
    def __init__(self, *args, **kwargs):
 
        self.slot = kwargs.pop("slot")
 
        super(SlotEditForm, self).__init__(*args, **kwargs)
 
        # @@@ TODO - Make this configurable
 
        if self.slot.kind.label in ["talk", "tutorial", "keynote"]:
 
            self.fields["presentation"] = self.build_presentation_field()
 
        else:
 
            self.fields["content_override"] = self.build_content_override_field()
 

	
 
    def build_presentation_field(self):
 
        kwargs = {}
 
        queryset = Presentation.objects.all()
 
        queryset = queryset.exclude(cancelled=True)
 
        queryset = queryset.order_by("proposal_base__pk")
 
        if self.slot.content:
 
            queryset = queryset.filter(Q(slot=None) | Q(pk=self.slot.content.pk))
 
            kwargs["required"] = False
 
            kwargs["initial"] = self.slot.content
 
        else:
 
            queryset = queryset.filter(slot=None)
 
            kwargs["required"] = True
 
        kwargs["queryset"] = queryset
 
        return forms.ModelChoiceField(**kwargs)
 

	
 
    def build_content_override_field(self):
 
        kwargs = {
 
            "label": "Content",
 
            "widget": MarkItUpWidget(),
 
            "required": False,
 
            "initial": self.slot.content_override,
symposion/schedule/helpers.py
Show inline comments
 
"""
 
This file contains functions that are useful to humans at the shell for
 
manipulating the database in more natural ways.
 
"""
 
from __future__ import unicode_literals
 
from django.db import transaction
 

	
 
from .models import Schedule, Day, Room, Slot, SlotKind, SlotRoom
 

	
 

	
 
@transaction.commit_on_success
 
def create_slot(section_slug, date, kind, start, end, rooms):
 
    schedule = Schedule.objects.get(section__slug=section_slug)
 
    slot = Slot()
 
    slot.day = Day.objects.get(schedule=schedule, date=date)
 
    slot.kind = SlotKind.objects.get(schedule=schedule, label=kind)
 
    slot.start = start
 
    slot.end = end
 
    slot.save(force_insert=True)
 
    if rooms == "all":
 
        rooms_qs = Room.objects.filter(schedule=schedule).order_by("order")
 
    else:
 
        rooms_qs = Room.objects.filter(schedule=schedule, name__in=rooms).order_by("order")
 
        if rooms_qs.count() != len(rooms):
 
            raise Exception("input rooms do not match queried rooms; typo?")
 
    for room in rooms_qs:
 
        slot_room = SlotRoom()
 
        slot_room.slot = slot
 
        slot_room.room = room
 
        slot_room.save(force_insert=True)
 
    print "created {} [start={}; end={}]".format(slot.kind.label, slot.start, slot.end)
symposion/schedule/models.py
Show inline comments
 
from __future__ import unicode_literals
 
import datetime
 

	
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.contrib.auth.models import User
 
from django.db import models
 
from django.utils.encoding import python_2_unicode_compatible
 

	
 
from markitup.fields import MarkupField
 

	
 
from symposion.proposals.models import ProposalBase
 
from symposion.conference.models import Section
 
from symposion.speakers.models import Speaker
 

	
 

	
 
@python_2_unicode_compatible
 
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 u"%s Schedule" % self.section
 
    def __str__(self):
 
        return "%s Schedule" % self.section
 

	
 
    class Meta:
 
        ordering = ["section"]
 

	
 

	
 
@python_2_unicode_compatible
 
class Day(models.Model):
 

	
 
    schedule = models.ForeignKey(Schedule)
 
    date = models.DateField()
 

	
 
    def __unicode__(self):
 
        return u"%s" % self.date
 
    def __str__(self):
 
        return "%s" % self.date
 

	
 
    class Meta:
 
        unique_together = [("schedule", "date")]
 
        ordering = ["date"]
 

	
 

	
 
@python_2_unicode_compatible
 
class Room(models.Model):
 

	
 
    schedule = models.ForeignKey(Schedule)
 
    name = models.CharField(max_length=65)
 
    order = models.PositiveIntegerField()
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        return self.name
 

	
 

	
 
@python_2_unicode_compatible
 
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):
 
    def __str__(self):
 
        return self.label
 

	
 

	
 
@python_2_unicode_compatible
 
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
 
        was given we need to unlink it to avoid integrity errors.
 
        """
 
        self.unassign()
 
        content.slot = self
 
        content.save()
 

	
 
    def unassign(self):
 
        """
 
        Unassign the associated content with this slot.
 
        """
 
        content = self.content
 
        if content and content.slot_id:
 
            content.slot = None
 
            content.save()
 

	
 
    @property
 
    def content(self):
 
        """
 
        Return the content this slot represents.
 
        @@@ hard-coded for presentation for now
 
        """
 
        try:
 
            return self.content_ptr
 
        except ObjectDoesNotExist:
 
            return None
 

	
 
    @property
 
    def start_datetime(self):
 
        return datetime.datetime(
 
            self.day.date.year,
 
            self.day.date.month,
 
            self.day.date.day,
 
            self.start.hour,
 
            self.start.minute)
 

	
 
    @property
 
    def end_datetime(self):
 
        return datetime.datetime(
 
            self.day.date.year,
 
            self.day.date.month,
 
            self.day.date.day,
 
            self.end.hour,
 
            self.end.minute)
 

	
 
    @property
 
    def length_in_minutes(self):
 
        return int(
 
            (self.end_datetime - self.start_datetime).total_seconds() / 60)
 

	
 
    @property
 
    def rooms(self):
 
        return Room.objects.filter(pk__in=self.slotroom_set.values("room"))
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        roomlist = ' '.join(map(lambda r: r.__unicode__(), self.rooms))
 
        return u"%s %s (%s - %s) %s" % (self.day, self.kind, self.start, self.end, roomlist)
 
        return "%s %s (%s - %s) %s" % (self.day, self.kind, self.start, self.end, roomlist)
 

	
 
    class Meta:
 
        ordering = ["day", "start", "end"]
 

	
 

	
 
@python_2_unicode_compatible
 
class SlotRoom(models.Model):
 
    """
 
    Links a slot with a room.
 
    """
 

	
 
    slot = models.ForeignKey(Slot)
 
    room = models.ForeignKey(Room)
 

	
 
    def __unicode__(self):
 
        return u"%s %s" % (self.room, self.slot)
 
    def __str__(self):
 
        return "%s %s" % (self.room, self.slot)
 

	
 
    class Meta:
 
        unique_together = [("slot", "room")]
 
        ordering = ["slot", "room__order"]
 

	
 

	
 
@python_2_unicode_compatible
 
class Presentation(models.Model):
 

	
 
    slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr")
 
    title = models.CharField(max_length=100)
 
    description = MarkupField()
 
    abstract = MarkupField()
 
    speaker = models.ForeignKey(Speaker, related_name="presentations")
 
    additional_speakers = models.ManyToManyField(Speaker, related_name="copresentations",
 
                                                 blank=True)
 
    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 u"#%s %s (%s)" % (self.number, self.title, self.speaker)
 
    def __str__(self):
 
        return "#%s %s (%s)" % (self.number, self.title, self.speaker)
 

	
 
    class Meta:
 
        ordering = ["slot"]
 

	
 

	
 
@python_2_unicode_compatible
 
class Session(models.Model):
 

	
 
    day = models.ForeignKey(Day, related_name="sessions")
 
    slots = models.ManyToManyField(Slot, related_name="sessions")
 

	
 
    def sorted_slots(self):
 
        return self.slots.order_by("start")
 

	
 
    def start(self):
 
        slots = self.sorted_slots()
 
        if slots:
 
            return list(slots)[0].start
 
        else:
 
            return None
 

	
 
    def end(self):
 
        slots = self.sorted_slots()
 
        if slots:
 
            return list(slots)[-1].end
 
        else:
 
            return None
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        start = self.start()
 
        end = self.end()
 
        if start and end:
 
            return u"%s: %s - %s" % (
 
            return "%s: %s - %s" % (
 
                self.day.date.strftime("%a"),
 
                start.strftime("%X"),
 
                end.strftime("%X")
 
            )
 
        return u""
 
        return ""
 

	
 

	
 
@python_2_unicode_compatible
 
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")]
 

	
 
    def __unicode__(self):
 
        return u"%s %s: %s" % (self.user, self.session,
 
                               self.SESSION_ROLE_TYPES[self.role - 1][1])
 
    def __str__(self):
 
        return "%s %s: %s" % (self.user, self.session,
 
                              self.SESSION_ROLE_TYPES[self.role - 1][1])
symposion/schedule/timetable.py
Show inline comments
 
from __future__ import unicode_literals
 
import itertools
 

	
 
from django.db.models import Count, Min
 

	
 
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"))
 
        slots = slots.annotate(room_count=Count("slotroom"), order=Min("slotroom__room__order"))
 
        slots = slots.order_by("start", "order")
 
        row = []
 
        for time, next_time in pairwise(times):
 
            row = {"time": time, "slots": []}
 
            for slot in slots:
 
                if slot.start == time:
 
                    slot.rowspan = TimeTable.rowspan(times, slot.start, slot.end)
 
                    slot.colspan = slot.room_count
 
                    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)
 

	
 

	
 
def pairwise(iterable):
 
    a, b = itertools.tee(iterable)
symposion/schedule/urls.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.conf.urls import url, patterns
 

	
 

	
 
urlpatterns = patterns(
 
    "symposion.schedule.views",
 
    url(r"^$", "schedule_conference", name="schedule_conference"),
 
    url(r"^edit/$", "schedule_edit", name="schedule_edit"),
 
    url(r"^list/$", "schedule_list", name="schedule_list"),
 
    url(r"^presentations.csv$", "schedule_list_csv", name="schedule_list_csv"),
 
    url(r"^presentation/(\d+)/$", "schedule_presentation_detail",
 
        name="schedule_presentation_detail"),
 
    url(r"^([\w\-]+)/$", "schedule_detail", name="schedule_detail"),
 
    url(r"^([\w\-]+)/edit/$", "schedule_edit", name="schedule_edit"),
 
    url(r"^([\w\-]+)/list/$", "schedule_list", name="schedule_list"),
 
    url(r"^([\w\-]+)/presentations.csv$", "schedule_list_csv",
 
        name="schedule_list_csv"),
 
    url(r"^([\w\-]+)/edit/slot/(\d+)/", "schedule_slot_edit",
 
        name="schedule_slot_edit"),
 
    url(r"^conference.json", "schedule_json", name="schedule_json"),
 
    url(r"^sessions/staff.txt$", "session_staff_email", name="schedule_session_staff_email"),
 
    url(r"^sessions/$", "session_list", name="schedule_session_list"),
 
    url(r"^session/(\d+)/$", "session_detail", name="schedule_session_detail"),
 
)
symposion/schedule/views.py
Show inline comments
 
from __future__ import unicode_literals
 
import json
 

	
 
from django.core.urlresolvers import reverse
 
from django.http import Http404, HttpResponse
 
from django.shortcuts import render, get_object_or_404, redirect
 
from django.template import loader, Context
 

	
 
from django.contrib.auth.decorators import login_required
 
from django.contrib.auth.models import User
 
from django.contrib import messages
 
from django.contrib.sites.models import Site
 

	
 
from symposion.schedule.forms import SlotEditForm, ScheduleSectionForm
 
from symposion.schedule.models import Schedule, Day, Slot, Presentation, Session, SessionRole
 
from symposion.schedule.timetable import TimeTable
 

	
 

	
 
def fetch_schedule(slug):
 
    qs = Schedule.objects.all()
 

	
 
    if slug is None:
 
        if qs.count() > 1:
 
            raise Http404()
 
        schedule = next(iter(qs), None)
 
        if schedule is None:
 
            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)
 
        days = [TimeTable(day) for day in days_qs]
 
        sections.append({
 
            "schedule": schedule,
 
            "days": days,
 
        })
 

	
 
    ctx = {
 
        "sections": sections,
 
    }
symposion/speakers/admin.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.contrib import admin
 

	
 
from symposion.speakers.models import Speaker
 

	
 

	
 
admin.site.register(Speaker,
 
                    list_display=["name", "email", "created"],
 
                    search_fields=["name"])
symposion/speakers/apps.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.apps import AppConfig
 

	
 

	
 
class SpeakersConfig(AppConfig):
 
    name = "symposion.speakers"
 
    label = "symposion_speakers"
 
    verbose_name = "Symposion Speakers"
symposion/speakers/forms.py
Show inline comments
 
from __future__ import unicode_literals
 
from django import forms
 

	
 
from markitup.widgets import MarkItUpWidget
 

	
 
from symposion.speakers.models import Speaker
 

	
 

	
 
class SpeakerForm(forms.ModelForm):
 

	
 
    class Meta:
 
        model = Speaker
 
        fields = [
 
            "name",
 
            "biography",
 
            "photo",
 
        ]
 
        widgets = {
 
            "biography": MarkItUpWidget(),
 
        }
symposion/speakers/models.py
Show inline comments
 
from __future__ import unicode_literals
 
import datetime
 
from django.utils.encoding import python_2_unicode_compatible
 

	
 
from django.db import models
 
from django.core.urlresolvers import reverse
 

	
 
from django.contrib.auth.models import User
 

	
 
from markitup.fields import MarkupField
 

	
 

	
 
@python_2_unicode_compatible
 
class Speaker(models.Model):
 

	
 
    SESSION_COUNT_CHOICES = [
 
        (1, "One"),
 
        (2, "Two")
 
    ]
 

	
 
    user = models.OneToOneField(User, null=True, related_name="speaker_profile")
 
    name = models.CharField(max_length=100, help_text=("As you would like it to appear in the "
 
                                                       "conference program."))
 
    biography = MarkupField(blank=True, help_text=("A little bit about you.  Edit using "
 
                                                   "<a href='http://warpedvisions.org/projects/"
 
                                                   "markdown-cheat-sheet/target='_blank'>"
 
                                                   "Markdown</a>."))
 
    photo = models.ImageField(upload_to="speaker_photos", blank=True)
 
    annotation = models.TextField()  # staff only
 
    invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True)
 
    invite_token = models.CharField(max_length=40, db_index=True)
 
    created = models.DateTimeField(
 
        default=datetime.datetime.now,
 
        editable=False
 
    )
 

	
 
    class Meta:
 
        ordering = ['name']
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        if self.user:
 
            return self.name
 
        else:
 
            return u"?"
 
            return "?"
 

	
 
    def get_absolute_url(self):
 
        return reverse("speaker_edit")
 

	
 
    @property
 
    def email(self):
 
        if self.user is not None:
 
            return self.user.email
 
        else:
 
            return self.invite_email
 

	
 
    @property
 
    def all_presentations(self):
 
        presentations = []
 
        if self.presentations:
 
            for p in self.presentations.all():
 
                presentations.append(p)
 
            for p in self.copresentations.all():
 
                presentations.append(p)
 
        return presentations
symposion/speakers/urls.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.conf.urls import patterns, url
 

	
 

	
 
urlpatterns = patterns(
 
    "symposion.speakers.views",
 
    url(r"^create/$", "speaker_create", name="speaker_create"),
 
    url(r"^create/(\w+)/$", "speaker_create_token",
 
        name="speaker_create_token"),
 
    url(r"^edit/(?:(?P<pk>\d+)/)?$", "speaker_edit", name="speaker_edit"),
 
    url(r"^profile/(?P<pk>\d+)/$", "speaker_profile", name="speaker_profile"),
 
    url(r"^staff/create/(\d+)/$", "speaker_create_staff",
 
        name="speaker_create_staff"),
 
)
symposion/speakers/views.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.http import Http404
 
from django.shortcuts import render, redirect, get_object_or_404
 

	
 
from django.contrib import messages
 
from django.contrib.auth.decorators import login_required
 
from django.contrib.auth.models import User
 

	
 
from symposion.proposals.models import ProposalBase
 
from symposion.speakers.forms import SpeakerForm
 
from symposion.speakers.models import Speaker
 

	
 

	
 
@login_required
 
def speaker_create(request):
 
    try:
 
        return redirect(request.user.speaker_profile)
 
    except ObjectDoesNotExist:
 
        pass
 

	
 
    if request.method == "POST":
 
        try:
 
            speaker = Speaker.objects.get(invite_email=request.user.email)
 
            found = True
 
        except Speaker.DoesNotExist:
 
            speaker = None
 
            found = False
 
        form = SpeakerForm(request.POST, request.FILES, instance=speaker)
 

	
 
        if form.is_valid():
 
            speaker = form.save(commit=False)
 
            speaker.user = request.user
 
            if not found:
 
                speaker.invite_email = None
 
            speaker.save()
 
            messages.success(request, "Speaker profile created.")
 
            return redirect("dashboard")
 
    else:
 
        form = SpeakerForm(initial={"name": request.user.get_full_name()})
 

	
 
    return render(request, "speakers/speaker_create.html", {
 
        "form": form,
 
    })
 

	
 

	
 
@login_required
 
def speaker_create_staff(request, pk):
 
    user = get_object_or_404(User, pk=pk)
symposion/sponsorship/admin.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.contrib import admin
 
from django.utils.html import escape
 
from django.utils.safestring import mark_safe
 
from django.utils.translation import ugettext_lazy as _
 

	
 
from symposion.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, \
 
    SponsorBenefit
 

	
 

	
 
class BenefitLevelInline(admin.TabularInline):
 
    model = BenefitLevel
 
    extra = 0
 

	
 

	
 
class SponsorBenefitInline(admin.StackedInline):
 
    model = SponsorBenefit
 
    extra = 0
 
    fieldsets = [
 
        (None, {
 
            "fields": [
 
                ("benefit", "active"),
 
                ("max_words", "other_limits"),
 
                "text",
 
                "upload",
 
            ]
 
        })
 
    ]
 

	
 

	
 
class SponsorAdmin(admin.ModelAdmin):
 

	
 
    save_on_top = True
 
    fieldsets = [
 
        (None, {
 
            "fields": [
 
                ("name", "applicant"),
 
                ("level", "active"),
 
                "external_url",
 
                "annotation",
 
                ("contact_name", "contact_email")
 
            ]
 
        }),
 
        ("Metadata", {
 
            "fields": ["added"],
 
            "classes": ["collapse"]
 
        })
 
    ]
 
    inlines = [SponsorBenefitInline]
symposion/sponsorship/forms.py
Show inline comments
 
from __future__ import unicode_literals
 
from django import forms
 
from django.forms.models import inlineformset_factory, BaseInlineFormSet
 

	
 
from django.contrib.admin.widgets import AdminFileWidget
 

	
 
from symposion.sponsorship.models import Sponsor, SponsorBenefit
 

	
 

	
 
class SponsorApplicationForm(forms.ModelForm):
 
    def __init__(self, *args, **kwargs):
 
        self.user = kwargs.pop("user")
 
        kwargs.update({
 
            "initial": {
 
                "contact_name": self.user.get_full_name,
 
                "contact_email": self.user.email,
 
            }
 
        })
 
        super(SponsorApplicationForm, self).__init__(*args, **kwargs)
 

	
 
    class Meta:
 
        model = Sponsor
 
        fields = [
 
            "name",
 
            "external_url",
 
            "contact_name",
 
            "contact_email",
 
            "level"
 
        ]
 

	
 
    def save(self, commit=True):
 
        obj = super(SponsorApplicationForm, self).save(commit=False)
 
        obj.applicant = self.user
 
        if commit:
 
            obj.save()
 
        return obj
 

	
 

	
 
class SponsorDetailsForm(forms.ModelForm):
 
    class Meta:
 
        model = Sponsor
 
        fields = [
 
            "name",
 
            "external_url",
 
            "contact_name",
 
            "contact_email"
 
        ]
 

	
 

	
symposion/sponsorship/models.py
Show inline comments
 
from __future__ import unicode_literals
 
import datetime
 

	
 
from django.conf import settings
 
from django.core.exceptions import ValidationError
 
from django.core.urlresolvers import reverse
 
from django.db import models
 
from django.db.models.signals import post_init, post_save
 
from django.utils.encoding import python_2_unicode_compatible
 
from django.utils.translation import ugettext_lazy as _
 

	
 
from django.contrib.auth.models import User
 

	
 
from symposion.conference.models import Conference
 

	
 
from symposion.sponsorship.managers import SponsorManager
 

	
 

	
 
@python_2_unicode_compatible
 
class SponsorLevel(models.Model):
 

	
 
    conference = models.ForeignKey(Conference, verbose_name=_("conference"))
 
    name = models.CharField(_("name"), max_length=100)
 
    order = models.IntegerField(_("order"), default=0)
 
    cost = models.PositiveIntegerField(_("cost"))
 
    description = models.TextField(_("description"), blank=True, help_text=_("This is private."))
 

	
 
    class Meta:
 
        ordering = ["conference", "order"]
 
        verbose_name = _("sponsor level")
 
        verbose_name_plural = _("sponsor levels")
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        return self.name
 

	
 
    def sponsors(self):
 
        return self.sponsor_set.filter(active=True).order_by("added")
 

	
 

	
 
@python_2_unicode_compatible
 
class Sponsor(models.Model):
 

	
 
    applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"),
 
                                  null=True)
 

	
 
    name = models.CharField(_("Sponsor Name"), max_length=100)
 
    display_url = models.URLField(_("display URL"), blank=True)
 
    external_url = models.URLField(_("external URL"))
 
    annotation = models.TextField(_("annotation"), blank=True)
 
    contact_name = models.CharField(_("Contact Name"), max_length=100)
 
    contact_email = models.EmailField(_(u"Contact Email"))
 
    level = models.ForeignKey(SponsorLevel, verbose_name=_("level"))
 
    added = models.DateTimeField(_("added"), default=datetime.datetime.now)
 
    active = models.BooleanField(_("active"), default=False)
 

	
 
    # Denormalization (this assumes only one logo)
 
    sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True,
 
                                     editable=False)
 

	
 
    objects = SponsorManager()
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        return self.name
 

	
 
    class Meta:
 
        verbose_name = _("sponsor")
 
        verbose_name_plural = _("sponsors")
 

	
 
    def get_absolute_url(self):
 
        if self.active:
 
            return reverse("sponsor_detail", kwargs={"pk": self.pk})
 
        return reverse("sponsor_list")
 

	
 
    def get_display_url(self):
 
        if self.display_url:
 
            return self.display_url
 
        else:
 
            return self.external_url
 

	
 
    @property
 
    def website_logo(self):
 
        if self.sponsor_logo is None:
 
            benefits = self.sponsor_benefits.filter(
 
                benefit__type="weblogo", upload__isnull=False)[:1]
 
            if benefits.count():
 
                if benefits[0].upload:
 
                    self.sponsor_logo = benefits[0]
 
                    self.save()
 
        return self.sponsor_logo.upload
 

	
 
    @property
 
    def listing_text(self):
 
        if not hasattr(self, "_listing_text"):
 
            self._listing_text = None
 
            # @@@ better than hard-coding a pk but still not good
 
            benefits = self.sponsor_benefits.filter(benefit__name="Sponsor Description")
 
            if benefits.count():
 
                self._listing_text = benefits[0].text
 
        return self._listing_text
 

	
 
    def reset_benefits(self):
 
        """
 
        Reset all benefits for this sponsor to the defaults for their
 
        sponsorship level.
 
        """
 
        level = None
 

	
 
        try:
 
            level = self.level
 
        except SponsorLevel.DoesNotExist:
...
 
@@ -121,115 +125,118 @@ class Sponsor(models.Model):
 
                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,
 
                # and won't raise a validation error until it's next
 
                # edited.
 
                sponsor_benefit.save()
 

	
 
                allowed_benefits.append(sponsor_benefit.pk)
 

	
 
        # Any remaining sponsor benefits that don't normally belong to
 
        # this level are set to inactive
 
        self.sponsor_benefits.exclude(pk__in=allowed_benefits)\
 
            .update(active=False, max_words=None, other_limits="")
 

	
 
    def send_coordinator_emails(self):
 
        pass  # @@@ should this just be done centrally?
 

	
 

	
 
def _store_initial_level(sender, instance, **kwargs):
 
    if instance:
 
        instance._initial_level_id = instance.level_id
 
post_init.connect(_store_initial_level, sender=Sponsor)
 

	
 

	
 
def _check_level_change(sender, instance, created, **kwargs):
 
    if instance and (created or instance.level_id != instance._initial_level_id):
 
        instance.reset_benefits()
 
post_save.connect(_check_level_change, sender=Sponsor)
 

	
 

	
 
BENEFIT_TYPE_CHOICES = [
 
    ("text", "Text"),
 
    ("richtext", "Rich Text"),
 
    ("file", "File"),
 
    ("weblogo", "Web Logo"),
 
    ("simple", "Simple"),
 
    ("option", "Option")
 
]
 

	
 
CONTENT_TYPE_CHOICES = [
 
    ("simple", "Simple"),
 
] + [
 
    ("listing_text_%s" % lang, "Listing Text (%s)" % label) for lang, label in settings.LANGUAGES
 
]
 

	
 

	
 
@python_2_unicode_compatible
 
class Benefit(models.Model):
 

	
 
    name = models.CharField(_("name"), max_length=100)
 
    description = models.TextField(_("description"), blank=True)
 
    type = models.CharField(_("type"), choices=BENEFIT_TYPE_CHOICES,
 
                            max_length=10, default="simple")
 
    content_type = models.CharField(_("content type"), choices=CONTENT_TYPE_CHOICES,
 
                                    max_length=20, default="simple")
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        return self.name
 

	
 

	
 
@python_2_unicode_compatible
 
class BenefitLevel(models.Model):
 

	
 
    benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit"))
 
    level = models.ForeignKey(SponsorLevel, related_name="benefit_levels", verbose_name=_("level"))
 

	
 
    # default limits for this benefit at given level
 
    max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True)
 
    other_limits = models.CharField(_("other limits"), max_length=200, blank=True)
 

	
 
    class Meta:
 
        ordering = ["level"]
 

	
 
    def __unicode__(self):
 
        return u"%s - %s" % (self.level, self.benefit)
 
    def __str__(self):
 
        return "%s - %s" % (self.level, self.benefit)
 

	
 

	
 
@python_2_unicode_compatible
 
class SponsorBenefit(models.Model):
 

	
 
    sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("sponsor"))
 
    benefit = models.ForeignKey(Benefit, related_name="sponsor_benefits", verbose_name=_("benefit"))
 
    active = models.BooleanField(default=True)
 

	
 
    # Limits: will initially be set to defaults from corresponding BenefitLevel
 
    max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True)
 
    other_limits = models.CharField(_("other limits"), max_length=200, blank=True)
 

	
 
    # Data: zero or one of these fields will be used, depending on the
 
    # type of the Benefit (text, file, or simple)
 
    text = models.TextField(_("text"), blank=True)
 
    upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files")
 

	
 
    class Meta:
 
        ordering = ["-active"]
 

	
 
    def __unicode__(self):
 
        return u"%s - %s" % (self.sponsor, self.benefit)
 
    def __str__(self):
 
        return "%s - %s" % (self.sponsor, self.benefit)
 

	
 
    def clean(self):
 
        num_words = len(self.text.split())
 
        if self.max_words and num_words > self.max_words:
 
            raise ValidationError(
 
                "Sponsorship level only allows for %s words, you provided %d." % (
 
                    self.max_words, num_words))
 

	
 
    def data_fields(self):
 
        """
 
        Return list of data field names which should be editable for
 
        this ``SponsorBenefit``, depending on its ``Benefit`` type.
 
        """
 
        if self.benefit.type == "file" or self.benefit.type == "weblogo":
 
            return ["upload"]
 
        elif self.benefit.type == "text":
 
            return ["text"]
 
        return []
symposion/sponsorship/views.py
Show inline comments
 
from __future__ import unicode_literals
 
from cStringIO import StringIO
 
import itertools
 
import logging
 
import os
 
import time
 
from zipfile import ZipFile, ZipInfo
 

	
 
from django.conf import settings
 

	
 
from django.http import Http404, HttpResponse
 
from django.shortcuts import render_to_response, redirect, get_object_or_404
 
from django.template import RequestContext
 
from django.utils.translation import ugettext_lazy as _
 

	
 
from django.contrib import messages
 
from django.contrib.admin.views.decorators import staff_member_required
 
from django.contrib.auth.decorators import login_required
 

	
 
from symposion.sponsorship.forms import SponsorApplicationForm, \
 
    SponsorDetailsForm, SponsorBenefitsFormSet
 
from symposion.sponsorship.models import Benefit, Sponsor, SponsorBenefit, \
 
    SponsorLevel
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
@login_required
 
def sponsor_apply(request):
 
    if request.method == "POST":
 
        form = SponsorApplicationForm(request.POST, user=request.user)
 
        if form.is_valid():
 
            sponsor = form.save()
 
            if sponsor.sponsor_benefits.all():
 
                # Redirect user to sponsor_detail to give extra information.
 
                messages.success(request, _("Thank you for your sponsorship "
 
                                            "application. Please update your "
 
                                            "benefit details below."))
 
                return redirect("sponsor_detail", pk=sponsor.pk)
 
            else:
 
                messages.success(request, _("Thank you for your sponsorship "
 
                                            "application."))
 
                return redirect("dashboard")
 
    else:
 
        form = SponsorApplicationForm(user=request.user)
 

	
 
    return render_to_response("sponsorship/apply.html", {
 
        "form": form,
symposion/teams/forms.py
Show inline comments
 
from __future__ import unicode_literals
 
from django import forms
 

	
 
from django.utils.html import escape
 
from django.utils.safestring import mark_safe
 

	
 
from django.contrib.auth.models import User
 

	
 
from symposion.teams.models import Membership
 

	
 

	
 
class TeamInvitationForm(forms.Form):
 

	
 
    email = forms.EmailField(help_text=("email address must be that of an account on this "
 
                                        "conference site"))
 

	
 
    def __init__(self, *args, **kwargs):
 
        self.team = kwargs.pop("team")
 
        super(TeamInvitationForm, self).__init__(*args, **kwargs)
 

	
 
    def clean(self):
 
        cleaned_data = super(TeamInvitationForm, self).clean()
 
        email = cleaned_data.get("email")
 

	
 
        if email is None:
 
            raise forms.ValidationError("valid email address required")
 

	
 
        try:
 
            user = User.objects.get(email=email)
 
        except User.DoesNotExist:
 
            # eventually we can invite them but for now assume they are
 
            # already on the site
 
            raise forms.ValidationError(
 
                mark_safe("no account with email address <b>%s</b> found on this conference "
 
                          "site" % escape(email)))
 

	
 
        state = self.team.get_state_for_user(user)
 

	
 
        if state in ["member", "manager"]:
 
            raise forms.ValidationError("user already in team")
 

	
 
        if state in ["invited"]:
 
            raise forms.ValidationError("user already invited to team")
 

	
 
        self.user = user
 
        self.state = state
 

	
 
        return cleaned_data
 

	
symposion/teams/models.py
Show inline comments
 
from __future__ import unicode_literals
 
import datetime
 

	
 
from django.db import models
 

	
 
import reversion
 

	
 
from django.contrib.auth.models import Permission, User
 
from django.utils.encoding import python_2_unicode_compatible
 

	
 

	
 
TEAM_ACCESS_CHOICES = [
 
    ("open", "open"),
 
    ("application", "by application"),
 
    ("invitation", "by invitation")
 
]
 

	
 

	
 
@python_2_unicode_compatible
 
class Team(models.Model):
 

	
 
    slug = models.SlugField(unique=True)
 
    name = models.CharField(max_length=100)
 
    description = models.TextField(blank=True)
 
    access = models.CharField(max_length=20, choices=TEAM_ACCESS_CHOICES)
 

	
 
    # member permissions
 
    permissions = models.ManyToManyField(Permission, blank=True, related_name="member_teams")
 

	
 
    # manager permissions
 
    manager_permissions = models.ManyToManyField(Permission, blank=True,
 
                                                 related_name="manager_teams")
 

	
 
    created = models.DateTimeField(default=datetime.datetime.now, editable=False)
 

	
 
    @models.permalink
 
    def get_absolute_url(self):
 
        return ("team_detail", [self.slug])
 

	
 
    def __unicode__(self):
 
    def __str__(self):
 
        return self.name
 

	
 
    def get_state_for_user(self, user):
 
        try:
 
            return self.memberships.get(user=user).state
 
        except Membership.DoesNotExist:
 
            return None
 

	
 
    def applicants(self):
 
        return self.memberships.filter(state="applied")
 

	
 
    def invitees(self):
 
        return self.memberships.filter(state="invited")
 

	
 
    def members(self):
 
        return self.memberships.filter(state="member")
 

	
 
    def managers(self):
 
        return self.memberships.filter(state="manager")
 

	
 

	
 
MEMBERSHIP_STATE_CHOICES = [
 
    ("applied", "applied"),
 
    ("invited", "invited"),
 
    ("declined", "declined"),
 
    ("rejected", "rejected"),
 
    ("member", "member"),
 
    ("manager", "manager"),
 
]
 

	
 

	
 
class Membership(models.Model):
 

	
 
    user = models.ForeignKey(User, related_name="memberships")
 
    team = models.ForeignKey(Team, related_name="memberships")
 
    state = models.CharField(max_length=20, choices=MEMBERSHIP_STATE_CHOICES)
 
    message = models.TextField(blank=True)
 

	
 

	
 
reversion.register(Membership)
symposion/teams/views.py
Show inline comments
 
from __future__ import unicode_literals
 
from django.http import Http404, HttpResponseNotAllowed
 
from django.shortcuts import render, redirect, get_object_or_404
 

	
 
from django.contrib.auth.decorators import login_required
 
from django.contrib import messages
 

	
 
from symposion.utils.mail import send_email
 

	
 
from symposion.teams.forms import TeamInvitationForm
 
from symposion.teams.models import Team, Membership
 

	
 

	
 
# perm checks
 
#
 
# @@@ these can be moved
 

	
 
def can_join(team, user):
 
    state = team.get_state_for_user(user)
 
    if team.access == "open" and state is None:
 
        return True
 
    elif state == "invited":
 
        return True
 
    elif user.is_staff and state is None:
 
        return True
 
    else:
 
        return False
 

	
 

	
 
def can_leave(team, user):
 
    state = team.get_state_for_user(user)
 
    if state == "member":  # managers can't leave at the moment
 
        return True
 
    else:
 
        return False
 

	
 

	
 
def can_apply(team, user):
 
    state = team.get_state_for_user(user)
 
    if team.access == "application" and state is None:
 
        return True
 
    else:
 
        return False
 

	
 

	
 
def can_invite(team, user):
 
    state = team.get_state_for_user(user)
 
    if team.access == "invitation":
 
        if state == "manager" or user.is_staff:
symposion/views.py
Show inline comments
 
from __future__ import unicode_literals
 
import hashlib
 
import random
 

	
 
from django.shortcuts import render, redirect
 

	
 
from django.contrib.auth.models import User
 
from django.contrib.auth.decorators import login_required
 

	
 
import account.views
 

	
 
import symposion.forms
 

	
 

	
 
class SignupView(account.views.SignupView):
 

	
 
    form_class = symposion.forms.SignupForm
 
    form_kwargs = {
 
        "prefix": "signup",
 
    }
 

	
 
    def create_user(self, form, commit=True):
 
        user_kwargs = {
 
            "first_name": form.cleaned_data["first_name"],
 
            "last_name": form.cleaned_data["last_name"]
 
        }
 
        return super(SignupView, self).create_user(form, commit=commit,
 
                                                   **user_kwargs)
 

	
 
    def generate_username(self, form):
 
        def random_username():
 
            h = hashlib.sha1(form.cleaned_data["email"]).hexdigest()[:25]
 
            # don't ask
 
            n = random.randint(1, (10 ** (5 - 1)) - 1)
 
            return "%s%d" % (h, n)
 
        while True:
 
            try:
 
                username = random_username()
 
                User.objects.get(username=username)
 
            except User.DoesNotExist:
 
                break
 
        return username
 

	
 

	
 
class LoginView(account.views.LoginView):
 

	
 
    form_class = account.forms.LoginEmailForm
 
    form_kwargs = {
 
        "prefix": "login",
0 comments (0 inline, 0 general)