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
66 files changed with 611 insertions and 604 deletions:
0 comments (0 inline, 0 general)
symposion/boxes/authorization.py
Show inline comments
...
 
@@ -14,7 +14,7 @@ def default_can_edit(request, *args, **kwargs):
 
def load_can_edit():
 
    import_path = getattr(settings, "BOXES_CAN_EDIT_CALLABLE", None)
 
    
 

 
    if import_path is None:
 
        return default_can_edit
 
    
 

 
    return load_path_attr(import_path)
symposion/boxes/forms.py
Show inline comments
...
 
@@ -5,5 +5,5 @@ from symposion.boxes.models import Box
 

	
 
class BoxForm(forms.ModelForm):
 
    
 

 
    class Meta:
 
        model = Box
symposion/boxes/models.py
Show inline comments
 
import datetime
 

	
 
from django.db import models
 

	
...
 
@@ -11,14 +9,14 @@ 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"
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
...
 
@@ -17,13 +12,13 @@ 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)
...
 
@@ -32,5 +27,5 @@ def box(context, label, show_edit=True, *args, **kwargs):
 
        form = None
 
        form_action = None
 
    
 

 
    return {
 
        "request": request,
symposion/boxes/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import url, patterns
 

	
...
 
@@ -4,3 +5,3 @@ from django.conf.urls.defaults import url, patterns
 
urlpatterns = patterns("symposion.boxes.views",
 
    url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"),
 
)
...
 
\ No newline at end of file
 
)
symposion/boxes/views.py
Show inline comments
...
 
@@ -8,5 +8,6 @@ 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 = {}
...
 
@@ -21,15 +22,15 @@ def get_auth_vars(request):
 
@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)
 

	
symposion/cms/admin.py
Show inline comments
...
 
@@ -5,4 +5,5 @@ import reversion
 
from .models import Page
 

	
 

	
 
class PageAdmin(reversion.VersionAdmin):
 

	
symposion/cms/forms.py
Show inline comments
...
 
@@ -7,5 +7,5 @@ from .models import Page
 

	
 
class PageForm(forms.ModelForm):
 
    
 

 
    class Meta:
 
        model = Page
...
 
@@ -18,4 +18,4 @@ class PageForm(forms.ModelForm):
 

	
 
class FileUploadForm(forms.Form):
 
    
 

 
    file = forms.FileField()
symposion/cms/managers.py
Show inline comments
...
 
@@ -3,6 +3,7 @@ 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()
symposion/cms/models.py
Show inline comments
...
 
@@ -19,10 +19,10 @@ from .managers import PublishedPageManager
 

	
 
class Page(models.Model):
 
    
 

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

 
    title = models.CharField(max_length=100)
 
    path = models.CharField(max_length=100, unique=True)
...
 
@@ -33,26 +33,27 @@ class Page(models.Model):
 
    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 /")]})
 

	
 

	
...
 
