Changeset - 6d5c24e6354f
[Not reviewed]
Merge
! ! !
Patrick Altman - 10 years ago 2014-07-30 21:16:03
paltman@gmail.com
Merge pull request #56 from chromano/master

Fix flake8 warnings
48 files changed with 133 insertions and 126 deletions:
0 comments (0 inline, 0 general)
symposion/boxes/models.py
Show inline comments
 
import datetime
 

	
 
from django.db import models
 

	
 
from django.contrib.auth.models import User
 

	
 
import reversion
 

	
 
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"
 

	
 

	
 
reversion.register(Box)
symposion/boxes/templatetags/boxes_tags.py
Show inline comments
 
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
 
from symposion.boxes.authorization import load_can_edit
 

	
 

	
 
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,
 
        "box": box,
 
        "form": form,
symposion/boxes/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import url, patterns
 

	
 

	
 
urlpatterns = patterns("symposion.boxes.views",
 
    url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"),
 
)
symposion/boxes/views.py
Show inline comments
 
from django.http import HttpResponseForbidden
 
from django.shortcuts import redirect
 
from django.views.decorators.http import require_POST
 

	
 
from symposion.boxes.authorization import load_can_edit
 
from symposion.boxes.forms import BoxForm
 
from symposion.boxes.models import Box
 

	
 

	
 
# @@@ problem with this is that the box_edit.html and box_create.html won't have domain objects in context
 
# @@@ problem with this is that the box_edit.html and box_create.html won't have domain objects in
 
# context
 
def get_auth_vars(request):
 
    auth_vars = {}
 
    if request.method == "POST":
 
        keys = [k for k in request.POST.keys() if k.startswith("boxes_auth_")]
 
        for key in keys:
 
            auth_vars[key.replace("boxes_auth_", "")] = request.POST.get(key)
 
        auth_vars["user"] = request.user
 
    return auth_vars
 

	
 

	
 
@require_POST
 
def box_edit(request, label):
 

	
 
    if not load_can_edit()(request, **get_auth_vars(request)):
 
        return HttpResponseForbidden()
 

	
 
    next = request.GET.get("next")
 

	
 
    try:
 
        box = Box.objects.get(label=label)
 
    except Box.DoesNotExist:
 
        box = None
 

	
 
    form = BoxForm(request.POST, instance=box, prefix=label)
 

	
 
    if form.is_valid():
 
        if box is None:
 
            box = form.save(commit=False)
 
            box.label = label
 
            box.created_by = request.user
 
            box.last_updated_by = request.user
 
            box.save()
symposion/cms/admin.py
Show inline comments
 
from django.contrib import admin
 

	
 
import reversion
 

	
 
from .models import Page
 

	
 

	
 
class PageAdmin(reversion.VersionAdmin):
 

	
 
    pass
 

	
 

	
 
admin.site.register(Page, PageAdmin)
symposion/cms/managers.py
Show inline comments
 
from datetime import datetime
 

	
 
from django.db import models
 

	
 

	
 
class PublishedPageManager(models.Manager):
 

	
 
    def get_query_set(self):
 
        qs = super(PublishedPageManager, self).get_query_set()
 
        return qs.filter(publish_date__lte=datetime.now())
symposion/cms/models.py
Show inline comments
...
 
@@ -24,49 +24,50 @@ class Page(models.Model):
 
        (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):
 
        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 /")]})
 
            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/cms/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import url, patterns
 

	
 
PAGE_RE = r"(([\w-]{1,})(/[\w-]{1,})*)/"
 

	
 
urlpatterns = patterns("symposion.cms.views",
 
    url(r"^files/$", "file_index", name="file_index"),
 
    url(r"^files/create/$", "file_create", name="file_create"),
 
    url(r"^files/(\d+)/([^/]+)$", "file_download", name="file_download"),
 
    url(r"^files/(\d+)/delete/$", "file_delete", name="file_delete"),
 
    url(r"^(?P<path>%s)_edit/$" % PAGE_RE, "page_edit", name="cms_page_edit"),
 
    url(r"^(?P<path>%s)$" % PAGE_RE, "page", name="cms_page"),
 
)
symposion/conf.py
Show inline comments
 
from django.conf import settings
 

	
 
from appconf import AppConf
 

	
 

	
 
class SymposionAppConf(AppConf):
 

	
 
    VOTE_THRESHOLD = 3
symposion/conference/urls.py
Show inline comments
 
from django.conf.urls.defaults import *
 
# flake8: noqa
 
from django.conf.urls.defaults import patterns, url
 

	
 

	
 
urlpatterns = patterns("symposion.conference.views",
 
    url(r"^users/$", "user_list", name="user_list"),
 
)
symposion/forms.py
Show inline comments
 
from django import forms
 

	
 
import account.forms
 

	
 

	
 
class SignupForm(account.forms.SignupForm):
 

	
 
    first_name = forms.CharField()
 
    last_name = forms.CharField()
 
    email_confirm = forms.EmailField(label="Confirm Email")
 

	
 
    def __init__(self, *args, **kwargs):
 
        super(SignupForm, self).__init__(*args, **kwargs)
 
        del self.fields["username"]
 
        self.fields.keyOrder = [
 
            "email",
 
            "email_confirm",
 
            "first_name",
 
            "last_name",
 
            "password",
 
            "password_confirm"
 
        ]
 

	
 
    def clean_email_confirm(self):
 
        email = self.cleaned_data.get("email")
 
        email_confirm = self.cleaned_data["email_confirm"]
 
        if email:
 
            if email != email_confirm:
 
                raise forms.ValidationError("Email address must match previously typed email address")
 
                raise forms.ValidationError(
 
                    "Email address must match previously typed email address")
 
        return email_confirm
symposion/markdown_parser.py
Show inline comments
 
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)
 
    for token in parser.parseFragment(text).childNodes:
 
        bits.append(token.toxml())
 
    return "".join(bits)
symposion/proposals/actions.py
Show inline comments
 
import csv
 

	
 
