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

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

	
 
from django.db import models
 

	
 
from django.contrib.auth.models import User
 

	
 
import reversion
 

	
 
from markitup.fields import MarkupField
 

	
symposion/boxes/templatetags/boxes_tags.py
Show inline comments
 
from django import template
 
from django.core.exceptions import ImproperlyConfigured
 
from django.core.urlresolvers import reverse
 
from django.utils.safestring import mark_safe
 
from django.utils.encoding import smart_str
 
from django.utils.translation import ugettext_lazy as _
 
from django.template.defaulttags import kwarg_re
 

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

	
 

	
 
register = template.Library()
 

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

	
 

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

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

	
 

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

	
 
import reversion
 

	
 
from .models import Page
 

	
 

	
 
class PageAdmin(reversion.VersionAdmin):
 

	
 
    pass
 

	
 

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

	
 
from django.db import models
 

	
 

	
 
class PublishedPageManager(models.Manager):
 

	
 
    def get_query_set(self):
 
        qs = super(PublishedPageManager, self).get_query_set()
 
        return qs.filter(publish_date__lte=datetime.now())
symposion/cms/models.py
Show inline comments
...
 
@@ -48,17 +48,18 @@ class Page(models.Model):
 

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

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

	
 

	
 
reversion.register(Page)
 

	
 

	
 
def generate_filename(instance, filename):
 
    return filename
 

	
symposion/cms/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import url, patterns
 

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

	
 
urlpatterns = patterns("symposion.cms.views",
 
    url(r"^files/$", "file_index", name="file_index"),
 
    url(r"^files/create/$", "file_create", name="file_create"),
 
    url(r"^files/(\d+)/([^/]+)$", "file_download", name="file_download"),
symposion/conf.py
Show inline comments
 
from django.conf import settings
 

	
 
from appconf import AppConf
 

	
 

	
 
class SymposionAppConf(AppConf):
 

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

	
 

	
 
urlpatterns = patterns("symposion.conference.views",
 
    url(r"^users/$", "user_list", name="user_list"),
 
)
symposion/forms.py
Show inline comments
...
 
@@ -21,10 +21,11 @@ class SignupForm(account.forms.SignupForm):
 
            "password_confirm"
 
        ]
 

	
 
    def clean_email_confirm(self):
 
        email = self.cleaned_data.get("email")
 
        email_confirm = self.cleaned_data["email_confirm"]
 
        if email:
 
            if email != email_confirm:
 
                raise forms.ValidationError("Email address must match previously typed email address")
 
                raise forms.ValidationError(
 
                    "Email address must match previously typed email address")
 
        return email_confirm
symposion/markdown_parser.py
Show inline comments
 
import html5lib
 
from html5lib import html5parser, sanitizer
 

	
 
import markdown
 

	
 

	
 
def parse(text):
 

	
 
    # First run through the Markdown parser
symposion/proposals/actions.py
Show inline comments
 
import csv
 

	
 
from django.http import HttpResponse
 

	
 

	
 