@@ -65,8 +66,8 @@ def generate_filename(instance, 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
 

	
symposion/cms/views.py
Show inline comments
...
 
@@ -24,12 +24,12 @@ def can_upload(user):
 

	
 
def page(request, path):
 
    
 

 
    try:
 
        page = Page.published.get(path=path)
 
    except Page.DoesNotExist:
 
        page = None
 
    
 

 
    editable = can_edit(page, request.user)
 
    
 

 
    if page is None:
 
        if editable:
...
 
@@ -37,5 +37,5 @@ def page(request, path):
 
        else:
 
            raise Http404
 
    
 

 
    return render(request, "cms/page_detail.html", {
 
        "page": page,
...
 
@@ -46,13 +46,13 @@ def page(request, path):
 
@login_required
 
def page_edit(request, path):
 
    
 

 
    try:
 
        page = Page.published.get(path=path)
 
    except Page.DoesNotExist:
 
        page = None
 
    
 

 
    if not can_edit(page, request.user):
 
        raise Http404
 
    
 

 
    if request.method == "POST":
 
        form = PageForm(request.POST, instance=page)
...
 
@@ -66,5 +66,5 @@ def page_edit(request, path):
 
    else:
 
        form = PageForm(instance=page, initial={"path": path})
 
    
 

 
    return render(request, "cms/page_edit.html", {
 
        "path": path,
...
 
@@ -76,5 +76,5 @@ def file_index(request):
 
    if not can_upload(request.user):
 
        raise Http404
 
    
 

 
    ctx = {
 
        "files": File.objects.all(),
...
 
@@ -86,5 +86,5 @@ def file_create(request):
 
    if not can_upload(request.user):
 
        raise Http404
 
    
 

 
    if request.method == "POST":
 
        form = FileUploadForm(request.POST, request.FILES)
...
 
@@ -98,5 +98,5 @@ def file_create(request):
 
    else:
 
        form = FileUploadForm()
 
    
 

 
    ctx = {
 
        "form": form,
...
 
@@ -107,5 +107,5 @@ def file_create(request):
 
def file_download(request, pk, *args):
 
    file = get_object_or_404(File, pk=pk)
 
    
 

 
    if getattr(settings, "USE_X_ACCEL_REDIRECT", False):
 
        response = HttpResponse()
...
 
@@ -116,5 +116,5 @@ def file_download(request, pk, *args):
 
    else:
 
        response = static.serve(request, file.file.name, document_root=settings.MEDIA_ROOT)
 
    
 

 
    return response
 

	
...
 
@@ -123,5 +123,5 @@ def file_delete(request, pk):
 
    if not can_upload(request.user):
 
        raise Http404
 
    
 

 
    file = get_object_or_404(File, pk=pk)
 
    if request.method == "POST":
symposion/conf.py
Show inline comments
 
from django.conf import settings
 

	
 
from appconf import AppConf
 

	
 

	
 
class SymposionAppConf(AppConf):
 
    
 

 
    VOTE_THRESHOLD = 3
symposion/conference/admin.py
Show inline comments
...
 
@@ -7,5 +7,5 @@ admin.site.register(Conference, list_display=("title", "start_date", "end_date")
 
admin.site.register(
 
    Section,
 
    prepopulated_fields = {"slug": ("name",)},
 
    list_display = ("name", "conference", "start_date", "end_date")
 
    prepopulated_fields={"slug": ("name",)},
 
    list_display=("name", "conference", "start_date", "end_date")
 
)
symposion/conference/models.py
Show inline comments
...
 
@@ -12,22 +12,22 @@ 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):
 
        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
...
 
@@ -37,5 +37,5 @@ class Conference(models.Model):
 
        except KeyError:
 
            pass
 
    
 

 
    class Meta(object):
 
        verbose_name = _("conference")
...
 
@@ -49,7 +49,7 @@ class Section(models.Model):
 
    scheduling process.
 
    """
 
    
 

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

 
    name = models.CharField(_("name"), max_length=100)
 
    slug = models.SlugField()
...
 
@@ -58,8 +58,8 @@ class Section(models.Model):
 
    start_date = models.DateField(_("start date"), null=True, blank=True)
 
    end_date = models.DateField(_("end date"), null=True, blank=True)
 
    
 

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

 
    class Meta(object):
 
        verbose_name = _("section")
symposion/conference/urls.py
Show inline comments
 
from django.conf.urls.defaults import *
 
# flake8: noqa
 
from django.conf.urls.defaults import patterns, url
 

	
 

	
symposion/conference/views.py
Show inline comments
...
 
@@ -8,8 +8,8 @@ from django.contrib.auth.models import User
 
@login_required
 
def user_list(request):
 
    
 

 
    if not request.user.is_staff:
 
        raise Http404()
 
    
 

 
    return render(request, "conference/user_list.html", {
 
        "users": User.objects.all(),
symposion/forms.py
Show inline comments
...
 
@@ -5,5 +5,5 @@ import account.forms
 

	
 
class SignupForm(account.forms.SignupForm):
 
    
 

 
    first_name = forms.CharField()
 
    last_name = forms.CharField()
...
 
@@ -21,5 +21,5 @@ class SignupForm(account.forms.SignupForm):
 
            "password_confirm"
 
        ]
 
    
 

 
    def clean_email_confirm(self):
 
        email = self.cleaned_data.get("email")
...
 
@@ -27,4 +27,5 @@ class SignupForm(account.forms.SignupForm):
 
        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
 

	
...
 
@@ -6,8 +5,8 @@ import markdown
 

	
 
def parse(text):
 
    
 

 
    # First run through the Markdown parser
 
    text = markdown.markdown(text, extensions=["extra"], safe_mode=False)
 
    
 

 
    # Sanitize using html5lib
 
    bits = []
symposion/proposals/actions.py
Show inline comments
...
 
@@ -4,7 +4,6 @@ from django.http import HttpResponse
 

	
 

	
 
def export_as_csv_action(
 
    description="Export selected objects as CSV file",
 
    fields=None, exclude=None, header=True):
 
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
...
 
@@ -25,10 +24,12 @@ def export_as_csv_action(
 
            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
symposion/proposals/forms.py
Show inline comments
...
 
@@ -10,13 +10,13 @@ from symposion.proposals.models import SupportingDocument
 

	
 
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"]
...
 
@@ -33,5 +33,5 @@ class AddSpeakerForm(forms.Form):
 

	
 
class SupportingDocumentCreateForm(forms.ModelForm):
 
    
 

 
    class Meta:
 
        model = SupportingDocument
symposion/proposals/managers.py
Show inline comments
 
deleted file
symposion/proposals/models.py
Show inline comments
...
 
@@ -22,5 +22,5 @@ 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
...
 
@@ -28,12 +28,12 @@ class ProposalSection(models.Model):
 
      * 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):
...
 
@@ -44,5 +44,5 @@ class ProposalSection(models.Model):
 
            Q(closed=False) | Q(closed=None),
 
        )
 
    
 

 
    def is_available(self):
 
        if self.closed:
...
 
@@ -54,5 +54,5 @@ class ProposalSection(models.Model):
 
            return False
 
        return True
 
    
 

 
    def __unicode__(self):
 
        return self.section.name
...
 
@@ -62,14 +62,14 @@ 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
...
 
@@ -77,22 +77,28 @@ class ProposalKind(models.Model):
 

	
 
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(
...
 
@@ -101,27 +107,30 @@ class ProposalBase(models.Model):
 
    )
 
    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 {
...
 
@@ -136,9 +145,9 @@ 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")),
...
 
@@ -146,9 +155,9 @@ class AdditionalSpeaker(models.Model):
 
        (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"
...
 
@@ -163,13 +172,14 @@ def uuid_filename(instance, 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
...
 
@@ -8,5 +8,5 @@ register = template.Library()
 

	
 
class AssociatedProposalsNode(template.Node):
 
    
 

 
    @classmethod
 
    def handle_token(cls, parser, token):
...
 
@@ -16,8 +16,8 @@ class AssociatedProposalsNode(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"]
...
 
@@ -33,5 +33,5 @@ class AssociatedProposalsNode(template.Node):
 

	
 
class PendingProposalsNode(template.Node):
 
    
 

 
    @classmethod
 
    def handle_token(cls, parser, token):
...
 
@@ -41,8 +41,8 @@ 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"]
...
 
@@ -71,3 +71,2 @@ def associated_proposals(parser, token):
 
    """
 
    return AssociatedProposalsNode.handle_token(parser, token)
 

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

	
...
 
@@ -12,5 +13,5 @@ urlpatterns = patterns("symposion.proposals.views",
 
    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"),
symposion/proposals/views.py
Show inline comments
...
 
@@ -38,10 +38,10 @@ def proposal_submit(request):
 
        except ObjectDoesNotExist:
 
            return redirect("dashboard")
 
    
 

 
    kinds = []
 
    for proposal_section in ProposalSection.available():
 
        for kind in proposal_section.section.proposal_kinds.all():
 
            kinds.append(kind)
 
    
 

 
    return render(request, "proposals/proposal_submit.html", {
 
        "kinds": kinds,
...
 
@@ -50,7 +50,7 @@ def proposal_submit(request):
 

	
 
def proposal_submit_kind(request, kind_slug):
 
    
 

 
    kind = get_object_or_404(ProposalKind, slug=kind_slug)
 
    
 

 
    if not request.user.is_authenticated():
 
        return redirect("home")  # @@@ unauth'd speaker info page?
...
 
@@ -60,10 +60,10 @@ def proposal_submit_kind(request, kind_slug):
 
        except ObjectDoesNotExist:
 
            return redirect("dashboard")
 
    
 

 
    if not kind.section.proposalsection.is_available():
 
        return redirect("proposal_submit")
 
    
 

 
    form_class = get_form(settings.PROPOSAL_FORMS[kind_slug])
 
    
 

 
    if request.method == "POST":
 
        form = form_class(request.POST)
...
 
@@ -80,5 +80,5 @@ def proposal_submit_kind(request, kind_slug):
 
    else:
 
        form = form_class()
 
    
 

 
    return render(request, "proposals/proposal_submit_kind.html", {
 
        "kind": kind,
...
 
@@ -92,8 +92,8 @@ def proposal_speaker_manage(request, pk):
 
    proposal = get_object_or_404(queryset, pk=pk)
 
    proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
 
    
 

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

 
    if request.method == "POST":
 
        add_speaker_form = AddSpeakerForm(request.POST, proposal=proposal)
...
 
@@ -102,5 +102,5 @@ def proposal_speaker_manage(request, pk):
 
                "proposal": proposal,
 
            }
 
            
 

 
            def create_speaker_token(email_address):
 
                # create token and look for an existing speaker to prevent
...
 
@@ -136,5 +136,5 @@ def proposal_speaker_manage(request, pk):
 
                    send_email(
 
                        [email_address], "speaker_no_profile",
 
                        context = message_ctx
 
                        context=message_ctx
 
                    )
 
                else:
...
 
@@ -142,5 +142,5 @@ def proposal_speaker_manage(request, pk):
 
                    send_email(
 
                        [email_address], "speaker_addition",
 
                        context = message_ctx
 
                        context=message_ctx
 
                    )
 
            else:
...
 
@@ -151,7 +151,8 @@ def proposal_speaker_manage(request, pk):
 
                send_email(
 
                    [email_address], "speaker_invite",
 
                    context = message_ctx
 
                    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)
...
 
@@ -174,5 +175,5 @@ def proposal_edit(request, pk):
 
    if request.user != proposal.speaker.user:
 
        raise Http404()
 
    
 

 
    if not proposal.can_edit():
 
        ctx = {
...
 
@@ -181,5 +182,5 @@ def proposal_edit(request, pk):
 
        }
 
        return render(request, "proposals/proposal_error.html", ctx)
 
    
 

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

	
...
 
@@ -207,5 +208,5 @@ def proposal_edit(request, pk):
 
    else:
 
        form = form_class(instance=proposal)
 
    
 

 
    return render(request, "proposals/proposal_edit.html", {
 
        "proposal": proposal,
...
 
@@ -219,8 +220,8 @@ def proposal_detail(request, pk):
 
    proposal = get_object_or_404(queryset, pk=pk)
 
    proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
 
    
 

 
    if request.user not in [p.user for p in proposal.speakers()]:
 
        raise Http404()
 
    
 

 
    if "symposion.reviews" in settings.INSTALLED_APPS:
 
        from symposion.reviews.forms import SpeakerCommentForm
...
 
@@ -229,10 +230,10 @@ def proposal_detail(request, pk):
 
            message_form = SpeakerCommentForm(request.POST)
 
            if message_form.is_valid():
 
                
 

 
                message = message_form.save(commit=False)
 
                message.user = request.user
 
                message.proposal = proposal
 
                message.save()
 
                
 

 
                ProposalMessage = SpeakerCommentForm.Meta.model
 
                reviewers = User.objects.filter(
...
 
@@ -243,5 +244,5 @@ def proposal_detail(request, pk):
 
                    ).distinct().values_list("user", flat=True)
 
                )
 
                
 

 
                for reviewer in reviewers:
 
                    ctx = {
...
 
@@ -254,5 +255,5 @@ def proposal_detail(request, pk):
 
                        context=ctx
 
                    )
 
                
 

 
                return redirect(request.path)
 
        else:
...
 
@@ -260,5 +261,5 @@ def proposal_detail(request, pk):
 
    else:
 
        message_form = None
 
    
 

 
    return render(request, "proposals/proposal_detail.html", {
 
        "proposal": proposal,
...
 
@@ -272,5 +273,5 @@ def proposal_cancel(request, pk):
 
    proposal = get_object_or_404(queryset, pk=pk)
 
    proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
 
    
 

 
    if proposal.speaker.user != request.user:
 
        return HttpResponseForbidden()
...
 
@@ -282,5 +283,5 @@ 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,
...
 
@@ -312,5 +313,6 @@ def proposal_leave(request, pk):
 
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
...
 
@@ -325,5 +327,6 @@ def proposal_pending_join(request, pk):
 
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
...
 
@@ -340,8 +343,8 @@ def document_create(request, proposal_pk):
 
    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)
...
 
@@ -354,5 +357,5 @@ def document_create(request, proposal_pk):
 
    else:
 
        form = SupportingDocumentCreateForm()
 
        
 

 
    return render(request, "proposals/document_create.html", {
 
        "proposal": proposal,
...
 
@@ -379,7 +382,7 @@ def document_delete(request, pk):
 
    document = get_object_or_404(SupportingDocument, pk=pk, uploaded_by=request.user)
 
    proposal_pk = document.proposal.pk
 
    
 

 
    if request.method == "POST":
 
        document.delete()
 
    
 

 
    return redirect("proposal_detail", proposal_pk)
symposion/reviews/context_processors.py
Show inline comments
 
from django.contrib.contenttypes.models import ContentType
 

	
 
from symposion.proposals.models import ProposalSection
 

	
symposion/reviews/forms.py
Show inline comments
...
 
@@ -10,11 +10,11 @@ class ReviewForm(forms.ModelForm):
 
        model = Review
 
        fields = ["vote", "comment"]
 
        widgets = { "comment": MarkItUpWidget() }
 
    
 
        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
 
            widget=forms.RadioSelect(),
 
            choices=VOTES.CHOICES
 
        )
 

	
...
 
@@ -24,5 +24,5 @@ class ReviewCommentForm(forms.ModelForm):
 
        model = Comment
 
        fields = ["text"]
 
        widgets = { "text": MarkItUpWidget() }
 
        widgets = {"text": MarkItUpWidget()}
 

	
 

	
...
 
@@ -31,5 +31,5 @@ class SpeakerCommentForm(forms.ModelForm):
 
        model = ProposalMessage
 
        fields = ["message"]
 
        widgets = { "message": MarkItUpWidget() }
 
        widgets = {"message": MarkItUpWidget()}
 

	
 

	
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
 

	
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/create_review_permissions.py
Show inline comments
...
 
@@ -8,5 +8,5 @@ from symposion.proposals.models import ProposalSection
 

	
 
class Command(BaseCommand):
 
    
 

 
    def handle(self, *args, **options):
 
        ct, created = ContentType.objects.get_or_create(
...
 
@@ -15,5 +15,5 @@ class Command(BaseCommand):
 
            defaults={"name": "reviews"}
 
        )
 
        
 

 
        for ps in ProposalSection.objects.all():
 
            for action in ["review", "manage"]:
symposion/reviews/management/commands/promoteproposals.py
Show inline comments
...
 
@@ -6,10 +6,11 @@ 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
...
 
@@ -16,9 +16,9 @@ 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
...
 
@@ -30,5 +30,5 @@ class Votes(object):
 
    MINUS_ZERO = u"−0"
 
    MINUS_ONE = u"−1"
 
    
 

 
    CHOICES = [
 
        (PLUS_ONE, u"+1 — Good proposal and I will argue for it to be accepted."),
...
 
@@ -46,5 +46,5 @@ class ReviewAssignment(models.Model):
 

	
 
    NUM_REVIEWERS = 3
 
    
 

 
    ORIGIN_CHOICES = [
 
        (AUTO_ASSIGNED_INITIAL, "auto-assigned, initial"),
...
 
@@ -52,13 +52,13 @@ class ReviewAssignment(models.Model):
 
        (AUTO_ASSIGNED_LATER, "auto-assigned, later"),
 
    ]
 
    
 

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

 
    origin = models.IntegerField(choices=ORIGIN_CHOICES)
 
    
 

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

 
    @classmethod
 
    def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL):
...
 
@@ -95,5 +95,5 @@ class ProposalMessage(models.Model):
 
    proposal = models.ForeignKey("proposals.ProposalBase", related_name="messages")
 
    user = models.ForeignKey(User)
 
    
 

 
    message = MarkupField()
 
    submitted_at = models.DateTimeField(default=datetime.now, editable=False)
...
 
@@ -105,8 +105,8 @@ class ProposalMessage(models.Model):
 
class Review(models.Model):
 
    VOTES = VOTES
 
    
 

 
    proposal = models.ForeignKey("proposals.ProposalBase", related_name="reviews")
 
    user = models.ForeignKey(User)
 
    
 

 
    # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel
 
    # like some complicated encoding system.
...
 
@@ -114,13 +114,13 @@ class Review(models.Model):
 
    comment = MarkupField()
 
    submitted_at = models.DateTimeField(default=datetime.now, editable=False)
 
    
 

 
    def save(self, **kwargs):
 
        if self.vote:
 
            vote, created = LatestVote.objects.get_or_create(
 
                proposal = self.proposal,
 
                user = self.user,
 
                defaults = dict(
 
                    vote = self.vote,
 
                    submitted_at = self.submitted_at,
 
                proposal=self.proposal,
 
                user=self.user,
 
                defaults=dict(
 
                    vote=self.vote,
 
                    submitted_at=self.submitted_at,
 
                )
 
            )
...
 
@@ -131,5 +131,5 @@ class Review(models.Model):
 
                self.proposal.result.update_vote(self.vote)
 
        super(Review, self).save(**kwargs)
 
    
 

 
    def delete(self):
 
        model = self.__class__
...
 
@@ -153,5 +153,6 @@ class Review(models.Model):
 
                # 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)
...
 
@@ -167,5 +168,5 @@ class Review(models.Model):
 
        # in all cases we need to delete the review; let's do it!
 
        super(Review, self).delete()
 
    
 

 
    def css_class(self):
 
        return {
...
 
@@ -175,5 +176,5 @@ class Review(models.Model):
 
            self.VOTES.MINUS_ONE: "minus-one",
 
        }[self.vote]
 
    
 

 
    @property
 
    def section(self):
...
 
@@ -183,16 +184,16 @@ class Review(models.Model):
 
class LatestVote(models.Model):
 
    VOTES = VOTES
 
    
 

 
    proposal = models.ForeignKey("proposals.ProposalBase", related_name="votes")
 
    user = models.ForeignKey(User)
 
    
 

 
    # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel
 
    # like some complicated encoding system.
 
    vote = models.CharField(max_length=2, choices=VOTES.CHOICES)
 
    submitted_at = models.DateTimeField(default=datetime.now, editable=False)
 
    
 

 
    class Meta:
 
        unique_together = [("proposal", "user")]
 
    
 

 
    def css_class(self):
 
        return {
...
 
@@ -224,5 +225,5 @@ class ProposalResult(models.Model):
 
        ("standby", "standby"),
 
    ], default="undecided")
 
    
 

 
    @classmethod
 
    def full_calculate(cls):
...
 
@@ -232,22 +233,22 @@ class ProposalResult(models.Model):
 
            result.vote_count = LatestVote.objects.filter(proposal=proposal).count()
 
            result.plus_one = LatestVote.objects.filter(
 
                proposal = proposal,
 
                vote = VOTES.PLUS_ONE
 
                proposal=proposal,
 
                vote=VOTES.PLUS_ONE
 
            ).count()
 
            result.plus_zero = LatestVote.objects.filter(
 
                proposal = proposal,
 
                vote = VOTES.PLUS_ZERO
 
                proposal=proposal,
 
                vote=VOTES.PLUS_ZERO
 
            ).count()
 
            result.minus_zero = LatestVote.objects.filter(
 
                proposal = proposal,
 
                vote = VOTES.MINUS_ZERO
 
                proposal=proposal,
 
                vote=VOTES.MINUS_ZERO
 
            ).count()
 
            result.minus_one = LatestVote.objects.filter(
 
                proposal = proposal,
 
                vote = VOTES.MINUS_ONE
 
                proposal=proposal,
 
                vote=VOTES.MINUS_ONE
 
            ).count()
 
            result.save()
 
            cls._default_manager.filter(pk=result.pk).update(score=ProposalScoreExpression())
 
    
 

 
    def update_vote(self, vote, previous=None, removal=False):
 
        mapping = {
...
 
@@ -284,5 +285,5 @@ class Comment(models.Model):
 
    commenter = models.ForeignKey(User)
 
    text = MarkupField()
 
    
 

 
    # Or perhaps more accurately, can the user see this comment.
 
    public = models.BooleanField(choices=[
...
 
@@ -294,5 +295,5 @@ class Comment(models.Model):
 

	
 
class NotificationTemplate(models.Model):
 
    
 

 
    label = models.CharField(max_length=100)
 
    from_address = models.EmailField()
...
 
@@ -302,7 +303,8 @@ class NotificationTemplate(models.Model):
 

	
 
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()
...
 
@@ -310,5 +312,5 @@ class ResultNotification(models.Model):
 
    subject = models.CharField(max_length=100)
 
    body = models.TextField()
 
    
 

 
    @property
 
    def email_args(self):
...
 
@@ -322,10 +324,10 @@ def promote_proposal(proposal):
 
    else:
 
        presentation = Presentation(
 
            title = proposal.title,
 
            description = proposal.description,
 
            abstract = proposal.abstract,
 
            speaker = proposal.speaker,
 
            section = proposal.section,
 
            proposal_base = proposal,
 
            title=proposal.title,
 
            description=proposal.description,
 
            abstract=proposal.abstract,
 
            speaker=proposal.speaker,
 
            section=proposal.section,
 
            proposal_base=proposal,
 
        )
 
        presentation.save()
...
 
@@ -333,5 +335,5 @@ def promote_proposal(proposal):
 
            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
 

	
 

	
symposion/reviews/tests.py
Show inline comments
...
 
@@ -16,8 +16,8 @@ class login(object):
 
            "login with username=%r, password=%r failed" % (user, password)
 
        )
 
    
 

 
    def __enter__(self):
 
        pass
 
    
 

 
    def __exit__(self, *args):
 
        self.testcase.client.logout()
...
 
@@ -26,32 +26,32 @@ class login(object):
 
class ReviewTests(TestCase):
 
    fixtures = ["proposals"]
 
    
 

 
    def get(self, url_name, *args, **kwargs):
 
        return self.client.get(reverse(url_name, args=args, kwargs=kwargs))
 
    
 

 
    def post(self, url_name, *args, **kwargs):
 
        data = kwargs.pop("data")
 
        return self.client.post(reverse(url_name, args=args, kwargs=kwargs), data)
 
    
 

 
    def login(self, user, password):
 
        return login(self, user, password)
 
    
 

 
    def test_detail_perms(self):
 
        guidos_proposal = Proposal.objects.all()[0]
 
        response = self.get("review_detail", pk=guidos_proposal.pk)
 
        
 

 
        # Not logged in
 
        self.assertEqual(response.status_code, 302)
 
        
 

 
        with self.login("guido", "pythonisawesome"):
 
            response = self.get("review_detail", pk=guidos_proposal.pk)
 
            # Guido can see his own proposal.
 
            self.assertEqual(response.status_code, 200)
 
        
 

 
        with self.login("matz", "pythonsucks"):
 
            response = self.get("review_detail", pk=guidos_proposal.pk)
 
            # Matz can't see guido's proposal
 
            self.assertEqual(response.status_code, 302)
 
        
 

 
        larry = User.objects.get(username="larryw")
 
        # Larry is a trustworthy guy, he's a reviewer.
...
 
@@ -61,8 +61,8 @@ class ReviewTests(TestCase):
 
            # Reviewers can see a review detail page.
 
            self.assertEqual(response.status_code, 200)
 
    
 

 
    def test_reviewing(self):
 
        guidos_proposal = Proposal.objects.all()[0]
 
        
 

 
        with self.login("guido", "pythonisawesome"):
 
            response = self.post("review_review", pk=guidos_proposal.pk, data={
...
 
@@ -73,5 +73,5 @@ class ReviewTests(TestCase):
 
            # ... no vote recorded
 
            self.assertEqual(guidos_proposal.reviews.count(), 0)
 
        
 

 
        larry = User.objects.get(username="larryw")
 
        # Larry is a trustworthy guy, he's a reviewer.
...
 
@@ -91,5 +91,5 @@ class ReviewTests(TestCase):
 
            comment = guidos_proposal.comments.get()
 
            self.assertFalse(comment.public)
 
            
 

 
            response = self.post("review_review", pk=guidos_proposal.pk, data={
 
                "vote": "+1",
...
 
@@ -101,5 +101,5 @@ class ReviewTests(TestCase):
 
            self.assertEqual(assignment.review, Review.objects.order_by("-id")[0])
 
            self.assertEqual(guidos_proposal.comments.count(), 2)
 
            
 

 
            # Larry's a big fan...
 
            response = self.post("review_review", pk=guidos_proposal.pk, data={
...
 
@@ -107,13 +107,13 @@ class ReviewTests(TestCase):
 
            })
 
            self.assertEqual(guidos_proposal.reviews.count(), 2)
 
    
 

 
    def test_speaker_commenting(self):
 
        guidos_proposal = Proposal.objects.all()[0]
 
        
 

 
        with self.login("guido", "pythonisawesome"):
 
            response = self.get("review_comment", pk=guidos_proposal.pk)
 
            # Guido can comment on his proposal.
 
            self.assertEqual(response.status_code, 200)
 
            
 

 
            response = self.post("review_comment", pk=guidos_proposal.pk, data={
 
                "text": "FYI I can do this as a 30-minute or 45-minute talk.",
...
 
@@ -123,5 +123,5 @@ class ReviewTests(TestCase):
 
            comment = guidos_proposal.comments.get()
 
            self.assertTrue(comment.public)
 
        
 

 
        larry = User.objects.get(username="larryw")
 
        # Larry is a trustworthy guy, he's a reviewer.
...
 
@@ -131,5 +131,5 @@ class ReviewTests(TestCase):
 
            # Larry can comment, since he's a reviewer
 
            self.assertEqual(response.status_code, 200)
 
            
 

 
            response = self.post("review_comment", pk=guidos_proposal.pk, data={
 
                "text": "Thanks for the heads-up Guido."
...
 
@@ -137,5 +137,5 @@ class ReviewTests(TestCase):
 
            self.assertEqual(response.status_code, 302)
 
            self.assertEqual(guidos_proposal.comments.count(), 2)
 
        
 

 
        with self.login("matz", "pythonsucks"):
 
            response = self.get("review_comment", pk=guidos_proposal.pk)
symposion/reviews/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import patterns, url
 

	
...
 
@@ -15,7 +16,7 @@ urlpatterns = patterns("symposion.reviews.views",
 
    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"),
symposion/reviews/utils.py
Show inline comments
...
 
@@ -3,6 +3,6 @@ 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 
 

	
 
    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.
...
 
@@ -11,6 +11,6 @@ def has_permission(user, proposal, speaker=False, reviewer=False):
 
        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:
symposion/reviews/views.py
Show inline comments
 
import re
 

	
 
from django.core.mail import send_mass_mail
 
from django.db.models import Q
...
 
@@ -28,5 +26,5 @@ def access_not_permitted(request):
 

	
 
def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
 
    
 

 
    for obj in queryset:
 
        # @@@ this sucks; we can do better
...
 
@@ -34,10 +32,10 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
 
            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
...
 
@@ -47,10 +45,10 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
 
        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
...
 
@@ -59,23 +57,24 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
 
            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
...
 
@@ -89,7 +88,7 @@ def review_section(request, section_slug, assigned=False, reviewed="all"):
 
        queryset = queryset.exclude(reviews__user=request.user).exclude(speaker=request.user)
 
        reviewed = "user_not_reviewed"
 
    
 

 
    proposals = proposals_generator(request, queryset)
 
    
 

 
    ctx = {
 
        "proposals": proposals,
...
 
@@ -97,10 +96,11 @@ def review_section(request, section_slug, assigned=False, reviewed="all"):
 
        "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
...
 
@@ -108,14 +108,14 @@ def review_list(request, section_slug, user_pk):
 
        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,
...
 
@@ -126,11 +126,11 @@ def review_list(request, section_slug, user_pk):
 
@login_required
 
def review_admin(request, section_slug):
 
    
 

 
    if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
 
        return access_not_permitted(request)
 
    
 

 
    def reviewers():
 
        already_seen = set()
 
        
 

 
        for team in Team.objects.filter(permissions__codename="can_review_%s" % section_slug):
 
            for membership in team.memberships.filter(Q(state="member") | Q(state="manager")):
...
 
@@ -139,26 +139,26 @@ def review_admin(request, section_slug):
 
                    continue
 
                already_seen.add(user.pk)
 
                
 

 
                user.comment_count = Review.objects.filter(user=user).count()
 
                user.total_votes = LatestVote.objects.filter(user=user).count()
 
                user.plus_one = LatestVote.objects.filter(
 
                    user = user,
 
                    vote = LatestVote.VOTES.PLUS_ONE
 
                    user=user,
 
                    vote=LatestVote.VOTES.PLUS_ONE
 
                ).count()
 
                user.plus_zero = LatestVote.objects.filter(
 
                    user = user,
 
                    vote = LatestVote.VOTES.PLUS_ZERO
 
                    user=user,
 
                    vote=LatestVote.VOTES.PLUS_ZERO
 
                ).count()
 
                user.minus_zero = LatestVote.objects.filter(
 
                    user = user,
 
                    vote = LatestVote.VOTES.MINUS_ZERO
 
                    user=user,
 
                    vote=LatestVote.VOTES.MINUS_ZERO
 
                ).count()
 
                user.minus_one = LatestVote.objects.filter(
 
                    user = user,
 
                    vote = LatestVote.VOTES.MINUS_ONE
 
                    user=user,
 
                    vote=LatestVote.VOTES.MINUS_ONE
 
                ).count()
 
                
 

 
                yield user
 
    
 

 
    ctx = {
 
        "section_slug": section_slug,
...
 
@@ -170,36 +170,36 @@ def review_admin(request, section_slug):
 
@login_required
 
def review_detail(request, pk):
 
    
 

 
    proposals = ProposalBase.objects.select_related("result").select_subclasses()
 
    proposal = get_object_or_404(proposals, pk=pk)
 
    
 

 
    if not request.user.has_perm("reviews.can_review_%s" % proposal.kind.section.slug):
 
        return access_not_permitted(request)
 
    
 

 
    speakers = [s.user for s in proposal.speakers()]
 
    
 

 
    if not request.user.is_superuser and request.user in speakers:
 
        return access_not_permitted(request)
 
    
 

 
    admin = request.user.is_staff
 
    
 

 
    try:
 
        latest_vote = LatestVote.objects.get(proposal=proposal, user=request.user)
 
    except LatestVote.DoesNotExist:
 
        latest_vote = None
 
    
 

 
    if request.method == "POST":
 
        if request.user in speakers:
 
            return access_not_permitted(request)
 
        
 

 
        if "vote_submit" in request.POST:
 
            review_form = ReviewForm(request.POST)
 
            if review_form.is_valid():
 
                
 

 
                review = review_form.save(commit=False)
 
                review.user = request.user
 
                review.proposal = proposal
 
                review.save()
 
                
 

 
                return redirect(request.path)
 
            else:
...
 
@@ -208,10 +208,10 @@ def review_detail(request, pk):
 
            message_form = SpeakerCommentForm(request.POST)
 
            if message_form.is_valid():
 
                
 

 
                message = message_form.save(commit=False)
 
                message.user = request.user
 
                message.proposal = proposal
 
                message.save()
 
                
 

 
                for speaker in speakers:
 
                    if speaker and speaker.email:
...
 
@@ -223,7 +223,7 @@ def review_detail(request, pk):
 
                        send_email(
 
                            [speaker.email], "proposal_new_message",
 
                            context = ctx
 
                            context=ctx
 
                        )
 
                
 

 
                return redirect(request.path)
 
            else:
...
 
@@ -238,5 +238,5 @@ def review_detail(request, pk):
 
            if admin:
 
                result = request.POST["result_submit"]
 
                
 

 
                if result == "accept":
 
                    proposal.result.status = "accepted"
...
 
@@ -251,5 +251,5 @@ def review_detail(request, pk):
 
                    proposal.result.status = "standby"
 
                    proposal.result.save()
 
            
 

 
            return redirect(request.path)
 
    else:
...
 
@@ -262,5 +262,5 @@ def review_detail(request, pk):
 
            review_form = ReviewForm(initial=initial)
 
        message_form = SpeakerCommentForm()
 
    
 

 
    proposal.comment_count = proposal.result.comment_count
 
    proposal.total_votes = proposal.result.vote_count
...
 
@@ -269,8 +269,8 @@ def review_detail(request, pk):
 
    proposal.minus_zero = proposal.result.minus_zero
 
    proposal.minus_one = proposal.result.minus_one
 
    
 

 
    reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at")
 
    messages = proposal.messages.order_by("submitted_at")
 
    
 

 
    return render(request, "reviews/review_detail.html", {
 
        "proposal": proposal,
...
 
@@ -288,11 +288,11 @@ 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)
 

	
...
 
@@ -300,39 +300,50 @@ def review_delete(request, 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({
...
 
@@ -342,5 +353,5 @@ def review_status(request, section_slug=None, key=None):
 
    else:
 
        ctx["proposals"] = proposals
 
    
 

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

	
...
 
@@ -362,12 +373,11 @@ def review_assignments(request):
 
@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")
 

	
...
 
@@ -388,5 +398,5 @@ def review_bulk_accept(request, section_slug):
 
    else:
 
        form = BulkPresentationForm()
 
    
 

 
    return render(request, "reviews/review_bulk_accept.html", {
 
        "form": form,
...
 
@@ -398,8 +408,10 @@ 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,
...
 
@@ -415,8 +427,8 @@ 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:
...
 
@@ -432,5 +444,5 @@ def result_notification_prepare(request, section_slug, status):
 
    proposals = proposals.select_related("speaker__user", "result")
 
    proposals = proposals.select_subclasses()
 
    
 

 
    notification_template_pk = request.POST.get("notification_template", "")
 
    if notification_template_pk:
...
 
@@ -438,5 +450,5 @@ def result_notification_prepare(request, section_slug, status):
 
    else:
 
        notification_template = None
 
    
 

 
    ctx = {
 
        "section_slug": section_slug,
...
 
@@ -453,16 +465,16 @@ def result_notification_send(request, section_slug, status):
 
    if request.method != "POST":
 
        return HttpResponseNotAllowed(["POST"])
 
    
 

 
    if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
 
        return access_not_permitted(request)
 
    
 

 
    if not all([k in request.POST for k in ["proposal_pks", "from_address", "subject", "body"]]):
 
        return HttpResponseBadRequest()
 
    
 

 
    try:
 
        proposal_pks = [int(pk) for pk in request.POST["proposal_pks"].split(",")]
 
    except ValueError:
 
        return HttpResponseBadRequest()
 
    
 

 
    proposals = ProposalBase.objects.filter(
 
        kind__section__slug=section_slug,
...
 
@@ -472,5 +484,5 @@ def result_notification_send(request, section_slug, status):
 
    proposals = proposals.select_related("speaker__user", "result")
 
    proposals = proposals.select_subclasses()
 
    
 

 
    notification_template_pk = request.POST.get("notification_template", "")
 
    if notification_template_pk:
...
 
@@ -478,7 +490,7 @@ def result_notification_send(request, section_slug, status):
 
    else:
 
        notification_template = None
 
    
 

 
    emails = []
 
    
 

 
    for proposal in proposals:
 
        rn = ResultNotification()
...
 
@@ -495,6 +507,6 @@ def result_notification_send(request, section_slug, status):
 
        rn.save()
 
        emails.append(rn.email_args)
 
    
 

 
    send_mass_mail(emails)
 
    
 

 
    return redirect("result_notification", section_slug=section_slug, status=status)
symposion/schedule/forms.py
Show inline comments
...
 
@@ -8,5 +8,5 @@ from symposion.schedule.models import Presentation
 

	
 
class SlotEditForm(forms.Form):
 
    
 

 
    def __init__(self, *args, **kwargs):
 
        self.slot = kwargs.pop("slot")
...
 
@@ -17,5 +17,5 @@ class SlotEditForm(forms.Form):
 
        else:
 
            self.fields["content_override"] = self.build_content_override_field()
 
    
 

 
    def build_presentation_field(self):
 
        kwargs = {}
...
 
@@ -32,5 +32,5 @@ class SlotEditForm(forms.Form):
 
        kwargs["queryset"] = queryset
 
        return forms.ModelChoiceField(**kwargs)
 
    
 

 
    def build_content_override_field(self):
 
        kwargs = {
symposion/schedule/models.py
Show inline comments
...
 
@@ -3,5 +3,4 @@ from django.db import models
 

	
 
from markitup.fields import MarkupField
 
from model_utils.managers import InheritanceManager
 

	
 
from symposion.proposals.models import ProposalBase
...
 
@@ -10,12 +9,12 @@ 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"]
...
 
@@ -23,11 +22,11 @@ class Schedule(models.Model):
 

	
 
class Day(models.Model):
 
    
 

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

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

 
    class Meta:
 
        unique_together = [("schedule", "date")]
...
 
@@ -36,9 +35,9 @@ class Day(models.Model):
 

	
 
class Room(models.Model):
 
    
 

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

 
    def __unicode__(self):
 
        return self.name
...
 
@@ -50,8 +49,8 @@ class SlotKind(models.Model):
 
    break, lunch, or X-minute talk.
 
    """
 
    
 

 
    schedule = models.ForeignKey(Schedule)
 
    label = models.CharField(max_length=50)
 
    
 

 
    def __unicode__(self):
 
        return self.label
...
 
@@ -59,5 +58,5 @@ class SlotKind(models.Model):
 

	
 
class Slot(models.Model):
 
    
 

 
    day = models.ForeignKey(Day)
 
    kind = models.ForeignKey(SlotKind)
...
 
@@ -65,5 +64,5 @@ class Slot(models.Model):
 
    end = models.TimeField()
 
    content_override = MarkupField(blank=True)
 
    
 

 
    def assign(self, content):
 
        """
...
 
@@ -74,5 +73,5 @@ class Slot(models.Model):
 
        content.slot = self
 
        content.save()
 
    
 

 
    def unassign(self):
 
        """
...
 
@@ -82,5 +81,5 @@ class Slot(models.Model):
 
            self.content.slot = None
 
            self.content.save()
 
    
 

 
    @property
 
    def content(self):
...
 
@@ -93,12 +92,12 @@ class Slot(models.Model):
 
        except ObjectDoesNotExist:
 
            return None
 
    
 

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

 
    def __unicode__(self):
 
        return "%s %s (%s - %s)" % (self.day, self.kind, self.start, self.end)
 
    
 

 
    class Meta:
 
        ordering = ["day", "start", "end"]
...
 
@@ -109,11 +108,11 @@ 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")]
...
 
@@ -122,5 +121,5 @@ class SlotRoom(models.Model):
 

	
 
class Presentation(models.Model):
 
    
 

 
    slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr")
 
    title = models.CharField(max_length=100)
...
 
@@ -128,13 +127,14 @@ class Presentation(models.Model):
 
    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):
...
 
@@ -142,5 +142,5 @@ class Presentation(models.Model):
 
            return None
 
        return ProposalBase.objects.get_subclass(pk=self.proposal_base_id)
 
    
 

 
    def speakers(self):
 
        yield self.speaker
...
 
@@ -148,8 +148,8 @@ class Presentation(models.Model):
 
            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
...
 
@@ -8,20 +7,21 @@ 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"))))
...
 
@@ -39,5 +39,5 @@ class TimeTable(object):
 
            if row["slots"] or next_time is None:
 
                yield row
 
    
 

 
    @staticmethod
 
    def rowspan(times, start, end):
symposion/schedule/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import url, patterns
 

	
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
...
 
@@ -13,5 +12,5 @@ from symposion.schedule.timetable import TimeTable
 
def fetch_schedule(slug):
 
    qs = Schedule.objects.all()
 
    
 

 
    if slug is None:
 
        if qs.count() > 1:
...
 
@@ -22,12 +21,12 @@ def fetch_schedule(slug):
 
    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:
...
 
@@ -38,5 +37,5 @@ def schedule_conference(request):
 
            "days": days,
 
        })
 
    
 

 
    ctx = {
 
        "sections": sections,
...
 
@@ -46,12 +45,12 @@ def schedule_conference(request):
 

	
 
def schedule_detail(request, slug=None):
 
    
 

 
    schedule = fetch_schedule(slug)
 
    if not schedule.published and not request.user.is_staff:
 
        raise Http404()
 
    
 

 
    days_qs = Day.objects.filter(schedule=schedule)
 
    days = [TimeTable(day) for day in days_qs]
 
    
 

 
    ctx = {
 
        "schedule": schedule,
...
 
@@ -63,8 +62,8 @@ def schedule_detail(request, slug=None):
 
def schedule_list(request, slug=None):
 
    schedule = fetch_schedule(slug)
 
    
 

 
    presentations = Presentation.objects.filter(section=schedule.section)
 
    presentations = presentations.exclude(cancelled=True)
 
    
 

 
    ctx = {
 
        "schedule": schedule,
...
 
@@ -76,8 +75,8 @@ def schedule_list(request, slug=None):
 
def schedule_list_csv(request, slug=None):
 
    schedule = fetch_schedule(slug)
 
    
 

 
    presentations = Presentation.objects.filter(section=schedule.section)
 
    presentations = presentations.exclude(cancelled=True).order_by("id")
 
    
 

 
    response = HttpResponse(mimetype="text/csv")
 
    if slug:
...
 
@@ -86,8 +85,8 @@ def schedule_list_csv(request, slug=None):
 
        file_slug = "presentations"
 
    response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug
 
    
 

 
    response.write(loader.get_template("schedule/schedule_list.csv").render(Context({
 
        "presentations": presentations,
 
        
 

 
    })))
 
    return response
...
 
@@ -96,10 +95,10 @@ def schedule_list_csv(request, slug=None):
 
@login_required
 
def schedule_edit(request, slug=None):
 
    
 

 
    if not request.user.is_staff:
 
        raise Http404()
 
    
 

 
    schedule = fetch_schedule(slug)
 
    
 

 
    days_qs = Day.objects.filter(schedule=schedule)
 
    days = [TimeTable(day) for day in days_qs]
...
 
@@ -113,10 +112,10 @@ def schedule_edit(request, slug=None):
 
@login_required
 
def schedule_slot_edit(request, slug, slot_pk):
 
    
 

 
    if not request.user.is_staff:
 
        raise Http404()
 
    
 

 
    slot = get_object_or_404(Slot, day__schedule__section__slug=slug, pk=slot_pk)
 
    
 

 
    if request.method == "POST":
 
        form = SlotEditForm(request.POST, slot=slot)
...
 
@@ -146,5 +145,5 @@ def schedule_slot_edit(request, slug, slot_pk):
 

	
 
def schedule_presentation_detail(request, pk):
 
    
 

 
    presentation = get_object_or_404(Presentation, pk=pk)
 
    if presentation.slot:
...
 
@@ -152,5 +151,5 @@ def schedule_presentation_detail(request, pk):
 
    else:
 
        schedule = None
 
    
 

 
    ctx = {
 
        "presentation": presentation,
symposion/speakers/admin.py
Show inline comments
...
 
@@ -5,5 +5,4 @@ from symposion.speakers.models import Speaker
 

	
 
admin.site.register(Speaker,
 
    list_display = ["name", "email", "created"],
 
    search_fields = ["name"],
 
)
...
 
\ No newline at end of file
 
                    list_display=["name", "email", "created"],
 
                    search_fields=["name"])
symposion/speakers/fixture_gen.py
Show inline comments
...
 
@@ -11,5 +11,5 @@ def speakers():
 
    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,
...
 
@@ -20,6 +20,6 @@ def speakers():
 
        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(
symposion/speakers/forms.py
Show inline comments
...
 
@@ -7,5 +7,5 @@ from symposion.speakers.models import Speaker
 

	
 
class SpeakerForm(forms.ModelForm):
 
    
 

 
    class Meta:
 
        model = Speaker
symposion/speakers/management/commands/export_speaker_data.py
Show inline comments
...
 
@@ -2,5 +2,5 @@ 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
...
 
@@ -8,9 +8,9 @@ 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([
symposion/speakers/models.py
Show inline comments
...
 
@@ -10,13 +10,17 @@ 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
...
 
@@ -24,11 +28,11 @@ class Speaker(models.Model):
 
    invite_token = models.CharField(max_length=40, db_index=True)
 
    created = models.DateTimeField(
 
        default = datetime.datetime.now,
 
        editable = False
 
        default=datetime.datetime.now,
 
        editable=False
 
    )
 

	
 
    class Meta:
 
        ordering = ['name']
 
    
 

 
    def __unicode__(self):
 
        if self.user:
...
 
@@ -37,8 +41,7 @@ class Speaker(models.Model):
 
            return "?"
 

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

 
    @property
 
    def email(self):
...
 
@@ -47,5 +50,5 @@ class Speaker(models.Model):
 
        else:
 
            return self.invite_email
 
    
 

 
    @property
 
    def all_presentations(self):
symposion/speakers/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import *
 

	
symposion/speakers/views.py
Show inline comments
...
 
@@ -18,5 +18,5 @@ def speaker_create(request):
 
    except ObjectDoesNotExist:
 
        pass
 
    
 

 
    if request.method == "POST":
 
        try:
...
 
@@ -27,5 +27,5 @@ def speaker_create(request):
 
            found = False
 
        form = SpeakerForm(request.POST, request.FILES, instance=speaker)
 
        
 

 
        if form.is_valid():
 
            speaker = form.save(commit=False)
...
 
@@ -38,5 +38,5 @@ def speaker_create(request):
 
    else:
 
        form = SpeakerForm(initial={"name": request.user.get_full_name()})
 
    
 

 
    return render(request, "speakers/speaker_create.html", {
 
        "form": form,
...
 
@@ -49,13 +49,13 @@ def speaker_create_staff(request, pk):
 
    if not request.user.is_staff:
 
        raise Http404
 
    
 

 
    try:
 
        return redirect(user.speaker_profile)
 
    except ObjectDoesNotExist:
 
        pass
 
    
 

 
    if request.method == "POST":
 
        form = SpeakerForm(request.POST, request.FILES)
 
        
 

 
        if form.is_valid():
 
            speaker = form.save(commit=False)
...
 
@@ -66,5 +66,5 @@ def speaker_create_staff(request, pk):
 
    else:
 
        form = SpeakerForm(initial={"name": user.get_full_name()})
 
    
 

 
    return render(request, "speakers/speaker_create.html", {
 
        "form": form,
...
 
@@ -89,6 +89,6 @@ def speaker_create_token(request, token):
 
                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:
...
 
@@ -110,5 +110,5 @@ def speaker_edit(request, pk=None):
 
        else:
 
            raise Http404()
 
    
 

 
    if request.method == "POST":
 
        form = SpeakerForm(request.POST, request.FILES, instance=speaker)
...
 
@@ -119,5 +119,5 @@ def speaker_edit(request, pk=None):
 
    else:
 
        form = SpeakerForm(instance=speaker)
 
    
 

 
    return render(request, "speakers/speaker_edit.html", {
 
        "form": form,
...
 
@@ -130,5 +130,5 @@ def speaker_profile(request, pk):
 
    if not presentations and not request.user.is_staff:
 
        raise Http404()
 
    
 

 
    return render(request, "speakers/speaker_profile.html", {
 
        "speaker": speaker,
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
 

	
 

	
...
 
@@ -25,5 +26,5 @@ class SponsorBenefitInline(admin.StackedInline):
 

	
 
class SponsorAdmin(admin.ModelAdmin):
 
    
 

 
    save_on_top = True
 
    fieldsets = [
...
 
@@ -44,5 +45,5 @@ class SponsorAdmin(admin.ModelAdmin):
 
    inlines = [SponsorBenefitInline]
 
    list_display = ["name", "external_url", "level", "active"]
 
    
 

 
    def get_form(self, *args, **kwargs):
 
        # @@@ kinda ugly but using choices= on NullBooleanField is broken
...
 
@@ -57,5 +58,5 @@ class SponsorAdmin(admin.ModelAdmin):
 

	
 
class BenefitAdmin(admin.ModelAdmin):
 
    
 

 
    list_display = ["name", "type", "description"]
 
    inlines = [BenefitLevelInline]
...
 
@@ -63,5 +64,5 @@ class BenefitAdmin(admin.ModelAdmin):
 

	
 
class SponsorLevelAdmin(admin.ModelAdmin):
 
    
 

 
    inlines = [BenefitLevelInline]
 

	
symposion/sponsorship/forms.py
Show inline comments
...
 
@@ -17,5 +17,5 @@ class SponsorApplicationForm(forms.ModelForm):
 
        })
 
        super(SponsorApplicationForm, self).__init__(*args, **kwargs)
 
    
 

 
    class Meta:
 
        model = Sponsor
...
 
@@ -27,5 +27,5 @@ class SponsorApplicationForm(forms.ModelForm):
 
            "level"
 
        ]
 
    
 

 
    def save(self, commit=True):
 
        obj = super(SponsorApplicationForm, self).save(commit=False)
...
 
@@ -48,24 +48,24 @@ class SponsorDetailsForm(forms.ModelForm):
 

	
 
class SponsorBenefitsInlineFormSet(BaseInlineFormSet):
 
    
 

 
    def _construct_form(self, i, **kwargs):
 
        form = super(SponsorBenefitsInlineFormSet, self)._construct_form(i, **kwargs)
 
        
 

 
        # only include the relevant data fields for this benefit type
 
        fields = form.instance.data_fields()
 
        form.fields = dict((k, v) for (k, v) in form.fields.items() if k in fields + ["id"])
 
        
 

 
        for field in fields:
 
            # don't need a label, the form template will label it with the benefit name
 
            form.fields[field].label = ""
 
            
 

 
            # provide word limit as help_text
 
            if form.instance.benefit.type == "text" and form.instance.max_words:
 
                form.fields[field].help_text = u"maximum %s words" % form.instance.max_words
 
            
 

 
            # use admin file widget that shows currently uploaded file
 
            if field == "upload":
 
                form.fields[field].widget = AdminFileWidget()
 
        
 

 
        return form
 

	
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  
 
            level = None
 
            try:
 
                level = sponsor.level
...
 
@@ -20,15 +18,15 @@ class Command(BaseCommand):
 
                    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
symposion/sponsorship/models.py
Show inline comments
...
 
@@ -15,5 +15,5 @@ from symposion.sponsorship.managers import SponsorManager
 

	
 
class SponsorLevel(models.Model):
 
    
 

 
    conference = models.ForeignKey(Conference, verbose_name=_("conference"))
 
    name = models.CharField(_("name"), max_length=100)
...
 
@@ -21,13 +21,13 @@ class SponsorLevel(models.Model):
 
    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")
...
 
@@ -35,7 +35,8 @@ class SponsorLevel(models.Model):
 

	
 
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"))
...
 
@@ -46,26 +47,28 @@ class Sponsor(models.Model):
 
    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:
...
 
@@ -73,5 +76,5 @@ class Sponsor(models.Model):
 
                    self.save()
 
        return self.sponsor_logo.upload
 
    
 

 
    @property
 
    def listing_text(self):
...
 
@@ -83,5 +86,5 @@ class Sponsor(models.Model):
 
                self._listing_text = benefits[0].text
 
        return self._listing_text
 
    
 

 
    def reset_benefits(self):
 
        """
...
 
@@ -90,10 +93,10 @@ class Sponsor(models.Model):
 
        """
 
        level = None
 
        
 

 
        try:
 
            level = self.level
 
        except SponsorLevel.DoesNotExist:
 
            pass
 
        
 

 
        allowed_benefits = []
 
        if level:
...
 
@@ -102,12 +105,12 @@ class Sponsor(models.Model):
 
                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
...
 
@@ -116,11 +119,12 @@ class Sponsor(models.Model):
 
                # 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?
...
 
@@ -148,9 +152,10 @@ BENEFIT_TYPE_CHOICES = [
 

	
 
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
...
 
@@ -158,15 +163,15 @@ class Benefit(models.Model):
 

	
 
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)
...
 
@@ -174,24 +179,24 @@ class BenefitLevel(models.Model):
 

	
 
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 clean(self):
 
        num_words = len(self.text.split())
...
 
@@ -200,5 +205,5 @@ class SponsorBenefit(models.Model):
 
                "Sponsorship level only allows for %s words, you provided %d." % (
 
                    self.max_words, num_words))
 
    
 

 
    def data_fields(self):
 
        """
symposion/sponsorship/templatetags/sponsorship_tags.py
Show inline comments
...
 
@@ -9,5 +9,5 @@ register = template.Library()
 

	
 
class SponsorsNode(template.Node):
 
    
 

 
    @classmethod
 
    def handle_token(cls, parser, token):
...
 
@@ -19,5 +19,5 @@ class SponsorsNode(template.Node):
 
        else:
 
            raise template.TemplateSyntaxError("%r takes 'as var' or 'level as var'" % bits[0])
 
    
 

 
    def __init__(self, context_var, level=None):
 
        if level:
...
 
@@ -26,12 +26,15 @@ class SponsorsNode(template.Node):
 
            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""
...
 
@@ -39,5 +42,5 @@ class SponsorsNode(template.Node):
 

	
 
class SponsorLevelNode(template.Node):
 
    
 

 
    @classmethod
 
    def handle_token(cls, parser, token):
...
 
@@ -47,8 +50,8 @@ class SponsorLevelNode(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):
 
        conference = current_conference()
...
 
@@ -73,3 +76,2 @@ def sponsor_levels(parser, token):
 
    """
 
    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
symposion/sponsorship/views.py
Show inline comments
...
 
@@ -6,5 +6,6 @@ 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
 

	
...
 
@@ -19,5 +20,5 @@ def sponsor_apply(request):
 
    else:
 
        form = SponsorApplicationForm(user=request.user)
 
    
 

 
    return render_to_response("sponsorship/apply.html", {
 
        "form": form,
...
 
@@ -29,5 +30,5 @@ def sponsor_add(request):
 
    if not request.user.is_staff:
 
        raise Http404()
 
    
 

 
    if request.method == "POST":
 
        form = SponsorApplicationForm(request.POST, user=request.user)
...
 
@@ -39,5 +40,5 @@ def sponsor_add(request):
 
    else:
 
        form = SponsorApplicationForm(user=request.user)
 
    
 

 
    return render_to_response("sponsorship/add.html", {
 
        "form": form,
...
 
@@ -48,29 +49,29 @@ def sponsor_add(request):
 
def sponsor_detail(request, pk):
 
    sponsor = get_object_or_404(Sponsor, pk=pk)
 
    
 

 
    if sponsor.applicant != request.user:
 
        return redirect("sponsor_list")
 
    
 

 
    formset_kwargs = {
 
        "instance": sponsor,
 
        "queryset": SponsorBenefit.objects.filter(active=True)
 
    }
 
    
 

 
    if request.method == "POST":
 
        
 

 
        form = SponsorDetailsForm(request.POST, instance=sponsor)
 
        formset = SponsorBenefitsFormSet(request.POST, request.FILES, **formset_kwargs)
 
        
 

 
        if form.is_valid() and formset.is_valid():
 
            form.save()
 
            formset.save()
 
            
 

 
            messages.success(request, "Sponsorship details have been updated")
 
            
 

 
            return redirect("dashboard")
 
    else:
 
        form = SponsorDetailsForm(instance=sponsor)
 
        formset = SponsorBenefitsFormSet(**formset_kwargs)
 
    
 

 
    return render_to_response("sponsorship/detail.html", {
 
        "sponsor": sponsor,
symposion/teams/admin.py
Show inline comments
...
 
@@ -6,6 +6,5 @@ from symposion.teams.models import Team, Membership
 

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

	
 

	
symposion/teams/backends.py
Show inline comments
...
 
@@ -5,8 +5,8 @@ from .models import Team
 

	
 
class TeamPermissionsBackend(object):
 
    
 

 
    def authenticate(self, username=None, password=None):
 
        return None
 
    
 

 
    def get_team_permissions(self, user_obj, obj=None):
 
        """
symposion/teams/forms.py
Show inline comments
...
 
@@ -10,18 +10,19 @@ 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)
...
 
@@ -29,19 +30,21 @@ class TeamInvitationForm(forms.Form):
 
            # 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:
symposion/teams/models.py
Show inline comments
...
 
@@ -21,17 +21,18 @@ class Team(models.Model):
 
    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
...
 
@@ -42,14 +43,14 @@ class Team(models.Model):
 
        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")
symposion/teams/templatetags/teams_tags.py
Show inline comments
...
 
@@ -7,5 +7,5 @@ register = template.Library()
 

	
 
class AvailableTeamsNode(template.Node):
 
    
 

 
    @classmethod
 
    def handle_token(cls, parser, token):
...
 
@@ -15,8 +15,8 @@ class AvailableTeamsNode(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"]
symposion/teams/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import *
 

	
...
 
@@ -8,5 +9,5 @@ urlpatterns = patterns("symposion.teams.views",
 
    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"),
symposion/teams/views.py
Show inline comments
...
 
@@ -11,5 +11,5 @@ from symposion.teams.models import Team, Membership
 

	
 

	
 
## perm checks
 
# perm checks
 
#
 
# @@@ these can be moved
...
 
@@ -51,5 +51,5 @@ def can_invite(team, user):
 

	
 

	
 
## views
 
# views
 

	
 

	
...
 
@@ -60,5 +60,5 @@ def team_detail(request, slug):
 
    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":
...
 
@@ -73,5 +73,5 @@ def team_detail(request, slug):
 
    else:
 
        form = None
 
    
 

 
    return render(request, "teams/team_detail.html", {
 
        "team": team,
...
 
@@ -90,5 +90,5 @@ def team_join(request, slug):
 
    if team.access == "invitation" and state is None and not request.user.is_staff:
 
        raise Http404()
 
    
 

 
    if can_join(team, request.user) and request.method == "POST":
 
        membership, created = Membership.objects.get_or_create(team=team, user=request.user)
...
 
@@ -107,5 +107,5 @@ def team_leave(request, slug):
 
    if team.access == "invitation" and state is None and not request.user.is_staff:
 
        raise Http404()
 
    
 

 
    if can_leave(team, request.user) and request.method == "POST":
 
        membership = Membership.objects.get(team=team, user=request.user)
...
 
@@ -123,5 +123,5 @@ def team_apply(request, slug):
 
    if team.access == "invitation" and state is None and not request.user.is_staff:
 
        raise Http404()
 
    
 

 
    if can_apply(team, request.user) and request.method == "POST":
 
        membership, created = Membership.objects.get_or_create(team=team, user=request.user)
symposion/utils/mail.py
Show inline comments
...
 
@@ -8,7 +8,7 @@ from django.contrib.sites.models import Site
 

	
 
def send_email(to, kind, **kwargs):
 
    
 

 
    current_site = Site.objects.get_current()
 
    
 

 
    ctx = {
 
        "current_site": current_site,
...
 
@@ -20,10 +20,10 @@ def send_email(to, kind, **kwargs):
 
        render_to_string("emails/%s/subject.txt" % kind, ctx).strip()
 
    )
 
    
 

 
    message_html = render_to_string("emails/%s/message.html" % kind, ctx)
 
    message_plaintext = strip_tags(message_html)
 
    
 

 
    from_email = settings.DEFAULT_FROM_EMAIL
 
    
 

 
    email = EmailMultiAlternatives(subject, message_plaintext, from_email, to)
 
    email.attach_alternative(message_html, "text/html")
symposion/views.py
Show inline comments
...
 
@@ -13,10 +13,10 @@ 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 = {
0 comments (0 inline, 0 general)