from django.http import HttpResponse
 

	
 

	
 
def export_as_csv_action(
 
    description="Export selected objects as CSV file",
 
def export_as_csv_action(description="Export selected objects as CSV file",
 
                         fields=None, exclude=None, header=True):
 
    """
 
    This function returns an export csv action
 
    'fields' and 'exclude' work like in Django ModelForm
 
    'header' is whether or not to output the column names as the first row
 
    """
 
    def export_as_csv(modeladmin, request, queryset):
 
        """
 
        Generic csv export admin action.
 
        based on http://djangosnippets.org/snippets/1697/
 
        """
 
        opts = modeladmin.model._meta
 
        if fields:
 
            fieldset = set(fields)
 
            field_names = fieldset
 
        elif exclude:
 
            excludeset = set(exclude)
 
            field_names = field_names - excludeset
 
        response = HttpResponse(mimetype="text/csv")
 
        response["Content-Disposition"] = "attachment; filename=%s.csv" % unicode(opts).replace(".", "_")
 
        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])
 
            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/managers.py
Show inline comments
 
deleted file
symposion/proposals/models.py
Show inline comments
...
 
@@ -56,120 +56,130 @@ class ProposalSection(models.Model):
 

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

	
 

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

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

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

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

	
 
    def __unicode__(self):
 
        return self.name
 

	
 

	
 
class ProposalBase(models.Model):
 

	
 
    objects = InheritanceManager()
 

	
 
    kind = models.ForeignKey(ProposalKind)
 

	
 
    title = models.CharField(max_length=100)
 
    description = models.TextField(
 
        _("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."
 
        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>.")
 
        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>.")
 
        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=datetime.datetime.now,
 
        editable=False,
 
    )
 
    speaker = models.ForeignKey("speakers.Speaker", related_name="proposals")
 
    additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker", blank=True)
 
    additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker",
 
                                                 blank=True)
 
    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)
 

	
 
    def speakers(self):
 
        yield self.speaker
 
        for speaker in self.additional_speakers.exclude(additionalspeaker__status=AdditionalSpeaker.SPEAKING_STATUS_DECLINED):
 
        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,
 
            "kind": self.kind.name,
 
        }
 

	
 

	
 
reversion.register(ProposalBase)
 

	
 

	
 
class AdditionalSpeaker(models.Model):
 

	
 
    SPEAKING_STATUS_PENDING = 1
 
    SPEAKING_STATUS_ACCEPTED = 2
 
    SPEAKING_STATUS_DECLINED = 3
 

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

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

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

	
 

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

	
 

	
 
class SupportingDocument(models.Model):
 

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

	
 
    uploaded_by = models.ForeignKey(User)
 
    created_at = models.DateTimeField(default=datetime.datetime.now)
 

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

	
 
    def download_url(self):
 
        return reverse("proposal_document_download", args=[self.pk, os.path.basename(self.file.name).lower()])
 
        return reverse("proposal_document_download",
 
                       args=[self.pk, os.path.basename(self.file.name).lower()])
symposion/proposals/templatetags/proposal_tags.py
Show inline comments
...
 
@@ -41,33 +41,32 @@ class PendingProposalsNode(template.Node):
 
        else:
 
            raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
 

	
 
    def __init__(self, context_var):
 
        self.context_var = context_var
 

	
 
    def render(self, context):
 
        request = context["request"]
 
        if request.user.speaker_profile:
 
            pending = AdditionalSpeaker.SPEAKING_STATUS_PENDING
 
            speaker = request.user.speaker_profile
 
            queryset = AdditionalSpeaker.objects.filter(speaker=speaker, status=pending)
 
            context[self.context_var] = [item.proposalbase for item in queryset]
 
        else:
 
            context[self.context_var] = None
 
        return u""
 

	
 

	
 
@register.tag
 
def pending_proposals(parser, token):
 
    """
 
    {% pending_proposals as pending_proposals %}
 
    """
 
    return PendingProposalsNode.handle_token(parser, token)
 

	
 

	
 
@register.tag
 
def associated_proposals(parser, token):
 
    """
 
    {% associated_proposals as associated_proposals %}
 
    """
 
    return AssociatedProposalsNode.handle_token(parser, token)
 

	
symposion/proposals/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import *
 

	
 

	
 
urlpatterns = patterns("symposion.proposals.views",
 
    url(r"^submit/$", "proposal_submit", name="proposal_submit"),
 
    url(r"^submit/([\w\-]+)/$", "proposal_submit_kind", name="proposal_submit_kind"),
 
    url(r"^(\d+)/$", "proposal_detail", name="proposal_detail"),
 
    url(r"^(\d+)/edit/$", "proposal_edit", name="proposal_edit"),
 
    url(r"^(\d+)/speakers/$", "proposal_speaker_manage", name="proposal_speaker_manage"),
 
    url(r"^(\d+)/cancel/$", "proposal_cancel", name="proposal_cancel"),
 
    url(r"^(\d+)/leave/$", "proposal_leave", name="proposal_leave"),
 
    url(r"^(\d+)/join/$", "proposal_pending_join", name="proposal_pending_join"),
 
    url(r"^(\d+)/decline/$", "proposal_pending_decline", name="proposal_pending_decline"),
 

	
 
    url(r"^(\d+)/document/create/$", "document_create", name="proposal_document_create"),
 
    url(r"^document/(\d+)/delete/$", "document_delete", name="proposal_document_delete"),
 
    url(r"^document/(\d+)/([^/]+)$", "document_download", name="proposal_document_download"),
 
)
symposion/proposals/views.py
Show inline comments
...
 