def export_as_csv_action(
 
    description="Export selected objects as CSV file",
 
def export_as_csv_action(description="Export selected objects as CSV file",
 
                         fields=None, exclude=None, header=True):
 
    """
 
    This function returns an export csv action
 
    'fields' and 'exclude' work like in Django ModelForm
 
    'header' is whether or not to output the column names as the first row
 
    """
 
    def export_as_csv(modeladmin, request, queryset):
 
        """
...
 
@@ -19,17 +18,19 @@ def export_as_csv_action(
 
        opts = modeladmin.model._meta
 
        if fields:
 
            fieldset = set(fields)
 
            field_names = fieldset
 
        elif exclude:
 
            excludeset = set(exclude)
 
            field_names = field_names - excludeset
 
        response = HttpResponse(mimetype="text/csv")
 
        response["Content-Disposition"] = "attachment; filename=%s.csv" % unicode(opts).replace(".", "_")
 
        response["Content-Disposition"] = \
 
            "attachment; filename=%s.csv" % unicode(opts).replace(".", "_")
 
        writer = csv.writer(response)
 
        if header:
 
            writer.writerow(list(field_names))
 
        for obj in queryset:
 
            writer.writerow([unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names])
 
            writer.writerow(
 
                [unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names])
 
        return response
 
    export_as_csv.short_description = description
 
    return export_as_csv
symposion/proposals/managers.py
Show inline comments
 
deleted file
symposion/proposals/models.py
Show inline comments
...
 
@@ -80,32 +80,39 @@ class ProposalBase(models.Model):
 
    objects = InheritanceManager()
 

	
 
    kind = models.ForeignKey(ProposalKind)
 

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

	
 
    def can_edit(self):
 
        return True
 

	
 
    @property
 
    def section(self):
 
        return self.kind.section
...
 
@@ -115,17 +122,19 @@ class ProposalBase(models.Model):
 
        return self.speaker.email
 

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

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

	
 
    def notification_email_context(self):
 
        return {
 
            "title": self.title,
 
            "speaker": self.speaker.name,
 
            "kind": self.kind.name,
 
        }
...
 
@@ -167,9 +176,10 @@ class SupportingDocument(models.Model):
 

	
 
    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
...
 
@@ -65,9 +65,8 @@ def pending_proposals(parser, token):
 

	
 

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

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

	
 

	
 
urlpatterns = patterns("symposion.proposals.views",
 
    url(r"^submit/$", "proposal_submit", name="proposal_submit"),
 
    url(r"^submit/([\w\-]+)/$", "proposal_submit_kind", name="proposal_submit_kind"),
 
    url(r"^(\d+)/$", "proposal_detail", name="proposal_detail"),
 
    url(r"^(\d+)/edit/$", "proposal_edit", name="proposal_edit"),
symposion/proposals/views.py
Show inline comments
...
 
@@ -147,17 +147,18 @@ def proposal_speaker_manage(request, pk):
 
                speaker, token = create_speaker_token(email_address)
 
                message_ctx["token"] = token
 
                # fire off email letting user know about site and to create
 
                # account and speaker profile
 
                send_email(
 
                    [email_address], "speaker_invite",
 
                    context=message_ctx
 
                )
 
            invitation, created = AdditionalSpeaker.objects.get_or_create(proposalbase=proposal.proposalbase_ptr, speaker=speaker)
 
            invitation, created = AdditionalSpeaker.objects.get_or_create(
 
                proposalbase=proposal.proposalbase_ptr, speaker=speaker)
 
            messages.success(request, "Speaker invited to proposal.")
 
            return redirect("proposal_speaker_manage", proposal.pk)
 
    else:
 
        add_speaker_form = AddSpeakerForm(proposal=proposal)
 
    ctx = {
 
        "proposal": proposal,
 
        "speakers": proposal.speakers(),
 
        "add_speaker_form": add_speaker_form,
...
 
@@ -306,30 +307,32 @@ def proposal_leave(request, pk):
 
        "proposal": proposal,
 
    }
 
    return render(request, "proposals/proposal_leave.html", ctx)
 

	
 

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

	
 

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

	
symposion/reviews/context_processors.py
Show inline comments
 
from django.contrib.contenttypes.models import ContentType
 

	
 
from symposion.proposals.models import ProposalSection
 

	
 

	
 
def reviews(request):
 
    sections = []
 
    for section in ProposalSection.objects.all():
 
        if request.user.has_perm("reviews.can_review_%s" % section.section.slug):
 
            sections.append(section)
symposion/reviews/management/commands/assign_reviewers.py
Show inline comments
 
import csv
 
import os
 
import random
 

	
 
from django.contrib.auth import models
 
from django.core.management.base import BaseCommand
 

	
 
from symposion.reviews.models import ReviewAssignment
 
from symposion.proposals.models import ProposalBase
 

	
 

	
 
class Command(BaseCommand):
 

	
symposion/reviews/management/commands/calculate_results.py
Show inline comments
 
from django.core.management.base import BaseCommand
 

	
 
from django.contrib.auth.models import Group
 

	
 
from symposion.reviews.models import ProposalResult
 

	
 

	
 
class Command(BaseCommand):
 

	
 
    def handle(self, *args, **options):
 
        ProposalResult.full_calculate()
symposion/reviews/management/commands/promoteproposals.py
Show inline comments
...
 
@@ -7,9 +7,10 @@ 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
...
 
@@ -147,17 +147,18 @@ class Review(models.Model):
 
            lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user)
 
            lv.delete()
 
        else:
 
            # handle that we've found a latest vote
 
            # check if self is the lastest vote
 
            if self == latest:
 
                # self is the latest review; revert the latest vote to the
 
                # previous vote
 
                previous = user_reviews.filter(submitted_at__lt=self.submitted_at).order_by("-submitted_at")[0]
 
                previous = user_reviews.filter(submitted_at__lt=self.submitted_at)\
 
                    .order_by("-submitted_at")[0]
 
                self.proposal.result.update_vote(self.vote, previous=previous.vote, removal=True)
 
                lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user)
 
                lv.update(
 
                    vote=previous.vote,
 
                    submitted_at=previous.submitted_at,
 
                )
 
            else:
 
                # self is not the latest review so we just need to decrement
...
 
@@ -298,17 +299,18 @@ class NotificationTemplate(models.Model):
 
    from_address = models.EmailField()
 
    subject = models.CharField(max_length=100)
 
    body = models.TextField()
 

	
 

	
 
class ResultNotification(models.Model):
 

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

	
 
    @property
 
    def email_args(self):
symposion/reviews/templatetags/review_tags.py
Show inline comments
 
from django import template
 

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

	
 

	
 
register = template.Library()
 

	
 

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

	
 

	
 
urlpatterns = patterns("symposion.reviews.views",
 
    url(r"^section/(?P<section_slug>[\w\-]+)/all/$", "review_section", {"reviewed": "all"}, name="review_section"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/reviewed/$", "review_section", {"reviewed": "reviewed"}, name="user_reviewed"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/not_reviewed/$", "review_section", {"reviewed": "not_reviewed"}, name="user_not_reviewed"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/assignments/$", "review_section", {"assigned": True}, name="review_section_assignments"),
symposion/reviews/utils.py
Show inline comments
...
 
@@ -5,15 +5,15 @@ def has_permission(user, proposal, speaker=False, reviewer=False):
 

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

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

	
 
from django.contrib.auth.decorators import login_required
...
 
@@ -57,29 +55,30 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
 
            obj.user_vote_css = LatestVote.objects.get(**lookup_params).css_class()
 
        except LatestVote.DoesNotExist:
 
            obj.user_vote = None
 
            obj.user_vote_css = "no-vote"
 

	
 
        yield obj
 

	
 

	
 
# Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has yet to review
 
# depending on the link user clicks in dashboard
 
# Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has
 
# yet to review depending on the link user clicks in dashboard
 
@login_required
 
def review_section(request, section_slug, assigned=False, reviewed="all"):
 

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

	
 
    section = get_object_or_404(ProposalSection, section__slug=section_slug)
 
    queryset = ProposalBase.objects.filter(kind__section=section)
 

	
 
    if assigned:
 
        assignments = ReviewAssignment.objects.filter(user=request.user).values_list("proposal__id")
 
        assignments = ReviewAssignment.objects.filter(user=request.user)\
 
            .values_list("proposal__id")
 
        queryset = queryset.filter(id__in=assignments)
 

	
 
    # passing reviewed in from reviews.urls and out to review_list for
 
    # appropriate template header rendering
 
    if reviewed == "all":
 
        queryset = queryset.select_related("result").select_subclasses()
 
        reviewed = "all_reviews"
 
    elif reviewed == "reviewed":
...
 
@@ -94,16 +93,17 @@ def review_section(request, section_slug, assigned=False, reviewed="all"):
 
    ctx = {
 
        "proposals": proposals,
 
        "section": section,
 
        "reviewed": reviewed,
 
    }
 

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

	
 

	
 
@login_required
 
def review_list(request, section_slug, user_pk):
 

	
 
    # if they're not a reviewer admin and they aren't the person whose
 
    # review list is being asked for, don't let them in
 
    if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
 
        if not request.user.pk == user_pk:
 
            return access_not_permitted(request)
...
 
@@ -310,34 +310,45 @@ def review_status(request, section_slug=None, key=None):
 
        "vote_threshold": VOTE_THRESHOLD,
 
    }
 

	
 
    queryset = ProposalBase.objects.select_related("speaker__user", "result").select_subclasses()
 
    if section_slug:
 
        queryset = queryset.filter(kind__section__slug=section_slug)
 

	
 
    proposals = {
 
        # proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by the 'score'
 
        "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one=0).order_by("-result__score"),
 
        # proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse sorted by the 'score'
 
        "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0, result__plus_one=0).order_by("result__score"),
 
        # proposals with at least VOTE_THRESHOLD reviews and neither a +1 or a -1, sorted by total votes (lowest first)
 
        "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one=0, result__plus_one=0).order_by("result__vote_count"),
 
        # proposals with at least VOTE_THRESHOLD reviews and both a +1 and -1, sorted by total votes (highest first)
 
        "controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one__gt=0).order_by("-result__vote_count"),
 
        # proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by
 
        # the 'score'
 
        "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0,
 
                                    result__minus_one=0).order_by("-result__score"),
 
        # proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse
 
        # sorted by the 'score'
 
        "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0,
 
                                    result__plus_one=0).order_by("result__score"),
 
        # proposals with at least VOTE_THRESHOLD reviews and neither a +1 or a -1, sorted by total
 
        # votes (lowest first)
 
        "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one=0,
 
                                       result__plus_one=0).order_by("result__vote_count"),
 
        # proposals with at least VOTE_THRESHOLD reviews and both a +1 and -1, sorted by total
 
        # votes (highest first)
 
        "controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD,
 
                                         result__plus_one__gt=0, result__minus_one__gt=0)
 
        .order_by("-result__vote_count"),
 
        # proposals with fewer than VOTE_THRESHOLD reviews
 
        "too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD).order_by("result__vote_count"),
 
        "too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD)
 
        .order_by("result__vote_count"),
 
    }
 

	
 
    admin = request.user.has_perm("reviews.can_manage_%s" % section_slug)
 

	
 
    for status in proposals:
 
        if key and key != status:
 
            continue
 
        proposals[status] = list(proposals_generator(request, proposals[status], check_speaker=not admin))
 
        proposals[status] = list(proposals_generator(request, proposals[status],
 
                                                     check_speaker=not admin))
 

	
 
    if key:
 
        ctx.update({
 
            "key": key,
 
            "proposals": proposals[key],
 
        })
 
    else:
 
        ctx["proposals"] = proposals
...
 
@@ -356,24 +367,23 @@ def review_assignments(request):
 
    return render(request, "reviews/review_assignment.html", {
 
        "assignments": assignments,
 
    })
 

	
 

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

	
 

	
 
@login_required
 
def review_bulk_accept(request, section_slug):
 
    if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
 
        return access_not_permitted(request)
 
    if request.method == "POST":
...
 
@@ -393,17 +403,19 @@ def review_bulk_accept(request, section_slug):
 
    })
 

	
 

	
 
@login_required
 
def result_notification(request, section_slug, status):
 
    if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
 
        return access_not_permitted(request)
 

	
 
    proposals = ProposalBase.objects.filter(kind__section__slug=section_slug, result__status=status).select_related("speaker__user", "result").select_subclasses()
 
    proposals = ProposalBase.objects.filter(kind__section__slug=section_slug,
 
                                            result__status=status)\
 
        .select_related("speaker__user", "result").select_subclasses()
 
    notification_templates = NotificationTemplate.objects.all()
 

	
 
    ctx = {
 
        "section_slug": section_slug,
 
        "status": status,
 
        "proposals": proposals,
 
        "notification_templates": notification_templates,
 
    }
symposion/schedule/models.py
Show inline comments
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.db import models
 

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

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

	
 

	
 
class Schedule(models.Model):
 

	
 
    section = models.OneToOneField(Section)
...
 
@@ -122,17 +121,18 @@ 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)
 
    description = MarkupField()
 
    abstract = MarkupField()
 
    speaker = models.ForeignKey("speakers.Speaker", related_name="presentations")
 
    additional_speakers = models.ManyToManyField("speakers.Speaker", related_name="copresentations", blank=True)
 
    additional_speakers = models.ManyToManyField("speakers.Speaker", related_name="copresentations",
 
                                                 blank=True)
 
    cancelled = models.BooleanField(default=False)
 
    proposal_base = models.OneToOneField(ProposalBase, related_name="presentation")
 
    section = models.ForeignKey(Section, related_name="presentations")
 

	
 
    @property
 
    def number(self):
 
        return self.proposal.number
 

	
symposion/schedule/timetable.py
Show inline comments
 
import itertools
 
import operator
 

	
 
from django.db.models import Count, Min
 

	
 
from symposion.schedule.models import Room, Slot, SlotRoom
 

	
 

	
 
class TimeTable(object):
 

	
...
 
@@ -14,17 +13,18 @@ class TimeTable(object):
 
    def slots_qs(self):
 
        qs = Slot.objects.all()
 
        qs = qs.filter(day=self.day)
 
        return qs
 

	
 
    def rooms(self):
 
        qs = Room.objects.all()
 
        qs = qs.filter(schedule=self.day.schedule)
 
        qs = qs.filter(pk__in=SlotRoom.objects.filter(slot__in=self.slots_qs().values("pk")).values("room"))
 
        qs = qs.filter(
 
            pk__in=SlotRoom.objects.filter(slot__in=self.slots_qs().values("pk")).values("room"))
 
        qs = qs.order_by("order")
 
        return qs
 

	
 
    def __iter__(self):
 
        times = sorted(set(itertools.chain(*self.slots_qs().values_list("start", "end"))))
 
        slots = Slot.objects.filter(pk__in=self.slots_qs().values("pk"))
 
        slots = slots.annotate(room_count=Count("slotroom"), order=Min("slotroom__room__order"))
 
        slots = slots.order_by("start", "order")
symposion/schedule/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import url, patterns
 

	
 

	
 
urlpatterns = patterns("symposion.schedule.views",
 
    url(r"^$", "schedule_conference", name="schedule_conference"),
 
    url(r"^edit/$", "schedule_edit", name="schedule_edit"),
 
    url(r"^list/$", "schedule_list", name="schedule_list"),
 
    url(r"^presentations.csv$", "schedule_list_csv", name="schedule_list_csv"),
symposion/schedule/views.py
Show inline comments
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.http import Http404, HttpResponse
 
from django.shortcuts import render, get_object_or_404, redirect
 
from django.template import loader, Context
 

	
 
from django.contrib.auth.decorators import login_required
 

	
 
from symposion.schedule.forms import SlotEditForm
 
from symposion.schedule.models import Schedule, Day, Slot, Presentation
symposion/speakers/admin.py
Show inline comments
 
from django.contrib import admin
 

	
 
from symposion.speakers.models import Speaker
 

	
 

	
 
admin.site.register(Speaker,
 
                    list_display=["name", "email", "created"],
 
    search_fields = ["name"],
 
)
...
 
\ No newline at end of file
 
                    search_fields=["name"])
symposion/speakers/fixture_gen.py
Show inline comments
...
 
@@ -14,16 +14,16 @@ def speakers():
 
    Speaker.objects.create(
 
        user=guido,
 
        name="Guido van Rossum",
 
        biography="I wrote Python, and named it after Monty Python",
 
    )
 
    Speaker.objects.create(
 
        user=matz,
 
        name="Yukihiro Matsumoto",
 
        biography="I wrote Ruby, and named it after the rare gem Ruby, a pun "
 
            "on Perl/pearl.",
 
        biography=("I wrote Ruby, and named it after the rare gem Ruby, a pun "
 
                   "on Perl/pearl."),
 
    )
 
    Speaker.objects.create(
 
        user=larry,
 
        name="Larry Wall",
 
        biography="I wrote Perl, and named it after the Parable of the Pearl",
 
    )
symposion/speakers/management/commands/export_speaker_data.py
Show inline comments
 
import csv
 
import os
 

	
 
from django.core.management.base import BaseCommand, CommandError
 
from django.core.management.base import BaseCommand
 

	
 
from symposion.speakers.models import Speaker
 

	
 

	
 
class Command(BaseCommand):
 

	
 
    def handle(self, *args, **options):
 
        csv_file = csv.writer(open(os.path.join(os.getcwd(), "speakers.csv"), "wb"))
symposion/speakers/models.py
Show inline comments
...
 
@@ -11,18 +11,22 @@ from markitup.fields import MarkupField
 
class Speaker(models.Model):
 

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

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

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

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

	
 
    @property
 
    def email(self):
 
        if self.user is not None:
 
            return self.user.email
 
        else:
symposion/speakers/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import *
 

	
 

	
 
urlpatterns = patterns("symposion.speakers.views",
 
    url(r"^create/$", "speaker_create", name="speaker_create"),
 
    url(r"^create/(\w+)/$", "speaker_create_token", name="speaker_create_token"),
 
    url(r"^edit/(?:(?P<pk>\d+)/)?$", "speaker_edit", name="speaker_edit"),
 
    url(r"^profile/(?P<pk>\d+)/$", "speaker_profile", name="speaker_profile"),
symposion/speakers/views.py
Show inline comments
...
 
@@ -83,18 +83,18 @@ def speaker_create_token(request, token):
 
        else:
 
            del request.session["pending-token"]
 
            additional_speakers = ProposalBase.additional_speakers.through
 
            additional_speakers._default_manager.filter(
 
                speaker=speaker
 
            ).update(
 
                speaker=existing_speaker
 
            )
 
            messages.info(request, "You have been associated with all pending "
 
                "talk proposals")
 
            messages.info(request, ("You have been associated with all pending "
 
                                    "talk proposals"))
 
            return redirect("dashboard")
 
    else:
 
        if not request.user.is_authenticated():
 
            return redirect("account_login")
 
    return redirect("speaker_create")
 

	
 

	
 
@login_required
symposion/sponsorship/admin.py
Show inline comments
 
from django.contrib import admin
 

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

	
 

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

	
 

	
 
class SponsorBenefitInline(admin.StackedInline):
symposion/sponsorship/management/commands/reset_sponsor_benefits.py
Show inline comments
 
from django.core.management.base import BaseCommand
 

	
 
from django.contrib.auth.models import Group
 

	
 
from symposion.sponsorship.models import Sponsor, SponsorBenefit
 
from symposion.sponsorship.models import Sponsor, SponsorBenefit, SponsorLevel
 

	
 

	
 
class Command(BaseCommand):
 

	
 
    def handle(self, *args, **options):
 
        for sponsor in Sponsor.objects.all():
 
            level = None
 
            try:
symposion/sponsorship/models.py
Show inline comments
...
 
@@ -30,29 +30,31 @@ class SponsorLevel(models.Model):
 
        return self.name
 

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

	
 

	
 
class Sponsor(models.Model):
 

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

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

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

	
 
    objects = SponsorManager()
 

	
 
    def __unicode__(self):
 
        return self.name
 

	
 
    class Meta:
 
        verbose_name = _("sponsor")
...
 
@@ -61,17 +63,18 @@ class Sponsor(models.Model):
 
    def get_absolute_url(self):
 
        if self.active:
 
            return reverse("sponsor_detail", kwargs={"pk": self.pk})
 
        return reverse("sponsor_list")
 

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

	
 
    @property
 
    def listing_text(self):
...
 
@@ -115,17 +118,18 @@ class Sponsor(models.Model):
 
                # and won't raise a validation error until it's next
 
                # edited.
 
                sponsor_benefit.save()
 

	
 
                allowed_benefits.append(sponsor_benefit.pk)
 

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

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

	
 

	
 
def _store_initial_level(sender, instance, **kwargs):
 
    if instance:
 
        instance._initial_level_id = instance.level_id
...
 
@@ -145,17 +149,18 @@ BENEFIT_TYPE_CHOICES = [
 
    ("simple", "Simple")
 
]
 

	
 

	
 
class Benefit(models.Model):
 

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

	
 
    def __unicode__(self):
 
        return self.name
 

	
 

	
 
class BenefitLevel(models.Model):
 

	
 
    benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit"))
symposion/sponsorship/templatetags/sponsorship_tags.py
Show inline comments
...
 
@@ -25,19 +25,22 @@ class SponsorsNode(template.Node):
 
        else:
 
            self.level = None
 
        self.context_var = context_var
 

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

	
 

	
 
class SponsorLevelNode(template.Node):
 

	
 
    @classmethod
 
    def handle_token(cls, parser, token):
...
 
@@ -67,9 +70,8 @@ def sponsors(parser, token):
 

	
 

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

	
 

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

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

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

	
 

	
 
@login_required
 
def sponsor_apply(request):
 
    if request.method == "POST":
 
        form = SponsorApplicationForm(request.POST, user=request.user)
 
        if form.is_valid():
symposion/teams/admin.py
Show inline comments
 
from django.contrib import admin
 

	
 
import reversion
 

	
 
from symposion.teams.models import Team, Membership
 

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

	
 

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

	
 
admin.site.register(Membership, MembershipAdmin)
symposion/teams/forms.py
Show inline comments
...
 
@@ -5,17 +5,18 @@ from django.utils.safestring import mark_safe
 

	
 
from django.contrib.auth.models import User
 

	
 
from symposion.teams.models import Membership
 

	
 

	
 
class TeamInvitationForm(forms.Form):
 

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

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

	
 
    def clean(self):
 
        cleaned_data = super(TeamInvitationForm, self).clean()
 
        email = cleaned_data.get("email")
...
 
@@ -23,17 +24,19 @@ class TeamInvitationForm(forms.Form):
 
        if email is None:
 
            raise forms.ValidationError("valid email address required")
 

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

	
 
        state = self.team.get_state_for_user(user)
 

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

	
 
        if state in ["invited"]:
 
            raise forms.ValidationError("user already invited to team")
symposion/teams/models.py
Show inline comments
...
 
@@ -20,17 +20,18 @@ class Team(models.Model):
 
    name = models.CharField(max_length=100)
 
    description = models.TextField(blank=True)
 
    access = models.CharField(max_length=20, choices=TEAM_ACCESS_CHOICES)
 

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

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

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

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

	
 
    def __unicode__(self):
symposion/teams/urls.py
Show inline comments
 
# flake8: noqa
 
from django.conf.urls.defaults import *
 

	
 

	
 
urlpatterns = patterns("symposion.teams.views",
 
    # team specific
 
    url(r"^(?P<slug>[\w\-]+)/$", "team_detail", name="team_detail"),
 
    url(r"^(?P<slug>[\w\-]+)/join/$", "team_join", name="team_join"),
 
    url(r"^(?P<slug>[\w\-]+)/leave/$", "team_leave", name="team_leave"),
symposion/teams/views.py
Show inline comments
...
 
@@ -5,17 +5,17 @@ from django.contrib.auth.decorators import login_required
 
from django.contrib import messages
 

	
 
from symposion.utils.mail import send_email
 

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

	
 

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

	
 
def can_join(team, user):
 
    state = team.get_state_for_user(user)
 
    if team.access == "open" and state is None:
 
        return True
 
    elif state == "invited":
...
 
@@ -45,17 +45,17 @@ def can_apply(team, user):
 
def can_invite(team, user):
 
    state = team.get_state_for_user(user)
 
    if team.access == "invitation":
 
        if state == "manager" or user.is_staff:
 
            return True
 
    return False
 

	
 

	
 
## views
 
# views
 

	
 

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