@@ -123,65 +123,66 @@ def proposal_speaker_manage(request, pk):
 
            # check if email is on the site now
 
            users = EmailAddress.objects.get_users_for(email_address)
 
            if users:
 
                # should only be one since we enforce unique email
 
                user = users[0]
 
                message_ctx["user"] = user
 
                # look for speaker profile
 
                try:
 
                    speaker = user.speaker_profile
 
                except ObjectDoesNotExist:
 
                    speaker, token = create_speaker_token(email_address)
 
                    message_ctx["token"] = token
 
                    # fire off email to user to create profile
 
                    send_email(
 
                        [email_address], "speaker_no_profile",
 
                        context=message_ctx
 
                    )
 
                else:
 
                    # fire off email to user letting them they are loved.
 
                    send_email(
 
                        [email_address], "speaker_addition",
 
                        context=message_ctx
 
                    )
 
            else:
 
                speaker, token = create_speaker_token(email_address)
 
                message_ctx["token"] = token
 
                # fire off email letting user know about site and to create
 
                # account and speaker profile
 
                send_email(
 
                    [email_address], "speaker_invite",
 
                    context=message_ctx
 
                )
 
            invitation, created = AdditionalSpeaker.objects.get_or_create(proposalbase=proposal.proposalbase_ptr, speaker=speaker)
 
            invitation, created = AdditionalSpeaker.objects.get_or_create(
 
                proposalbase=proposal.proposalbase_ptr, speaker=speaker)
 
            messages.success(request, "Speaker invited to proposal.")
 
            return redirect("proposal_speaker_manage", proposal.pk)
 
    else:
 
        add_speaker_form = AddSpeakerForm(proposal=proposal)
 
    ctx = {
 
        "proposal": proposal,
 
        "speakers": proposal.speakers(),
 
        "add_speaker_form": add_speaker_form,
 
    }
 
    return render(request, "proposals/proposal_speaker_manage.html", ctx)
 

	
 

	
 
@login_required
 
def proposal_edit(request, pk):
 
    queryset = ProposalBase.objects.select_related("speaker")
 
    proposal = get_object_or_404(queryset, pk=pk)
 
    proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
 

	
 
    if request.user != proposal.speaker.user:
 
        raise Http404()
 

	
 
    if not proposal.can_edit():
 
        ctx = {
 
            "title": "Proposal editing closed",
 
            "body": "Proposal editing is closed for this session type."
 
        }
 
        return render(request, "proposals/proposal_error.html", ctx)
 

	
 
    form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug])
 

	
 
    if request.method == "POST":
 
        form = form_class(request.POST, instance=proposal)
...
 
@@ -282,78 +283,80 @@ def proposal_cancel(request, pk):
 
        messages.success(request, "%s has been cancelled" % proposal.title)
 
        return redirect("dashboard")
 

	
 
    return render(request, "proposals/proposal_cancel.html", {
 
        "proposal": proposal,
 
    })
 

	
 

	
 
@login_required
 
def proposal_leave(request, pk):
 
    queryset = ProposalBase.objects.select_related("speaker")
 
    proposal = get_object_or_404(queryset, pk=pk)
 
    proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
 

	
 
    try:
 
        speaker = proposal.additional_speakers.get(user=request.user)
 
    except ObjectDoesNotExist:
 
        return HttpResponseForbidden()
 
    if request.method == "POST":
 
        proposal.additional_speakers.remove(speaker)
 
        # @@@ fire off email to submitter and other speakers
 
        messages.success(request, "You are no longer speaking on %s" % proposal.title)
 
        return redirect("dashboard")
 
    ctx = {
 
        "proposal": proposal,
 
    }
 
    return render(request, "proposals/proposal_leave.html", ctx)
 

	
 

	
 
@login_required
 
def proposal_pending_join(request, pk):
 
    proposal = get_object_or_404(ProposalBase, pk=pk)
 
    speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, proposalbase=proposal)
 
    speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile,
 
                                 proposalbase=proposal)
 
    if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING:
 
        speaking.status = AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED
 
        speaking.save()
 
        messages.success(request, "You have accepted the invitation to join %s" % proposal.title)
 
        return redirect("dashboard")
 
    else:
 
        return redirect("dashboard")
 

	
 

	
 
@login_required
 
def proposal_pending_decline(request, pk):
 
    proposal = get_object_or_404(ProposalBase, pk=pk)
 
    speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, proposalbase=proposal)
 
    speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile,
 
                                 proposalbase=proposal)
 
    if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING:
 
        speaking.status = AdditionalSpeaker.SPEAKING_STATUS_DECLINED
 
        speaking.save()
 
        messages.success(request, "You have declined to speak on %s" % proposal.title)
 
        return redirect("dashboard")
 
    else:
 
        return redirect("dashboard")
 

	
 

	
 
@login_required
 
def document_create(request, proposal_pk):
 
    queryset = ProposalBase.objects.select_related("speaker")
 
    proposal = get_object_or_404(queryset, pk=proposal_pk)
 
    proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
 

	
 
    if proposal.cancelled:
 
        return HttpResponseForbidden()
 

	
 
    if request.method == "POST":
 
        form = SupportingDocumentCreateForm(request.POST, request.FILES)
 
        if form.is_valid():
 
            document = form.save(commit=False)
 
            document.proposal = proposal
 
            document.uploaded_by = request.user
 
            document.save()
 
            return redirect("proposal_detail", proposal.pk)
 
    else:
 
        form = SupportingDocumentCreateForm()
 

	
 
    return render(request, "proposals/document_create.html", {
 
        "proposal": proposal,
 
        "form": form,
symposion/reviews/context_processors.py
Show inline comments
 
from django.contrib.contenttypes.models import ContentType
 

	
 
from symposion.proposals.models import ProposalSection
 

	
 

	
 
def reviews(request):
 
    sections = []
 
    for section in ProposalSection.objects.all():
 
        if request.user.has_perm("reviews.can_review_%s" % section.section.slug):
 
            sections.append(section)
 
    return {
 
        "review_sections": sections,
 
    }
symposion/reviews/management/commands/assign_reviewers.py
Show inline comments
 
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
 
from symposion.proposals.models import ProposalBase
 

	
 

	
 
class Command(BaseCommand):
 

	
 
    def handle(self, *args, **options):
 
        for proposal in ProposalBase.objects.filter(cancelled=0):
 
            print "Creating assignments for %s" % (proposal.title,)
 
            ReviewAssignment.create_assignments(proposal)
symposion/reviews/management/commands/calculate_results.py
Show inline comments
 
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()
symposion/reviews/management/commands/promoteproposals.py
Show inline comments
 
from django.core.management.base import BaseCommand
 
from django.db import connections
 

	
 
from symposion.reviews.models import ProposalResult, promote_proposal
 

	
 

	
 
class Command(BaseCommand):
 

	
 
    def handle(self, *args, **options):
 
        accepted_proposals = ProposalResult.objects.filter(status="accepted")
 
        accepted_proposals = accepted_proposals.order_by("proposal")
 

	
 
        for result in accepted_proposals:
 
            promote_proposal(result.proposal)
 
        connections["default"].cursor().execute("SELECT setval('schedule_session_id_seq', (SELECT max(id) FROM schedule_session))")
 
        connections["default"].cursor().execute(
 
            "SELECT setval('schedule_session_id_seq', (SELECT max(id) FROM schedule_session))")
symposion/reviews/models.py
Show inline comments
...
 
@@ -123,65 +123,66 @@ class Review(models.Model):
 
                    vote=self.vote,
 
                    submitted_at=self.submitted_at,
 
                )
 
            )
 
            if not created:
 
                LatestVote.objects.filter(pk=vote.pk).update(vote=self.vote)
 
                self.proposal.result.update_vote(self.vote, previous=vote.vote)
 
            else:
 
                self.proposal.result.update_vote(self.vote)
 
        super(Review, self).save(**kwargs)
 

	
 
    def delete(self):
 
        model = self.__class__
 
        user_reviews = model._default_manager.filter(
 
            proposal=self.proposal,
 
            user=self.user,
 
        )
 
        try:
 
            # find the latest review
 
            latest = user_reviews.exclude(pk=self.pk).order_by("-submitted_at")[0]
 
        except IndexError:
 
            # did not find a latest which means this must be the only one.
 
            # treat it as a last, but delete the latest vote.
 
            self.proposal.result.update_vote(self.vote, removal=True)
 
            lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user)
 
            lv.delete()
 
        else:
 
            # handle that we've found a latest vote
 
            # check if self is the lastest vote
 
            if self == latest:
 
                # self is the latest review; revert the latest vote to the
 
                # previous vote
 
                previous = user_reviews.filter(submitted_at__lt=self.submitted_at).order_by("-submitted_at")[0]
 
                previous = user_reviews.filter(submitted_at__lt=self.submitted_at)\
 
                    .order_by("-submitted_at")[0]
 
                self.proposal.result.update_vote(self.vote, previous=previous.vote, removal=True)
 
                lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user)
 
                lv.update(
 
                    vote=previous.vote,
 
                    submitted_at=previous.submitted_at,
 
                )
 
            else:
 
                # self is not the latest review so we just need to decrement
 
                # the comment count
 
                self.proposal.result.comment_count = models.F("comment_count") - 1
 
                self.proposal.result.save()
 
        # in all cases we need to delete the review; let's do it!
 
        super(Review, self).delete()
 

	
 
    def css_class(self):
 
        return {
 
            self.VOTES.PLUS_ONE: "plus-one",
 
            self.VOTES.PLUS_ZERO: "plus-zero",
 
            self.VOTES.MINUS_ZERO: "minus-zero",
 
            self.VOTES.MINUS_ONE: "minus-one",
 
        }[self.vote]
 

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

	
 

	
 
class LatestVote(models.Model):
 
    VOTES = VOTES
 

	
 
    proposal = models.ForeignKey("proposals.ProposalBase", related_name="votes")
 
    user = models.ForeignKey(User)
...
 
@@ -274,65 +275,66 @@ class ProposalResult(models.Model):
 
        else:
 
            setattr(self, mapping[vote], models.F(mapping[vote]) + 1)
 
            self.comment_count = models.F("comment_count") + 1
 
        self.save()
 
        model = self.__class__
 
        model._default_manager.filter(pk=self.pk).update(score=ProposalScoreExpression())
 

	
 

	
 
class Comment(models.Model):
 
    proposal = models.ForeignKey("proposals.ProposalBase", related_name="comments")
 
    commenter = models.ForeignKey(User)
 
    text = MarkupField()
 

	
 
    # Or perhaps more accurately, can the user see this comment.
 
    public = models.BooleanField(choices=[
 
        (True, "public"),
 
        (False, "private"),
 
    ])
 
    commented_at = models.DateTimeField(default=datetime.now)
 

	
 

	
 
class NotificationTemplate(models.Model):
 

	
 
    label = models.CharField(max_length=100)
 
    from_address = models.EmailField()
 
    subject = models.CharField(max_length=100)
 
    body = models.TextField()
 

	
 

	
 
class ResultNotification(models.Model):
 

	
 
    proposal = models.ForeignKey("proposals.ProposalBase", related_name="notifications")
 
    template = models.ForeignKey(NotificationTemplate, null=True, blank=True, on_delete=models.SET_NULL)
 
    template = models.ForeignKey(NotificationTemplate, null=True, blank=True,
 
                                 on_delete=models.SET_NULL)
 
    timestamp = models.DateTimeField(default=datetime.now)
 
    to_address = models.EmailField()
 
    from_address = models.EmailField()
 
    subject = models.CharField(max_length=100)
 
    body = models.TextField()
 

	
 
    @property
 
    def email_args(self):
 
        return (self.subject, self.body, self.from_address, [self.to_address])
 

	
 

	
 
def promote_proposal(proposal):
 
    if hasattr(proposal, "presentation") and proposal.presentation:
 
        # already promoted
 
        presentation = proposal.presentation
 
    else:
 
        presentation = Presentation(
 
            title=proposal.title,
 
            description=proposal.description,
 
            abstract=proposal.abstract,
 
            speaker=proposal.speaker,
 
            section=proposal.section,
 
            proposal_base=proposal,
 
        )
 
        presentation.save()
 
        for speaker in proposal.additional_speakers.all():
 
            presentation.additional_speakers.add(speaker)
 
            presentation.save()
 

	
 
    return presentation
 

	
 

	
symposion/reviews/templatetags/review_tags.py
Show inline comments
 
from django import template
 

	
 
from symposion.reviews.models import Review, ReviewAssignment
 
from symposion.reviews.models import ReviewAssignment
 

	
 

	
 
register = template.Library()
 

	
 

	
 
@register.assignment_tag(takes_context=True)
 
def review_assignments(context):
 
    request = context["request"]
 
    assignments = ReviewAssignment.objects.filter(user=request.user)
 
    return assignments
symposion/reviews/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import patterns, url
 

	
 

	
 
urlpatterns = patterns("symposion.reviews.views",
 
    url(r"^section/(?P<section_slug>[\w\-]+)/all/$", "review_section", {"reviewed": "all"}, name="review_section"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/reviewed/$", "review_section", {"reviewed": "reviewed"}, name="user_reviewed"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/not_reviewed/$", "review_section", {"reviewed": "not_reviewed"}, name="user_not_reviewed"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/assignments/$", "review_section", {"assigned": True}, name="review_section_assignments"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/status/$", "review_status", name="review_status"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/status/(?P<key>\w+)/$", "review_status", name="review_status"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/list/(?P<user_pk>\d+)/$", "review_list", name="review_list_user"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/admin/$", "review_admin", name="review_admin"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/admin/accept/$", "review_bulk_accept", name="review_bulk_accept"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/$", "result_notification", name="result_notification"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/prepare/$", "result_notification_prepare", name="result_notification_prepare"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/send/$", "result_notification_send", name="result_notification_send"),
 

	
 
    url(r"^review/(?P<pk>\d+)/$", "review_detail", name="review_detail"),
 

	
 
    url(r"^(?P<pk>\d+)/delete/$", "review_delete", name="review_delete"),
 
    url(r"^assignments/$", "review_assignments", name="review_assignments"),
 
    url(r"^assignment/(?P<pk>\d+)/opt-out/$", "review_assignment_opt_out", name="review_assignment_opt_out"),
 
)
symposion/reviews/utils.py
Show inline comments
 
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()):
 
        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/reviews/views.py
Show inline comments
 
import re
 

	
 
from django.core.mail import send_mass_mail
 
from django.db.models import Q
 
from django.http import HttpResponseBadRequest, HttpResponseNotAllowed
 
from django.shortcuts import render, redirect, get_object_or_404
 
from django.template import Context, Template
 
from django.views.decorators.http import require_POST
 

	
 
from django.contrib.auth.decorators import login_required
 

	
 
from symposion.conf import settings
 
from symposion.proposals.models import ProposalBase, ProposalSection
 
from symposion.teams.models import Team
 
from symposion.utils.mail import send_email
 

	
 
from symposion.reviews.forms import ReviewForm, SpeakerCommentForm
 
from symposion.reviews.forms import BulkPresentationForm
 
from symposion.reviews.models import (
 
    ReviewAssignment, Review, LatestVote, ProposalResult, NotificationTemplate,
 
    ResultNotification
 
)
 

	
 

	
 
def access_not_permitted(request):
 
    return render(request, "reviews/access_not_permitted.html")
 

	
 

	
 
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
 
        obj.plus_zero = obj.result.plus_zero
 
        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
 

	
 

	
 
# Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has yet to review
 
# depending on the link user clicks in dashboard
 
# Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has
 
# yet to review 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")
 
        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":
 
        queryset = queryset.select_related("result").select_subclasses()
 
        reviewed = "all_reviews"
 
    elif reviewed == "reviewed":
 
        queryset = queryset.filter(reviews__user=request.user)
 
        reviewed = "user_reviewed"
 
    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,
 
    }
 
    return render(request, "reviews/review_list.html", ctx)
 

	
 

	
 
@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()
...
 
@@ -286,148 +286,160 @@ def review_detail(request, pk):
 
@require_POST
 
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"),
 
        # proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse sorted by the 'score'
 
        "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0, result__plus_one=0).order_by("result__score"),
 
        # proposals with at least VOTE_THRESHOLD reviews and neither a +1 or a -1, sorted by total votes (lowest first)
 
        "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one=0, result__plus_one=0).order_by("result__vote_count"),
 
        # proposals with at least VOTE_THRESHOLD reviews and both a +1 and -1, sorted by total votes (highest first)
 
        "controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one__gt=0).order_by("-result__vote_count"),
 
        # 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"),
 
        # proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse
 
        # sorted by the 'score'
 
        "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0,
 
                                    result__plus_one=0).order_by("result__score"),
 
        # proposals with at least VOTE_THRESHOLD reviews and neither a +1 or a -1, sorted by total
 
        # votes (lowest first)
 
        "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one=0,
 
                                       result__plus_one=0).order_by("result__vote_count"),
 
        # proposals with at least VOTE_THRESHOLD reviews and both a +1 and -1, sorted by total
 
        # votes (highest first)
 
        "controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD,
 
                                         result__plus_one__gt=0, result__minus_one__gt=0)
 
        .order_by("-result__vote_count"),
 
        # proposals with fewer than VOTE_THRESHOLD reviews
 
        "too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD).order_by("result__vote_count"),
 
        "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))
 
        proposals[status] = list(proposals_generator(request, proposals[status],
 
                                                     check_speaker=not admin))
 

	
 
    if key:
 
        ctx.update({
 
            "key": key,
 
            "proposals": proposals[key],
 
        })
 
    else:
 
        ctx["proposals"] = proposals
 

	
 
    return render(request, "reviews/review_stats.html", ctx)
 

	
 

	
 
@login_required
 
def review_assignments(request):
 
    if not request.user.groups.filter(name="reviewers").exists():
 
        return access_not_permitted(request)
 
    assignments = ReviewAssignment.objects.filter(
 
        user=request.user,
 
        opted_out=False
 
    )
 
    return render(request, "reviews/review_assignment.html", {
 
        "assignments": assignments,
 
    })
 

	
 

	
 
@login_required
 
@require_POST
 
def review_assignment_opt_out(request, pk):
 
    review_assignment = get_object_or_404(ReviewAssignment,
 
        pk=pk,
 
        user=request.user
 
    )
 
    review_assignment = get_object_or_404(
 
        ReviewAssignment, pk=pk, user=request.user)
 
    if not review_assignment.opted_out:
 
        review_assignment.opted_out = True
 
        review_assignment.save()
 
        ReviewAssignment.create_assignments(review_assignment.proposal, origin=ReviewAssignment.AUTO_ASSIGNED_LATER)
 
        ReviewAssignment.create_assignments(
 
            review_assignment.proposal, origin=ReviewAssignment.AUTO_ASSIGNED_LATER)
 
    return redirect("review_assignments")
 

	
 

	
 
@login_required
 
def review_bulk_accept(request, section_slug):
 
    if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
 
        return access_not_permitted(request)
 
    if request.method == "POST":
 
        form = BulkPresentationForm(request.POST)
 
        if form.is_valid():
 
            talk_ids = form.cleaned_data["talk_ids"].split(",")
 
            talks = ProposalBase.objects.filter(id__in=talk_ids).select_related("result")
 
            for talk in talks:
 
                talk.result.status = "accepted"
 
                talk.result.save()
 
            return redirect("review_section", section_slug=section_slug)
 
    else:
 
        form = BulkPresentationForm()
 

	
 
    return render(request, "reviews/review_bulk_accept.html", {
 
        "form": form,
 
    })
 

	
 

	
 
@login_required
 
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()
 
    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,
 
        "proposals": proposals,
 
        "notification_templates": notification_templates,
 
    }
 
    return render(request, "reviews/result_notification.html", ctx)
 

	
 

	
 
@login_required
 
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"):
 
            proposal_pks.append(int(pk))
 
    except ValueError:
 
        return HttpResponseBadRequest()
 
    proposals = ProposalBase.objects.filter(
 
        kind__section__slug=section_slug,
 
        result__status=status,
 
    )
 
    proposals = proposals.filter(pk__in=proposal_pks)
 
    proposals = proposals.select_related("speaker__user", "result")
 
    proposals = proposals.select_subclasses()
symposion/schedule/models.py
Show inline comments
 
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):
...
 
@@ -98,58 +97,59 @@ class Slot(models.Model):
 
        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"]
 

	
 

	
 
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()
 
    abstract = MarkupField()
 
    speaker = models.ForeignKey("speakers.Speaker", related_name="presentations")
 
    additional_speakers = models.ManyToManyField("speakers.Speaker", related_name="copresentations", blank=True)
 
    additional_speakers = models.ManyToManyField("speakers.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 "#%s %s (%s)" % (self.number, self.title, self.speaker)
 

	
 
    class Meta:
 
        ordering = ["slot"]
symposion/schedule/timetable.py
Show inline comments
 
import itertools
 
import operator
 

	
 
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.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)
 
    b.next()
 
    return itertools.izip_longest(a, b)
symposion/schedule/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults 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"),
 
)
symposion/schedule/views.py
Show inline comments
 
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
 

	
 
from django.contrib.auth.decorators import login_required
 

	
 
from symposion.schedule.forms import SlotEditForm
 
from symposion.schedule.models import Schedule, Day, Slot, Presentation
 
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:
symposion/speakers/admin.py
Show inline comments
 
from django.contrib import admin
 

	
 
from symposion.speakers.models import Speaker
 

	
 

	
 
admin.site.register(Speaker,
 
                    list_display=["name", "email", "created"],
 
    search_fields = ["name"],
 
)
...
 
\ No newline at end of file
 
                    search_fields=["name"])
symposion/speakers/fixture_gen.py
Show inline comments
 
from django.contrib.auth.models import User
 

	
 
from fixture_generator import fixture_generator
 

	
 
from symposion.speakers.models import Speaker
 

	
 

	
 
@fixture_generator(Speaker, User)
 
def speakers():
 
    guido = User.objects.create_user("guido", "guido@python.org", "pythonisawesome")
 
    matz = User.objects.create_user("matz", "matz@ruby.org", "pythonsucks")
 
    larry = User.objects.create_user("larryw", "larry@perl.org", "linenoisehere")
 

	
 
    Speaker.objects.create(
 
        user=guido,
 
        name="Guido van Rossum",
 
        biography="I wrote Python, and named it after Monty Python",
 
    )
 
    Speaker.objects.create(
 
        user=matz,
 
        name="Yukihiro Matsumoto",
 
        biography="I wrote Ruby, and named it after the rare gem Ruby, a pun "
 
            "on Perl/pearl.",
 
        biography=("I wrote Ruby, and named it after the rare gem Ruby, a pun "
 
                   "on Perl/pearl."),
 
    )
 
    Speaker.objects.create(
 
        user=larry,
 
        name="Larry Wall",
 
        biography="I wrote Perl, and named it after the Parable of the Pearl",
 
    )
symposion/speakers/management/commands/export_speaker_data.py
Show inline comments
 
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"),
 
                speaker.biography.encode("utf-8"),
 
            ])
symposion/speakers/models.py
Show inline comments
 
import datetime
 

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

	
 
from django.contrib.auth.models import User
 

	
 
from markitup.fields import MarkupField
 

	
 

	
 
class Speaker(models.Model):
 

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

	
 
    user = models.OneToOneField(User, null=True, related_name="speaker_profile")
 
    name = models.CharField(max_length=100, help_text="As you would like it to appear in the conference program.")
 
    biography = MarkupField(blank=True, help_text="A little bit about you. Edit using <a href='http://warpedvisions.org/projects/markdown-cheat-sheet/' target='_blank'>Markdown</a>.")
 
    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):
 
        if self.user:
 
            return self.name
 
        else:
 
            return "?"
 

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

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

	
 
    @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
 
# flake8: noqa
 
from django.conf.urls.defaults import *
 

	
 

	
 
urlpatterns = patterns("symposion.speakers.views",
 
    url(r"^create/$", "speaker_create", name="speaker_create"),
 
    url(r"^create/(\w+)/$", "speaker_create_token", name="speaker_create_token"),
 
    url(r"^edit/(?:(?P<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
...
 
@@ -59,66 +59,66 @@ def speaker_create_staff(request, pk):
 

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

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

	
 

	
 
def speaker_create_token(request, token):
 
    speaker = get_object_or_404(Speaker, invite_token=token)
 
    request.session["pending-token"] = token
 
    if request.user.is_authenticated():
 
        # check for speaker profile
 
        try:
 
            existing_speaker = request.user.speaker_profile
 
        except ObjectDoesNotExist:
 
            pass
 
        else:
 
            del request.session["pending-token"]
 
            additional_speakers = ProposalBase.additional_speakers.through
 
            additional_speakers._default_manager.filter(
 
                speaker=speaker
 
            ).update(
 
                speaker=existing_speaker
 
            )
 
            messages.info(request, "You have been associated with all pending "
 
                "talk proposals")
 
            messages.info(request, ("You have been associated with all pending "
 
                                    "talk proposals"))
 
            return redirect("dashboard")
 
    else:
 
        if not request.user.is_authenticated():
 
            return redirect("account_login")
 
    return redirect("speaker_create")
 

	
 

	
 
@login_required
 
def speaker_edit(request, pk=None):
 
    if pk is None:
 
        try:
 
            speaker = request.user.speaker_profile
 
        except Speaker.DoesNotExist:
 
            return redirect("speaker_create")
 
    else:
 
        if request.user.is_staff:
 
            speaker = get_object_or_404(Speaker, pk=pk)
 
        else:
 
            raise Http404()
 

	
 
    if request.method == "POST":
 
        form = SpeakerForm(request.POST, request.FILES, instance=speaker)
 
        if form.is_valid():
 
            form.save()
 
            messages.success(request, "Speaker profile updated.")
 
            return redirect("dashboard")
 
    else:
 
        form = SpeakerForm(instance=speaker)
 

	
 
    return render(request, "speakers/speaker_edit.html", {
 
        "form": form,
 
    })
symposion/sponsorship/admin.py
Show inline comments
 
from django.contrib import admin
 

	
 
from symposion.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, SponsorBenefit
 
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",
symposion/sponsorship/management/commands/reset_sponsor_benefits.py
Show inline comments
 
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
 
            try:
 
                level = sponsor.level
 
            except SponsorLevel.DoesNotExist:
 
                pass
 
            if level:
 
                for benefit_level in level.benefit_levels.all():
 
                    # 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,
 
                    # and won't raise a validation error until it's next
 
                    # edited.
symposion/sponsorship/models.py
Show inline comments
...
 
@@ -6,180 +6,185 @@ from django.db import models
 
from django.db.models.signals import post_init, post_save
 
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
 

	
 

	
 
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):
 
        return self.name
 

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

	
 

	
 
class Sponsor(models.Model):
 

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

	
 
    name = models.CharField(_("Sponsor Name"), max_length=100)
 
    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)
 
    sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True,
 
                                     editable=False)
 

	
 
    objects = SponsorManager()
 

	
 
    def __unicode__(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")
 

	
 
    @property
 
    def website_logo(self):
 
        if self.sponsor_logo is None:
 
            benefits = self.sponsor_benefits.filter(benefit__type="weblogo", upload__isnull=False)[:1]
 
            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:
 
            pass
 

	
 
        allowed_benefits = []
 
        if level:
 
            for benefit_level in level.benefit_levels.all():
 
                # Create all needed benefits if they don't exist already
 
                sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
 
                    sponsor=self, benefit=benefit_level.benefit)
 

	
 
                # 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,
 
                # 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="")
 
        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"),
 
    ("file", "File"),
 
    ("weblogo", "Web Logo"),
 
    ("simple", "Simple")
 
]
 

	
 

	
 
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")
 
    type = models.CharField(_("type"), choices=BENEFIT_TYPE_CHOICES, max_length=10,
 
                            default="simple")
 

	
 
    def __unicode__(self):
 
        return self.name
 

	
 

	
 
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)
 

	
 

	
 
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
symposion/sponsorship/templatetags/sponsorship_tags.py
Show inline comments
 
from django import template
 

	
 
from symposion.conference.models import current_conference
 
from symposion.sponsorship.models import Sponsor, SponsorLevel
 

	
 

	
 
register = template.Library()
 

	
 

	
 
class SponsorsNode(template.Node):
 

	
 
    @classmethod
 
    def handle_token(cls, parser, token):
 
        bits = token.split_contents()
 
        if len(bits) == 3 and bits[1] == "as":
 
            return cls(bits[2])
 
        elif len(bits) == 4 and bits[2] == "as":
 
            return cls(bits[3], bits[1])
 
        else:
 
            raise template.TemplateSyntaxError("%r takes 'as var' or 'level as var'" % bits[0])
 

	
 
    def __init__(self, context_var, level=None):
 
        if level:
 
            self.level = template.Variable(level)
 
        else:
 
            self.level = None
 
        self.context_var = context_var
 

	
 
    def render(self, context):
 
        conference = current_conference()
 
        if self.level:
 
            level = self.level.resolve(context)
 
            queryset = Sponsor.objects.filter(level__conference = conference, level__name__iexact = level, active = True).order_by("added")
 
            queryset = Sponsor.objects.filter(
 
                level__conference=conference, level__name__iexact=level, active=True)\
 
                .order_by("added")
 
        else:
 
            queryset = Sponsor.objects.filter(level__conference = conference, active = True).order_by("level__order", "added")
 
            queryset = Sponsor.objects.filter(level__conference=conference, active=True)\
 
                .order_by("level__order", "added")
 
        context[self.context_var] = queryset
 
        return u""
 

	
 

	
 
class SponsorLevelNode(template.Node):
 

	
 
    @classmethod
 
    def handle_token(cls, parser, token):
 
        bits = token.split_contents()
 
        if len(bits) == 3 and bits[1] == "as":
 
            return cls(bits[2])
 
        else:
 
            raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
 

	
 
    def __init__(self, context_var):
 
        self.context_var = context_var
 

	
 
    def render(self, context):
 
        conference = current_conference()
 
        context[self.context_var] = SponsorLevel.objects.filter(conference=conference)
 
        return u""
 

	
 

	
 
@register.tag
 
def sponsors(parser, token):
 
    """
 
    {% sponsors as all_sponsors %}
 
    or
 
    {% sponsors "gold" as gold_sponsors %}
 
    """
 
    return SponsorsNode.handle_token(parser, token)
 

	
 

	
 
@register.tag
 
def sponsor_levels(parser, token):
 
    """
 
    {% sponsor_levels as levels %}
 
    """
 
    return SponsorLevelNode.handle_token(parser, token)
 
    
...
 
\ No newline at end of file
symposion/sponsorship/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import patterns, url
 
from django.views.generic.simple import direct_to_template
 

	
 

	
 
urlpatterns = patterns("symposion.sponsorship.views",
 
    url(r"^$", direct_to_template, {"template": "sponsorship/list.html"}, name="sponsor_list"),
 
    url(r"^apply/$", "sponsor_apply", name="sponsor_apply"),
 
    url(r"^add/$", "sponsor_add", name="sponsor_add"),
 
    url(r"^(?P<pk>\d+)/$", "sponsor_detail", name="sponsor_detail"),
 
)
symposion/sponsorship/views.py
Show inline comments
 
from django.http import Http404
 
from django.shortcuts import render_to_response, redirect, get_object_or_404
 
from django.template import RequestContext
 

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

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

	
 

	
 
@login_required
 
def sponsor_apply(request):
 
    if request.method == "POST":
 
        form = SponsorApplicationForm(request.POST, user=request.user)
 
        if form.is_valid():
 
            sponsor = form.save()
 
            return redirect("sponsor_detail", pk=sponsor.pk)
 
    else:
 
        form = SponsorApplicationForm(user=request.user)
 

	
 
    return render_to_response("sponsorship/apply.html", {
 
        "form": form,
 
    }, context_instance=RequestContext(request))
 

	
 

	
 
@login_required
 
def sponsor_add(request):
 
    if not request.user.is_staff:
 
        raise Http404()
 

	
 
    if request.method == "POST":
 
        form = SponsorApplicationForm(request.POST, user=request.user)
 
        if form.is_valid():
 
            sponsor = form.save(commit=False)
 
            sponsor.active = True
 
            sponsor.save()
 
            return redirect("sponsor_detail", pk=sponsor.pk)
 
    else:
 
        form = SponsorApplicationForm(user=request.user)
symposion/teams/admin.py
Show inline comments
 
from django.contrib import admin
 

	
 
import reversion
 

	
 
from symposion.teams.models import Team, Membership
 

	
 
admin.site.register(Team,
 
    prepopulated_fields={"slug": ("name",)},
 
)
 
                    prepopulated_fields={"slug": ("name",)})
 

	
 

	
 
class MembershipAdmin(reversion.VersionAdmin):
 
    list_display = ["team", "user", "state"]
 
    list_filter = ["team"]
 
    search_fields = ["user__username"]
 

	
 
admin.site.register(Membership, MembershipAdmin)
symposion/teams/forms.py
Show inline comments
 
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")
 
    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)))
 
            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
 

	
 
    def invite(self):
 
        if self.state is None:
 
            Membership.objects.create(team=self.team, user=self.user, state="invited")
 
        elif self.state == "applied":
 
            # if they applied we shortcut invitation process
 
            membership = Membership.objects.filter(team=self.team, user=self.user)
 
            membership.update(state="member")
symposion/teams/models.py
Show inline comments
 
import datetime
 

	
 
from django.db import models
 

	
 
import reversion
 

	
 
from django.contrib.auth.models import Permission, User
 

	
 

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

	
 

	
 
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")
 
    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):
 
        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"),
symposion/teams/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import *
 

	
 

	
 
urlpatterns = patterns("symposion.teams.views",
 
    # team specific
 
    url(r"^(?P<slug>[\w\-]+)/$", "team_detail", name="team_detail"),
 
    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"),
 
    url(r"^accept/(?P<pk>\d+)/$", "team_accept", name="team_accept"),
 
    url(r"^reject/(?P<pk>\d+)/$", "team_reject", name="team_reject"),
 
)
symposion/teams/views.py
Show inline comments
 
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
 
# 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:
 
            return True
 
    return False
 

	
 

	
 
## views
 
# views
 

	
 

	
 
@login_required
 
def team_detail(request, slug):
 
    team = get_object_or_404(Team, slug=slug)
 
    state = team.get_state_for_user(request.user)
 
    if team.access == "invitation" and state is None and not request.user.is_staff:
 
        raise Http404()
 

	
 
    if can_invite(team, request.user):
 
        if request.method == "POST":
 
            form = TeamInvitationForm(request.POST, team=team)
 
            if form.is_valid():
 
                form.invite()
 
                send_email([form.user.email], "teams_user_invited", context={"team": team})
 
                messages.success(request, "Invitation created.")
 
                return redirect("team_detail", slug=slug)
 
        else:
 
            form = TeamInvitationForm(team=team)
 
    else:
 
        form = None
 

	
 
    return render(request, "teams/team_detail.html", {
 
        "team": team,
 
        "state": state,
 
        "invite_form": form,
 
        "can_join": can_join(team, request.user),
 
        "can_leave": can_leave(team, request.user),
 
        "can_apply": can_apply(team, request.user),
 
    })
 

	
 

	
0 comments (0 inline, 0 general)