diff --git a/symposion/boxes/authorization.py b/symposion/boxes/authorization.py
index 011e215d30915b4509d5329536e65731bace22b6..e898b0cf422eee1495383f4ca5811292c47e550e 100644
--- a/symposion/boxes/authorization.py
+++ b/symposion/boxes/authorization.py
@@ -13,8 +13,8 @@ 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)
diff --git a/symposion/boxes/forms.py b/symposion/boxes/forms.py
index b5ad3b32d7bea7b84cdec075f514edd3476cd230..7f881acce2e0405ddda430b39c73485fefedfcc2 100644
--- a/symposion/boxes/forms.py
+++ b/symposion/boxes/forms.py
@@ -4,7 +4,7 @@ from symposion.boxes.models import Box
class BoxForm(forms.ModelForm):
-
+
class Meta:
model = Box
fields = ["content"]
diff --git a/symposion/boxes/models.py b/symposion/boxes/models.py
index c83cadcb6b9d3bcfcaeff2411f2742a37d86cc80..f12e5c9511a1ae9f493823505c1c7451b5e27096 100644
--- a/symposion/boxes/models.py
+++ b/symposion/boxes/models.py
@@ -1,5 +1,3 @@
-import datetime
-
from django.db import models
from django.contrib.auth.models import User
@@ -10,16 +8,16 @@ from markitup.fields import MarkupField
class Box(models.Model):
-
+
label = models.CharField(max_length=100, db_index=True)
content = MarkupField(blank=True)
-
+
created_by = models.ForeignKey(User, related_name="boxes")
last_updated_by = models.ForeignKey(User, related_name="updated_boxes")
-
+
def __unicode__(self):
return self.label
-
+
class Meta:
verbose_name_plural = "boxes"
diff --git a/symposion/boxes/templatetags/boxes_tags.py b/symposion/boxes/templatetags/boxes_tags.py
index e300fd3bc392046c6dc15f78e3b3a3e91cf3c77b..53765e567f76656992e4f032c8d4e1409b345f30 100644
--- a/symposion/boxes/templatetags/boxes_tags.py
+++ b/symposion/boxes/templatetags/boxes_tags.py
@@ -1,10 +1,5 @@
from django import template
-from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse
-from django.utils.safestring import mark_safe
-from django.utils.encoding import smart_str
-from django.utils.translation import ugettext_lazy as _
-from django.template.defaulttags import kwarg_re
from symposion.boxes.models import Box
from symposion.boxes.forms import BoxForm
@@ -16,22 +11,22 @@ register = template.Library()
@register.inclusion_tag("boxes/box.html", takes_context=True)
def box(context, label, show_edit=True, *args, **kwargs):
-
+
request = context["request"]
can_edit = load_can_edit()(request, *args, **kwargs)
-
+
try:
box = Box.objects.get(label=label)
except Box.DoesNotExist:
box = None
-
+
if can_edit and show_edit:
form = BoxForm(instance=box, prefix=label)
form_action = reverse("box_edit", args=[label])
else:
form = None
form_action = None
-
+
return {
"request": request,
"label": label,
diff --git a/symposion/boxes/urls.py b/symposion/boxes/urls.py
index dc57fe6bd7fe5147a39210669f116f9086cd5cc2..c0cf3fb13332e8fe37ce7f050d3e8b55e2f15acd 100644
--- a/symposion/boxes/urls.py
+++ b/symposion/boxes/urls.py
@@ -1,6 +1,7 @@
+# flake8: noqa
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
+)
diff --git a/symposion/boxes/views.py b/symposion/boxes/views.py
index b314b07e9f40a6c0cac83ef4c467e5647d553045..1e93675d238dbf8d4d73ac192a26bc8998465a4d 100644
--- a/symposion/boxes/views.py
+++ b/symposion/boxes/views.py
@@ -7,7 +7,8 @@ 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":
@@ -20,17 +21,17 @@ 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)
if form.is_valid():
diff --git a/symposion/cms/admin.py b/symposion/cms/admin.py
index c45e4cc114b01bfdf9f6d78ca4dcfb2c7dcf648c..83b4c1767374a699ccda7d251850f26e7e546305 100644
--- a/symposion/cms/admin.py
+++ b/symposion/cms/admin.py
@@ -4,6 +4,7 @@ import reversion
from .models import Page
+
class PageAdmin(reversion.VersionAdmin):
pass
diff --git a/symposion/cms/forms.py b/symposion/cms/forms.py
index 149ba34a5b8676ae5ecc447d3e5bad1ba838ad18..5eecca31dfdbed13dc28af737ab7f70ca437cf7d 100644
--- a/symposion/cms/forms.py
+++ b/symposion/cms/forms.py
@@ -6,7 +6,7 @@ from .models import Page
class PageForm(forms.ModelForm):
-
+
class Meta:
model = Page
fields = ["title", "body", "path"]
@@ -17,5 +17,5 @@ class PageForm(forms.ModelForm):
class FileUploadForm(forms.Form):
-
+
file = forms.FileField()
diff --git a/symposion/cms/managers.py b/symposion/cms/managers.py
index c8381ebd47f561fb33bb885988a64876077a7762..fcf4a4e1ad6dc7f53c8443635608ec0e6e0616d6 100644
--- a/symposion/cms/managers.py
+++ b/symposion/cms/managers.py
@@ -2,8 +2,9 @@ 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())
diff --git a/symposion/cms/models.py b/symposion/cms/models.py
index 4acc174a655f1d127fe23ab00c9d8cac6f2c41f9..25c1caea7792f5ed8282a4e757442aea5c0812e1 100644
--- a/symposion/cms/models.py
+++ b/symposion/cms/models.py
@@ -18,12 +18,12 @@ 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)
body = MarkupField()
@@ -32,28 +32,29 @@ class Page(models.Model):
created = models.DateTimeField(editable=False, default=datetime.datetime.now)
updated = models.DateTimeField(editable=False, default=datetime.datetime.now)
tags = TaggableManager(blank=True)
-
+
published = PublishedPageManager()
-
+
def __unicode__(self):
return self.title
-
+
@models.permalink
def get_absolute_url(self):
return ("cms_page", [self.path])
-
+
@property
def is_community(self):
return self.path.lower().startswith("community/")
-
+
def save(self, *args, **kwargs):
self.updated = datetime.datetime.now()
super(Page, self).save(*args, **kwargs)
-
+
def clean_fields(self, exclude=None):
super(Page, self).clean_fields(exclude)
if not re.match(settings.SYMPOSION_PAGE_REGEX, self.path):
- raise ValidationError({"path": [_("Path can only contain letters, numbers and hyphens and end with /")]})
+ raise ValidationError(
+ {"path": [_("Path can only contain letters, numbers and hyphens and end with /")]})
reversion.register(Page)
@@ -64,9 +65,9 @@ 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()])
diff --git a/symposion/cms/urls.py b/symposion/cms/urls.py
index 3297ce53b69a3be359d133dde12f34245613809d..be16e2cd40d37fd1850535ee015130f0e986fdf4 100644
--- a/symposion/cms/urls.py
+++ b/symposion/cms/urls.py
@@ -1,3 +1,4 @@
+# flake8: noqa
from django.conf.urls.defaults import url, patterns
PAGE_RE = r"(([\w-]{1,})(/[\w-]{1,})*)/"
diff --git a/symposion/cms/views.py b/symposion/cms/views.py
index 4a4ee610e6f4722b68baee8f68e5a7cf1e542ba4..2f58381a077c219122d207515cd6c4d8e113bc5a 100644
--- a/symposion/cms/views.py
+++ b/symposion/cms/views.py
@@ -23,20 +23,20 @@ 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:
return redirect("cms_page_edit", path=path)
else:
raise Http404
-
+
return render(request, "cms/page_detail.html", {
"page": page,
"editable": editable,
@@ -45,15 +45,15 @@ 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)
if form.is_valid():
@@ -65,7 +65,7 @@ def page_edit(request, path):
print form.errors
else:
form = PageForm(instance=page, initial={"path": path})
-
+
return render(request, "cms/page_edit.html", {
"path": path,
"form": form
@@ -75,7 +75,7 @@ def page_edit(request, path):
def file_index(request):
if not can_upload(request.user):
raise Http404
-
+
ctx = {
"files": File.objects.all(),
}
@@ -85,7 +85,7 @@ def file_index(request):
def file_create(request):
if not can_upload(request.user):
raise Http404
-
+
if request.method == "POST":
form = FileUploadForm(request.POST, request.FILES)
if form.is_valid():
@@ -97,7 +97,7 @@ def file_create(request):
return redirect("file_index")
else:
form = FileUploadForm()
-
+
ctx = {
"form": form,
}
@@ -106,7 +106,7 @@ 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()
response["X-Accel-Redirect"] = file.file.url
@@ -115,14 +115,14 @@ def file_download(request, pk, *args):
del response["content-type"]
else:
response = static.serve(request, file.file.name, document_root=settings.MEDIA_ROOT)
-
+
return response
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":
file.delete()
diff --git a/symposion/conf.py b/symposion/conf.py
index e2fb8072b613b812f51b1b5a7045976c1752dacb..dd2a6d5035bad4b8c1f13f834322835a17b4351e 100644
--- a/symposion/conf.py
+++ b/symposion/conf.py
@@ -1,8 +1,6 @@
-from django.conf import settings
-
from appconf import AppConf
class SymposionAppConf(AppConf):
-
+
VOTE_THRESHOLD = 3
diff --git a/symposion/conference/admin.py b/symposion/conference/admin.py
index 7f0b55c21da382ce5f99e0b458ed7bfd730ea1bc..c8e57441b7da5e0dbc0b5602a8bc0a40d0fd5b43 100644
--- a/symposion/conference/admin.py
+++ b/symposion/conference/admin.py
@@ -6,6 +6,6 @@ from symposion.conference.models import Conference, Section
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")
)
diff --git a/symposion/conference/models.py b/symposion/conference/models.py
index 4bb6b8ce256d9787fd6b63c0afa4c01543a8b780..354403b67721257d53c9cb6b4cbec0d97ead383d 100644
--- a/symposion/conference/models.py
+++ b/symposion/conference/models.py
@@ -11,24 +11,24 @@ 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
super(Conference, self).delete()
@@ -36,7 +36,7 @@ class Conference(models.Model):
del CONFERENCE_CACHE[pk]
except KeyError:
pass
-
+
class Meta(object):
verbose_name = _("conference")
verbose_name_plural = _("conferences")
@@ -48,19 +48,19 @@ class Section(models.Model):
"Talks", "Expo", "Sprints", that may have its own review and
scheduling process.
"""
-
+
conference = models.ForeignKey(Conference, verbose_name=_("conference"))
-
+
name = models.CharField(_("name"), max_length=100)
slug = models.SlugField()
# when the section runs
start_date = models.DateField(_("start date"), null=True, blank=True)
end_date = models.DateField(_("end date"), null=True, blank=True)
-
+
def __unicode__(self):
return "%s %s" % (self.conference, self.name)
-
+
class Meta(object):
verbose_name = _("section")
verbose_name_plural = _("sections")
diff --git a/symposion/conference/urls.py b/symposion/conference/urls.py
index 6bd44c16c49fac3f86d32543e4a729fc35f15fb2..f2cdd52b55e521b4729dc41a0f16f7befb6de4b0 100644
--- a/symposion/conference/urls.py
+++ b/symposion/conference/urls.py
@@ -1,4 +1,5 @@
-from django.conf.urls.defaults import *
+# flake8: noqa
+from django.conf.urls.defaults import patterns, url
urlpatterns = patterns("symposion.conference.views",
diff --git a/symposion/conference/views.py b/symposion/conference/views.py
index 7edf644a922d323290e71054617978e91da256d4..3be21e31c4c2e47f27da77ac3bfc8315b1b7106d 100644
--- a/symposion/conference/views.py
+++ b/symposion/conference/views.py
@@ -7,10 +7,10 @@ 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(),
})
diff --git a/symposion/forms.py b/symposion/forms.py
index 0a623606828b87b372f69562af7274c0fcf37f63..7799e06aef6193727445c915dd7e51e8235c4c07 100644
--- a/symposion/forms.py
+++ b/symposion/forms.py
@@ -4,7 +4,7 @@ import account.forms
class SignupForm(account.forms.SignupForm):
-
+
first_name = forms.CharField()
last_name = forms.CharField()
email_confirm = forms.EmailField(label="Confirm Email")
@@ -20,11 +20,12 @@ class SignupForm(account.forms.SignupForm):
"password",
"password_confirm"
]
-
+
def clean_email_confirm(self):
email = self.cleaned_data.get("email")
email_confirm = self.cleaned_data["email_confirm"]
if email:
if email != email_confirm:
- raise forms.ValidationError("Email address must match previously typed email address")
+ raise forms.ValidationError(
+ "Email address must match previously typed email address")
return email_confirm
diff --git a/symposion/markdown_parser.py b/symposion/markdown_parser.py
index 07216c2aa8aa40126f1670275c6fc0c548b6a55c..178708f655f9824bf2c6cfca7be4f0d82a3d9748 100644
--- a/symposion/markdown_parser.py
+++ b/symposion/markdown_parser.py
@@ -1,14 +1,13 @@
-import html5lib
from html5lib import html5parser, sanitizer
import markdown
def parse(text):
-
+
# First run through the Markdown parser
text = markdown.markdown(text, extensions=["extra"], safe_mode=False)
-
+
# Sanitize using html5lib
bits = []
parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer)
diff --git a/symposion/proposals/actions.py b/symposion/proposals/actions.py
index e46e252434505e0a85db4746273a17613628a563..3dcfb1681da164932de83b32a994b868955d759e 100644
--- a/symposion/proposals/actions.py
+++ b/symposion/proposals/actions.py
@@ -3,9 +3,8 @@ import csv
from django.http import HttpResponse
-def export_as_csv_action(
- description="Export selected objects as CSV file",
- fields=None, exclude=None, header=True):
+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
@@ -24,12 +23,14 @@ def export_as_csv_action(
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
diff --git a/symposion/proposals/forms.py b/symposion/proposals/forms.py
index 3f3fe115b4f009a52310d0fbe31c46c940191d9c..91c7e0042bc4ba5cd8a99af80e181d550ca4c347 100644
--- a/symposion/proposals/forms.py
+++ b/symposion/proposals/forms.py
@@ -9,15 +9,15 @@ 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"]
exists = self.proposal.additional_speakers.filter(
@@ -32,7 +32,7 @@ class AddSpeakerForm(forms.Form):
class SupportingDocumentCreateForm(forms.ModelForm):
-
+
class Meta:
model = SupportingDocument
fields = [
diff --git a/symposion/proposals/managers.py b/symposion/proposals/managers.py
deleted file mode 100644
index b908c7b68edacd41b8bde56e20b4b8053b4430b3..0000000000000000000000000000000000000000
--- a/symposion/proposals/managers.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from django.db import models
-from django.db.models.query import QuerySet
-
-
-class CachingM2MQuerySet(QuerySet):
-
- def __init__(self, *args, **kwargs):
- super(CachingM2MQuerySet, self).__init__(*args, **kwargs)
- self.cached_m2m_field = kwargs["m2m_field"]
-
- def iterator(self):
- parent_iter = super(CachingM2MQuerySet, self).iterator()
- m2m_model = getattr(self.model, self.cached_m2m_field).through
-
- for obj in parent_iter:
- if obj.id in cached_objects:
- setattr(obj, "_cached_m2m_%s" % self.cached_m2m_field)
- yield obj
-
-
-class ProposalManager(models.Manager):
- def cache_m2m(self, m2m_field):
- return CachingM2MQuerySet(self.model, using=self._db, m2m_field=m2m_field)
- AdditionalSpeaker = queryset.model.additional_speakers.through
- additional_speakers = collections.defaultdict(set)
- for additional_speaker in AdditionalSpeaker._default_manager.filter(proposal__in=queryset).select_related("speaker__user"):
- additional_speakers[additional_speaker.proposal_id].add(additional_speaker.speaker)
\ No newline at end of file
diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py
index e70af55035d5caef2d5362dde52cefdd7c19637b..c12c5bcc768ca32c43c075da355f6ddd3af18e4c 100644
--- a/symposion/proposals/models.py
+++ b/symposion/proposals/models.py
@@ -21,20 +21,20 @@ from symposion.conference.models import Section
class ProposalSection(models.Model):
"""
configuration of proposal submissions for a specific Section.
-
+
a section is available for proposals iff:
* it is after start (if there is one) and
* it is before end (if there is one) and
* closed is NULL or False
"""
-
+
section = models.OneToOneField(Section)
-
+
start = models.DateTimeField(null=True, blank=True)
end = models.DateTimeField(null=True, blank=True)
closed = models.NullBooleanField()
published = models.NullBooleanField()
-
+
@classmethod
def available(cls):
now = datetime.datetime.now()
@@ -43,7 +43,7 @@ class ProposalSection(models.Model):
Q(end__gt=now) | Q(end=None),
Q(closed=False) | Q(closed=None),
)
-
+
def is_available(self):
if self.closed:
return False
@@ -53,7 +53,7 @@ class ProposalSection(models.Model):
if self.end and self.end < now:
return False
return True
-
+
def __unicode__(self):
return self.section.name
@@ -61,68 +61,77 @@ class ProposalSection(models.Model):
class ProposalKind(models.Model):
"""
e.g. talk vs panel vs tutorial vs poster
-
+
Note that if you have different deadlines, reviewers, etc. you'll want
to distinguish the section as well as the kind.
"""
-
+
section = models.ForeignKey(Section, related_name="proposal_kinds")
-
+
name = models.CharField(_("Name"), max_length=100)
slug = models.SlugField()
-
+
def __unicode__(self):
return self.name
class ProposalBase(models.Model):
-
+
objects = InheritanceManager()
-
+
kind = models.ForeignKey(ProposalKind)
-
+
title = models.CharField(max_length=100)
description = models.TextField(
_("Brief Description"),
max_length=400, # @@@ need to enforce 400 in UI
- help_text="If your proposal is accepted this will be made public and printed in the program. Should be one paragraph, maximum 400 characters."
+ help_text=_("If your proposal is accepted this will be made public and printed in the "
+ "program. Should be one paragraph, maximum 400 characters.")
)
abstract = MarkupField(
_("Detailed Abstract"),
- help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit using Markdown.")
+ help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit "
+ "using Markdown.")
)
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 Markdown.")
+ 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 "
+ "Markdown.")
)
submitted = models.DateTimeField(
default=datetime.datetime.now,
editable=False,
)
speaker = models.ForeignKey("speakers.Speaker", related_name="proposals")
- additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker", blank=True)
+ additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker",
+ blank=True)
cancelled = models.BooleanField(default=False)
-
+
def can_edit(self):
return True
-
+
@property
def section(self):
return self.kind.section
-
+
@property
def speaker_email(self):
return self.speaker.email
-
+
@property
def number(self):
return str(self.pk).zfill(3)
-
+
def speakers(self):
yield self.speaker
- for speaker in self.additional_speakers.exclude(additionalspeaker__status=AdditionalSpeaker.SPEAKING_STATUS_DECLINED):
+ speakers = self.additional_speakers.exclude(
+ additionalspeaker__status=AdditionalSpeaker.SPEAKING_STATUS_DECLINED)
+ for speaker in speakers:
yield speaker
-
+
def notification_email_context(self):
return {
"title": self.title,
@@ -135,21 +144,21 @@ reversion.register(ProposalBase)
class AdditionalSpeaker(models.Model):
-
+
SPEAKING_STATUS_PENDING = 1
SPEAKING_STATUS_ACCEPTED = 2
SPEAKING_STATUS_DECLINED = 3
-
+
SPEAKING_STATUS = [
(SPEAKING_STATUS_PENDING, _("Pending")),
(SPEAKING_STATUS_ACCEPTED, _("Accepted")),
(SPEAKING_STATUS_DECLINED, _("Declined")),
]
-
+
speaker = models.ForeignKey("speakers.Speaker")
proposalbase = models.ForeignKey(ProposalBase)
status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING)
-
+
class Meta:
db_table = "proposals_proposalbase_additional_speakers"
unique_together = ("speaker", "proposalbase")
@@ -162,14 +171,15 @@ 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()])
diff --git a/symposion/proposals/templatetags/proposal_tags.py b/symposion/proposals/templatetags/proposal_tags.py
index 2f728847e9d55a284999a9f0a032cd2f6ed7c651..f9581d3511ceb51747994f80ac9b2b373f0e8a50 100644
--- a/symposion/proposals/templatetags/proposal_tags.py
+++ b/symposion/proposals/templatetags/proposal_tags.py
@@ -7,7 +7,7 @@ register = template.Library()
class AssociatedProposalsNode(template.Node):
-
+
@classmethod
def handle_token(cls, parser, token):
bits = token.split_contents()
@@ -15,10 +15,10 @@ class AssociatedProposalsNode(template.Node):
return cls(bits[2])
else:
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
-
+
def __init__(self, context_var):
self.context_var = context_var
-
+
def render(self, context):
request = context["request"]
if request.user.speaker_profile:
@@ -32,7 +32,7 @@ class AssociatedProposalsNode(template.Node):
class PendingProposalsNode(template.Node):
-
+
@classmethod
def handle_token(cls, parser, token):
bits = token.split_contents()
@@ -40,10 +40,10 @@ class PendingProposalsNode(template.Node):
return cls(bits[2])
else:
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
-
+
def __init__(self, context_var):
self.context_var = context_var
-
+
def render(self, context):
request = context["request"]
if request.user.speaker_profile:
@@ -70,4 +70,3 @@ def associated_proposals(parser, token):
{% associated_proposals as associated_proposals %}
"""
return AssociatedProposalsNode.handle_token(parser, token)
-
diff --git a/symposion/proposals/urls.py b/symposion/proposals/urls.py
index 85e2bb1c5debb8401c38f8a2f3ca926244a939a2..c2efdcb538aa9ce1bbe5a1f15afd13c09ff4ed56 100644
--- a/symposion/proposals/urls.py
+++ b/symposion/proposals/urls.py
@@ -1,3 +1,4 @@
+# flake8: noqa
from django.conf.urls.defaults import *
@@ -11,7 +12,7 @@ urlpatterns = patterns("symposion.proposals.views",
url(r"^(\d+)/leave/$", "proposal_leave", name="proposal_leave"),
url(r"^(\d+)/join/$", "proposal_pending_join", name="proposal_pending_join"),
url(r"^(\d+)/decline/$", "proposal_pending_decline", name="proposal_pending_decline"),
-
+
url(r"^(\d+)/document/create/$", "document_create", name="proposal_document_create"),
url(r"^document/(\d+)/delete/$", "document_delete", name="proposal_document_delete"),
url(r"^document/(\d+)/([^/]+)$", "document_download", name="proposal_document_download"),
diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py
index cf532c2b5bc5c712db723b407f0206325ec19b4f..dea71b2005220d190e3aef854bf42a01f3cb9ab2 100644
--- a/symposion/proposals/views.py
+++ b/symposion/proposals/views.py
@@ -37,21 +37,21 @@ def proposal_submit(request):
request.user.speaker_profile
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,
})
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?
else:
@@ -59,12 +59,12 @@ def proposal_submit_kind(request, kind_slug):
speaker_profile = request.user.speaker_profile
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)
if form.is_valid():
@@ -79,7 +79,7 @@ def proposal_submit_kind(request, kind_slug):
return redirect("dashboard")
else:
form = form_class()
-
+
return render(request, "proposals/proposal_submit_kind.html", {
"kind": kind,
"form": form,
@@ -91,17 +91,17 @@ def proposal_speaker_manage(request, pk):
queryset = ProposalBase.objects.select_related("speaker")
proposal = get_object_or_404(queryset, pk=pk)
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
-
+
if proposal.speaker != request.user.speaker_profile:
raise Http404()
-
+
if request.method == "POST":
add_speaker_form = AddSpeakerForm(request.POST, proposal=proposal)
if add_speaker_form.is_valid():
message_ctx = {
"proposal": proposal,
}
-
+
def create_speaker_token(email_address):
# create token and look for an existing speaker to prevent
# duplicate tokens and confusing the pending speaker
@@ -135,13 +135,13 @@ def proposal_speaker_manage(request, pk):
# fire off email to user to create profile
send_email(
[email_address], "speaker_no_profile",
- context = message_ctx
+ context=message_ctx
)
else:
# fire off email to user letting them they are loved.
send_email(
[email_address], "speaker_addition",
- context = message_ctx
+ context=message_ctx
)
else:
speaker, token = create_speaker_token(email_address)
@@ -150,9 +150,10 @@ def proposal_speaker_manage(request, pk):
# account and speaker profile
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)
else:
@@ -173,14 +174,14 @@ def proposal_edit(request, pk):
if request.user != proposal.speaker.user:
raise Http404()
-
+
if not proposal.can_edit():
ctx = {
"title": "Proposal editing closed",
"body": "Proposal editing is closed for this session type."
}
return render(request, "proposals/proposal_error.html", ctx)
-
+
form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug])
if request.method == "POST":
@@ -206,7 +207,7 @@ def proposal_edit(request, pk):
return redirect("proposal_detail", proposal.pk)
else:
form = form_class(instance=proposal)
-
+
return render(request, "proposals/proposal_edit.html", {
"proposal": proposal,
"form": form,
@@ -218,22 +219,22 @@ def proposal_detail(request, pk):
queryset = ProposalBase.objects.select_related("speaker", "speaker__user")
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
message_form = SpeakerCommentForm()
if request.method == "POST":
message_form = SpeakerCommentForm(request.POST)
if message_form.is_valid():
-
+
message = message_form.save(commit=False)
message.user = request.user
message.proposal = proposal
message.save()
-
+
ProposalMessage = SpeakerCommentForm.Meta.model
reviewers = User.objects.filter(
id__in=ProposalMessage.objects.filter(
@@ -242,7 +243,7 @@ def proposal_detail(request, pk):
user=request.user
).distinct().values_list("user", flat=True)
)
-
+
for reviewer in reviewers:
ctx = {
"proposal": proposal,
@@ -253,13 +254,13 @@ def proposal_detail(request, pk):
[reviewer.email], "proposal_new_message",
context=ctx
)
-
+
return redirect(request.path)
else:
message_form = SpeakerCommentForm()
else:
message_form = None
-
+
return render(request, "proposals/proposal_detail.html", {
"proposal": proposal,
"message_form": message_form
@@ -271,7 +272,7 @@ def proposal_cancel(request, pk):
queryset = ProposalBase.objects.select_related("speaker")
proposal = get_object_or_404(queryset, pk=pk)
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
-
+
if proposal.speaker.user != request.user:
return HttpResponseForbidden()
@@ -281,7 +282,7 @@ def proposal_cancel(request, pk):
# @@@ fire off email to submitter and other speakers
messages.success(request, "%s has been cancelled" % proposal.title)
return redirect("dashboard")
-
+
return render(request, "proposals/proposal_cancel.html", {
"proposal": proposal,
})
@@ -311,7 +312,8 @@ def proposal_leave(request, pk):
@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()
@@ -324,7 +326,8 @@ def proposal_pending_join(request, pk):
@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()
@@ -339,10 +342,10 @@ def document_create(request, proposal_pk):
queryset = ProposalBase.objects.select_related("speaker")
proposal = get_object_or_404(queryset, pk=proposal_pk)
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
-
+
if proposal.cancelled:
return HttpResponseForbidden()
-
+
if request.method == "POST":
form = SupportingDocumentCreateForm(request.POST, request.FILES)
if form.is_valid():
@@ -353,7 +356,7 @@ def document_create(request, proposal_pk):
return redirect("proposal_detail", proposal.pk)
else:
form = SupportingDocumentCreateForm()
-
+
return render(request, "proposals/document_create.html", {
"proposal": proposal,
"form": form,
@@ -378,8 +381,8 @@ def document_download(request, pk, *args):
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)
diff --git a/symposion/reviews/context_processors.py b/symposion/reviews/context_processors.py
index 6e70715b7ea3dc4dffc4835e2ecab07a15b25e0e..02850f15a791baa55b7d4a54618e0af010a3b4f1 100644
--- a/symposion/reviews/context_processors.py
+++ b/symposion/reviews/context_processors.py
@@ -1,5 +1,3 @@
-from django.contrib.contenttypes.models import ContentType
-
from symposion.proposals.models import ProposalSection
diff --git a/symposion/reviews/forms.py b/symposion/reviews/forms.py
index b9eee672e964d6c76d74fc71b78e4c2ce28168f0..a705333cad2cf5e2210c8efd5e34019d29438495 100644
--- a/symposion/reviews/forms.py
+++ b/symposion/reviews/forms.py
@@ -9,13 +9,13 @@ class ReviewForm(forms.ModelForm):
class Meta:
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
)
@@ -23,14 +23,14 @@ class ReviewCommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ["text"]
- widgets = { "text": MarkItUpWidget() }
+ widgets = {"text": MarkItUpWidget()}
class SpeakerCommentForm(forms.ModelForm):
class Meta:
model = ProposalMessage
fields = ["message"]
- widgets = { "message": MarkItUpWidget() }
+ widgets = {"message": MarkItUpWidget()}
class BulkPresentationForm(forms.Form):
diff --git a/symposion/reviews/management/commands/assign_reviewers.py b/symposion/reviews/management/commands/assign_reviewers.py
index 334eab3049bed783b7810114413b936285b84c32..20594f4cc8bc7cf355b80c76bfbfe8bccee5e78d 100644
--- a/symposion/reviews/management/commands/assign_reviewers.py
+++ b/symposion/reviews/management/commands/assign_reviewers.py
@@ -1,8 +1,3 @@
-import csv
-import os
-import random
-
-from django.contrib.auth import models
from django.core.management.base import BaseCommand
from symposion.reviews.models import ReviewAssignment
diff --git a/symposion/reviews/management/commands/calculate_results.py b/symposion/reviews/management/commands/calculate_results.py
index 45185a22551e41219699ac263a14a6d78128db2a..6a06ce8ac86497d1b8db15477ec48ad23f98260f 100644
--- a/symposion/reviews/management/commands/calculate_results.py
+++ b/symposion/reviews/management/commands/calculate_results.py
@@ -1,11 +1,9 @@
from django.core.management.base import BaseCommand
-from django.contrib.auth.models import Group
-
from symposion.reviews.models import ProposalResult
class Command(BaseCommand):
-
+
def handle(self, *args, **options):
ProposalResult.full_calculate()
diff --git a/symposion/reviews/management/commands/create_review_permissions.py b/symposion/reviews/management/commands/create_review_permissions.py
index cde2c233cdaa019b08c09f8feda32dc593db2f3b..67dfc96b51590962e68e3fc3a20709a3fc79ef74 100644
--- a/symposion/reviews/management/commands/create_review_permissions.py
+++ b/symposion/reviews/management/commands/create_review_permissions.py
@@ -7,14 +7,14 @@ from symposion.proposals.models import ProposalSection
class Command(BaseCommand):
-
+
def handle(self, *args, **options):
ct, created = ContentType.objects.get_or_create(
model="",
app_label="reviews",
defaults={"name": "reviews"}
)
-
+
for ps in ProposalSection.objects.all():
for action in ["review", "manage"]:
perm, created = Permission.objects.get_or_create(
diff --git a/symposion/reviews/management/commands/promoteproposals.py b/symposion/reviews/management/commands/promoteproposals.py
index 72c6faabb04729eb1c065d14844c1440d7f02ba7..e62c6903f7a9e9b370badb0edc8b20c34ce22409 100644
--- a/symposion/reviews/management/commands/promoteproposals.py
+++ b/symposion/reviews/management/commands/promoteproposals.py
@@ -5,11 +5,12 @@ 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))")
diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py
index 64d6db8d93ded2690b2874d33423c343e50a7e45..b601e0c283d475e9dc6c4165d8a6d09533280cc9 100644
--- a/symposion/reviews/models.py
+++ b/symposion/reviews/models.py
@@ -15,11 +15,11 @@ 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
@@ -29,7 +29,7 @@ class Votes(object):
PLUS_ZERO = "+0"
MINUS_ZERO = u"−0"
MINUS_ONE = u"−1"
-
+
CHOICES = [
(PLUS_ONE, u"+1 — Good proposal and I will argue for it to be accepted."),
(PLUS_ZERO, u"+0 — OK proposal, but I will not argue for it to be accepted."),
@@ -45,21 +45,21 @@ class ReviewAssignment(models.Model):
AUTO_ASSIGNED_LATER = 2
NUM_REVIEWERS = 3
-
+
ORIGIN_CHOICES = [
(AUTO_ASSIGNED_INITIAL, "auto-assigned, initial"),
(OPT_IN, "opted-in"),
(AUTO_ASSIGNED_LATER, "auto-assigned, later"),
]
-
+
proposal = models.ForeignKey("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):
speakers = [proposal.speaker] + list(proposal.additional_speakers.all())
@@ -94,7 +94,7 @@ class ReviewAssignment(models.Model):
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)
@@ -104,24 +104,24 @@ 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.
vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES)
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,
)
)
if not created:
@@ -130,7 +130,7 @@ class Review(models.Model):
else:
self.proposal.result.update_vote(self.vote)
super(Review, self).save(**kwargs)
-
+
def delete(self):
model = self.__class__
user_reviews = model._default_manager.filter(
@@ -152,7 +152,8 @@ class Review(models.Model):
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(
@@ -166,7 +167,7 @@ class Review(models.Model):
self.proposal.result.save()
# in all cases we need to delete the review; let's do it!
super(Review, self).delete()
-
+
def css_class(self):
return {
self.VOTES.PLUS_ONE: "plus-one",
@@ -174,7 +175,7 @@ class Review(models.Model):
self.VOTES.MINUS_ZERO: "minus-zero",
self.VOTES.MINUS_ONE: "minus-one",
}[self.vote]
-
+
@property
def section(self):
return self.proposal.kind.section.slug
@@ -182,18 +183,18 @@ 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 {
self.VOTES.PLUS_ONE: "plus-one",
@@ -223,7 +224,7 @@ class ProposalResult(models.Model):
("undecided", "undecided"),
("standby", "standby"),
], default="undecided")
-
+
@classmethod
def full_calculate(cls):
for proposal in ProposalBase.objects.all():
@@ -231,24 +232,24 @@ class ProposalResult(models.Model):
result.comment_count = Review.objects.filter(proposal=proposal).count()
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 = {
VOTES.PLUS_ONE: "plus_one",
@@ -283,7 +284,7 @@ class Comment(models.Model):
proposal = models.ForeignKey("proposals.ProposalBase", related_name="comments")
commenter = models.ForeignKey(User)
text = MarkupField()
-
+
# Or perhaps more accurately, can the user see this comment.
public = models.BooleanField(choices=[
(True, "public"),
@@ -293,7 +294,7 @@ class Comment(models.Model):
class NotificationTemplate(models.Model):
-
+
label = models.CharField(max_length=100)
from_address = models.EmailField()
subject = models.CharField(max_length=100)
@@ -301,15 +302,16 @@ 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()
from_address = models.EmailField()
subject = models.CharField(max_length=100)
body = models.TextField()
-
+
@property
def email_args(self):
return (self.subject, self.body, self.from_address, [self.to_address])
@@ -321,18 +323,18 @@ def promote_proposal(proposal):
presentation = proposal.presentation
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()
for speaker in proposal.additional_speakers.all():
presentation.additional_speakers.add(speaker)
presentation.save()
-
+
return presentation
diff --git a/symposion/reviews/templatetags/review_tags.py b/symposion/reviews/templatetags/review_tags.py
index cfb6437e289f4036fe0daa482cc1d65e5a0ad9a5..c646ca962e0005a4af7733b4e6900777e006d194 100644
--- a/symposion/reviews/templatetags/review_tags.py
+++ b/symposion/reviews/templatetags/review_tags.py
@@ -1,6 +1,6 @@
from django import template
-from symposion.reviews.models import Review, ReviewAssignment
+from symposion.reviews.models import ReviewAssignment
register = template.Library()
diff --git a/symposion/reviews/tests.py b/symposion/reviews/tests.py
index e5fe7cafdee703dcd759f14634e607c964cce53c..07e3cc995d707470fd7798a841c39974e8dab039 100644
--- a/symposion/reviews/tests.py
+++ b/symposion/reviews/tests.py
@@ -15,44 +15,44 @@ class login(object):
success,
"login with username=%r, password=%r failed" % (user, password)
)
-
+
def __enter__(self):
pass
-
+
def __exit__(self, *args):
self.testcase.client.logout()
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.
larry.groups.add(Group.objects.get(name="reviewers"))
@@ -60,10 +60,10 @@ class ReviewTests(TestCase):
response = self.get("review_detail", pk=guidos_proposal.pk)
# 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={
"vote": "+1",
@@ -72,7 +72,7 @@ class ReviewTests(TestCase):
self.assertEqual(response.status_code, 302)
# ... 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.
larry.groups.add(Group.objects.get(name="reviewers"))
@@ -90,7 +90,7 @@ class ReviewTests(TestCase):
self.assertEqual(guidos_proposal.comments.count(), 1)
comment = guidos_proposal.comments.get()
self.assertFalse(comment.public)
-
+
response = self.post("review_review", pk=guidos_proposal.pk, data={
"vote": "+1",
"text": "Actually Perl is dead, we really need a talk on the future",
@@ -100,21 +100,21 @@ class ReviewTests(TestCase):
assignment = ReviewAssignment.objects.get()
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={
"vote": "+20",
})
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.",
})
@@ -122,7 +122,7 @@ class ReviewTests(TestCase):
self.assertEqual(guidos_proposal.comments.count(), 1)
comment = guidos_proposal.comments.get()
self.assertTrue(comment.public)
-
+
larry = User.objects.get(username="larryw")
# Larry is a trustworthy guy, he's a reviewer.
larry.groups.add(Group.objects.get(name="reviewers"))
@@ -130,13 +130,13 @@ class ReviewTests(TestCase):
response = self.get("review_comment", pk=guidos_proposal.pk)
# 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."
})
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)
# Matz can't comment.
diff --git a/symposion/reviews/urls.py b/symposion/reviews/urls.py
index 564bd35ebe68774750475716d89e1215c525d3bc..3cdc6f737580b55487f228a4162e8ef597a7957c 100644
--- a/symposion/reviews/urls.py
+++ b/symposion/reviews/urls.py
@@ -1,3 +1,4 @@
+# flake8: noqa
from django.conf.urls.defaults import patterns, url
@@ -14,9 +15,9 @@ urlpatterns = patterns("symposion.reviews.views",
url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/$", "result_notification", name="result_notification"),
url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/prepare/$", "result_notification_prepare", name="result_notification_prepare"),
url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/send/$", "result_notification_send", name="result_notification_send"),
-
+
url(r"^review/(?P\d+)/$", "review_detail", name="review_detail"),
-
+
url(r"^(?P\d+)/delete/$", "review_delete", name="review_delete"),
url(r"^assignments/$", "review_assignments", name="review_assignments"),
url(r"^assignment/(?P\d+)/opt-out/$", "review_assignment_opt_out", name="review_assignment_opt_out"),
diff --git a/symposion/reviews/utils.py b/symposion/reviews/utils.py
index b91e8166b8a3ef961f09f69f9359d2da24a701e5..80e87e496fcc9c2cf7d219a05245896087359f14 100644
--- a/symposion/reviews/utils.py
+++ b/symposion/reviews/utils.py
@@ -2,16 +2,16 @@ 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.
"""
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():
diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py
index 880e9e8b4786f8285255c5b4ff06cfbc66121a16..2be745e5234fbfbce58574cf6f3317bae4f9ae72 100644
--- a/symposion/reviews/views.py
+++ b/symposion/reviews/views.py
@@ -1,5 +1,3 @@
-import re
-
from django.core.mail import send_mass_mail
from django.db.models import Q
from django.http import HttpResponseBadRequest, HttpResponseNotAllowed
@@ -27,18 +25,18 @@ def access_not_permitted(request):
def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
-
+
for obj in queryset:
# @@@ this sucks; we can do better
if check_speaker:
if request.user in [s.user for s in obj.speakers()]:
continue
-
+
try:
obj.result
except ProposalResult.DoesNotExist:
ProposalResult.objects.get_or_create(proposal=obj)
-
+
obj.comment_count = obj.result.comment_count
obj.total_votes = obj.result.vote_count
obj.plus_one = obj.result.plus_one
@@ -46,37 +44,38 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
obj.minus_zero = obj.result.minus_zero
obj.minus_one = obj.result.minus_one
lookup_params = dict(proposal=obj)
-
+
if user_pk:
lookup_params["user__pk"] = user_pk
else:
lookup_params["user"] = request.user
-
+
try:
obj.user_vote = LatestVote.objects.get(**lookup_params).vote
obj.user_vote_css = LatestVote.objects.get(**lookup_params).css_class()
except LatestVote.DoesNotExist:
obj.user_vote = None
obj.user_vote_css = "no-vote"
-
+
yield obj
-# 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":
@@ -88,35 +87,36 @@ def review_section(request, section_slug, assigned=False, reviewed="all"):
else:
queryset = queryset.exclude(reviews__user=request.user).exclude(speaker=request.user)
reviewed = "user_not_reviewed"
-
+
proposals = proposals_generator(request, queryset)
-
+
ctx = {
"proposals": proposals,
"section": section,
"reviewed": reviewed,
}
-
+
return render(request, "reviews/review_list.html", ctx)
+
@login_required
def review_list(request, section_slug, user_pk):
-
+
# if they're not a reviewer admin and they aren't the person whose
# review list is being asked for, don't let them in
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
if not request.user.pk == user_pk:
return access_not_permitted(request)
-
+
queryset = ProposalBase.objects.select_related("speaker__user", "result")
reviewed = LatestVote.objects.filter(user__pk=user_pk).values_list("proposal", flat=True)
queryset = queryset.filter(pk__in=reviewed)
proposals = queryset.order_by("submitted")
-
+
admin = request.user.has_perm("reviews.can_manage_%s" % section_slug)
-
+
proposals = proposals_generator(request, proposals, user_pk=user_pk, check_speaker=not admin)
-
+
ctx = {
"proposals": proposals,
}
@@ -125,41 +125,41 @@ def review_list(request, section_slug, user_pk):
@login_required
def review_admin(request, section_slug):
-
+
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request)
-
+
def reviewers():
already_seen = set()
-
+
for team in Team.objects.filter(permissions__codename="can_review_%s" % section_slug):
for membership in team.memberships.filter(Q(state="member") | Q(state="manager")):
user = membership.user
if user.pk in already_seen:
continue
already_seen.add(user.pk)
-
+
user.comment_count = Review.objects.filter(user=user).count()
user.total_votes = LatestVote.objects.filter(user=user).count()
user.plus_one = LatestVote.objects.filter(
- 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,
"reviewers": reviewers(),
@@ -169,50 +169,50 @@ def review_admin(request, section_slug):
@login_required
def review_detail(request, pk):
-
+
proposals = ProposalBase.objects.select_related("result").select_subclasses()
proposal = get_object_or_404(proposals, pk=pk)
-
+
if not request.user.has_perm("reviews.can_review_%s" % proposal.kind.section.slug):
return access_not_permitted(request)
-
+
speakers = [s.user for s in proposal.speakers()]
-
+
if not request.user.is_superuser and request.user in speakers:
return access_not_permitted(request)
-
+
admin = request.user.is_staff
-
+
try:
latest_vote = LatestVote.objects.get(proposal=proposal, user=request.user)
except LatestVote.DoesNotExist:
latest_vote = None
-
+
if request.method == "POST":
if request.user in speakers:
return access_not_permitted(request)
-
+
if "vote_submit" in request.POST:
review_form = ReviewForm(request.POST)
if review_form.is_valid():
-
+
review = review_form.save(commit=False)
review.user = request.user
review.proposal = proposal
review.save()
-
+
return redirect(request.path)
else:
message_form = SpeakerCommentForm()
elif "message_submit" in request.POST:
message_form = SpeakerCommentForm(request.POST)
if message_form.is_valid():
-
+
message = message_form.save(commit=False)
message.user = request.user
message.proposal = proposal
message.save()
-
+
for speaker in speakers:
if speaker and speaker.email:
ctx = {
@@ -222,9 +222,9 @@ def review_detail(request, pk):
}
send_email(
[speaker.email], "proposal_new_message",
- context = ctx
+ context=ctx
)
-
+
return redirect(request.path)
else:
initial = {}
@@ -237,7 +237,7 @@ def review_detail(request, pk):
elif "result_submit" in request.POST:
if admin:
result = request.POST["result_submit"]
-
+
if result == "accept":
proposal.result.status = "accepted"
proposal.result.save()
@@ -250,7 +250,7 @@ def review_detail(request, pk):
elif result == "standby":
proposal.result.status = "standby"
proposal.result.save()
-
+
return redirect(request.path)
else:
initial = {}
@@ -261,17 +261,17 @@ def review_detail(request, pk):
else:
review_form = ReviewForm(initial=initial)
message_form = SpeakerCommentForm()
-
+
proposal.comment_count = proposal.result.comment_count
proposal.total_votes = proposal.result.vote_count
proposal.plus_one = proposal.result.plus_one
proposal.plus_zero = proposal.result.plus_zero
proposal.minus_zero = proposal.result.minus_zero
proposal.minus_one = proposal.result.minus_one
-
+
reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at")
messages = proposal.messages.order_by("submitted_at")
-
+
return render(request, "reviews/review_detail.html", {
"proposal": proposal,
"latest_vote": latest_vote,
@@ -287,53 +287,64 @@ def review_detail(request, pk):
def review_delete(request, pk):
review = get_object_or_404(Review, pk=pk)
section_slug = review.section.slug
-
+
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request)
-
+
review = get_object_or_404(Review, pk=pk)
review.delete()
-
+
return redirect("review_detail", pk=review.proposal.pk)
@login_required
def review_status(request, section_slug=None, key=None):
-
+
if not request.user.has_perm("reviews.can_review_%s" % section_slug):
return access_not_permitted(request)
-
+
VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD
-
+
ctx = {
"section_slug": section_slug,
"vote_threshold": VOTE_THRESHOLD,
}
-
+
queryset = ProposalBase.objects.select_related("speaker__user", "result").select_subclasses()
if section_slug:
queryset = queryset.filter(kind__section__slug=section_slug)
-
+
proposals = {
- # proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by the 'score'
- "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one=0).order_by("-result__score"),
- # 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,
@@ -341,7 +352,7 @@ def review_status(request, section_slug=None, key=None):
})
else:
ctx["proposals"] = proposals
-
+
return render(request, "reviews/review_stats.html", ctx)
@@ -361,14 +372,13 @@ def review_assignments(request):
@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")
@@ -387,7 +397,7 @@ def review_bulk_accept(request, section_slug):
return redirect("review_section", section_slug=section_slug)
else:
form = BulkPresentationForm()
-
+
return render(request, "reviews/review_bulk_accept.html", {
"form": form,
})
@@ -397,10 +407,12 @@ def review_bulk_accept(request, section_slug):
def result_notification(request, section_slug, status):
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request)
-
- proposals = ProposalBase.objects.filter(kind__section__slug=section_slug, result__status=status).select_related("speaker__user", "result").select_subclasses()
+
+ proposals = ProposalBase.objects.filter(kind__section__slug=section_slug,
+ result__status=status)\
+ .select_related("speaker__user", "result").select_subclasses()
notification_templates = NotificationTemplate.objects.all()
-
+
ctx = {
"section_slug": section_slug,
"status": status,
@@ -414,10 +426,10 @@ def result_notification(request, section_slug, status):
def result_notification_prepare(request, section_slug, status):
if request.method != "POST":
return HttpResponseNotAllowed(["POST"])
-
+
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request)
-
+
proposal_pks = []
try:
for pk in request.POST.getlist("_selected_action"):
@@ -431,13 +443,13 @@ def result_notification_prepare(request, section_slug, status):
proposals = proposals.filter(pk__in=proposal_pks)
proposals = proposals.select_related("speaker__user", "result")
proposals = proposals.select_subclasses()
-
+
notification_template_pk = request.POST.get("notification_template", "")
if notification_template_pk:
notification_template = NotificationTemplate.objects.get(pk=notification_template_pk)
else:
notification_template = None
-
+
ctx = {
"section_slug": section_slug,
"status": status,
@@ -452,18 +464,18 @@ def result_notification_prepare(request, section_slug, status):
def result_notification_send(request, section_slug, status):
if request.method != "POST":
return HttpResponseNotAllowed(["POST"])
-
+
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request)
-
+
if not all([k in request.POST for k in ["proposal_pks", "from_address", "subject", "body"]]):
return HttpResponseBadRequest()
-
+
try:
proposal_pks = [int(pk) for pk in request.POST["proposal_pks"].split(",")]
except ValueError:
return HttpResponseBadRequest()
-
+
proposals = ProposalBase.objects.filter(
kind__section__slug=section_slug,
result__status=status,
@@ -471,15 +483,15 @@ def result_notification_send(request, section_slug, status):
proposals = proposals.filter(pk__in=proposal_pks)
proposals = proposals.select_related("speaker__user", "result")
proposals = proposals.select_subclasses()
-
+
notification_template_pk = request.POST.get("notification_template", "")
if notification_template_pk:
notification_template = NotificationTemplate.objects.get(pk=notification_template_pk)
else:
notification_template = None
-
+
emails = []
-
+
for proposal in proposals:
rn = ResultNotification()
rn.proposal = proposal
@@ -494,7 +506,7 @@ def result_notification_send(request, section_slug, status):
)
rn.save()
emails.append(rn.email_args)
-
+
send_mass_mail(emails)
-
+
return redirect("result_notification", section_slug=section_slug, status=status)
diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py
index 63316743529a7f34a5c7419c2cc1176ba0016402..5a0f97c1312932aadfcf8160085a8d0044637ae5 100644
--- a/symposion/schedule/forms.py
+++ b/symposion/schedule/forms.py
@@ -7,7 +7,7 @@ from symposion.schedule.models import Presentation
class SlotEditForm(forms.Form):
-
+
def __init__(self, *args, **kwargs):
self.slot = kwargs.pop("slot")
super(SlotEditForm, self).__init__(*args, **kwargs)
@@ -16,7 +16,7 @@ class SlotEditForm(forms.Form):
self.fields["presentation"] = self.build_presentation_field()
else:
self.fields["content_override"] = self.build_content_override_field()
-
+
def build_presentation_field(self):
kwargs = {}
queryset = Presentation.objects.all()
@@ -31,7 +31,7 @@ class SlotEditForm(forms.Form):
kwargs["required"] = True
kwargs["queryset"] = queryset
return forms.ModelChoiceField(**kwargs)
-
+
def build_content_override_field(self):
kwargs = {
"label": "Content",
diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py
index dc070917d0c4129abf55807649f4906ed847b330..0d7f642e9b47b3f763334247a3f767c5cd288f8b 100644
--- a/symposion/schedule/models.py
+++ b/symposion/schedule/models.py
@@ -2,44 +2,43 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from markitup.fields import MarkupField
-from model_utils.managers import InheritanceManager
from symposion.proposals.models import ProposalBase
from symposion.conference.models import Section
class Schedule(models.Model):
-
+
section = models.OneToOneField(Section)
published = models.BooleanField(default=True)
hidden = models.BooleanField("Hide schedule from overall conference view", default=False)
-
+
def __unicode__(self):
return "%s Schedule" % self.section
-
+
class Meta:
ordering = ["section"]
class Day(models.Model):
-
+
schedule = models.ForeignKey(Schedule)
date = models.DateField()
-
+
def __unicode__(self):
return "%s" % self.date
-
+
class Meta:
unique_together = [("schedule", "date")]
ordering = ["date"]
class Room(models.Model):
-
+
schedule = models.ForeignKey(Schedule)
name = models.CharField(max_length=65)
order = models.PositiveIntegerField()
-
+
def __unicode__(self):
return self.name
@@ -49,22 +48,22 @@ class SlotKind(models.Model):
A slot kind represents what kind a slot is. For example, a slot can be a
break, lunch, or X-minute talk.
"""
-
+
schedule = models.ForeignKey(Schedule)
label = models.CharField(max_length=50)
-
+
def __unicode__(self):
return self.label
class Slot(models.Model):
-
+
day = models.ForeignKey(Day)
kind = models.ForeignKey(SlotKind)
start = models.TimeField()
end = models.TimeField()
content_override = MarkupField(blank=True)
-
+
def assign(self, content):
"""
Assign the given content to this slot and if a previous slot content
@@ -73,7 +72,7 @@ class Slot(models.Model):
self.unassign()
content.slot = self
content.save()
-
+
def unassign(self):
"""
Unassign the associated content with this slot.
@@ -81,7 +80,7 @@ class Slot(models.Model):
if self.content and self.content.slot_id:
self.content.slot = None
self.content.save()
-
+
@property
def content(self):
"""
@@ -92,14 +91,14 @@ class Slot(models.Model):
return self.content_ptr
except ObjectDoesNotExist:
return None
-
+
@property
def rooms(self):
return Room.objects.filter(pk__in=self.slotroom_set.values("room"))
-
+
def __unicode__(self):
return "%s %s (%s - %s)" % (self.day, self.kind, self.start, self.end)
-
+
class Meta:
ordering = ["day", "start", "end"]
@@ -108,48 +107,49 @@ class SlotRoom(models.Model):
"""
Links a slot with a room.
"""
-
+
slot = models.ForeignKey(Slot)
room = models.ForeignKey(Room)
-
+
def __unicode__(self):
return "%s %s" % (self.room, self.slot)
-
+
class Meta:
unique_together = [("slot", "room")]
ordering = ["slot", "room__order"]
class Presentation(models.Model):
-
+
slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr")
title = models.CharField(max_length=100)
description = MarkupField()
abstract = MarkupField()
speaker = models.ForeignKey("speakers.Speaker", related_name="presentations")
- additional_speakers = models.ManyToManyField("speakers.Speaker", related_name="copresentations", blank=True)
+ additional_speakers = models.ManyToManyField("speakers.Speaker", related_name="copresentations",
+ blank=True)
cancelled = models.BooleanField(default=False)
proposal_base = models.OneToOneField(ProposalBase, related_name="presentation")
section = models.ForeignKey(Section, related_name="presentations")
-
+
@property
def number(self):
return self.proposal.number
-
+
@property
def proposal(self):
if self.proposal_base_id is None:
return None
return ProposalBase.objects.get_subclass(pk=self.proposal_base_id)
-
+
def speakers(self):
yield self.speaker
for speaker in self.additional_speakers.all():
if speaker.user:
yield speaker
-
+
def __unicode__(self):
return "#%s %s (%s)" % (self.number, self.title, self.speaker)
-
+
class Meta:
ordering = ["slot"]
diff --git a/symposion/schedule/timetable.py b/symposion/schedule/timetable.py
index 2aba1de30ce972995f0a6a2d3f78103719962444..1756db6c9ea0fe65799769239a36f777c2392ba5 100644
--- a/symposion/schedule/timetable.py
+++ b/symposion/schedule/timetable.py
@@ -1,5 +1,4 @@
import itertools
-import operator
from django.db.models import Count, Min
@@ -7,22 +6,23 @@ from symposion.schedule.models import Room, Slot, SlotRoom
class TimeTable(object):
-
+
def __init__(self, day):
self.day = day
-
+
def slots_qs(self):
qs = Slot.objects.all()
qs = qs.filter(day=self.day)
return qs
-
+
def rooms(self):
qs = Room.objects.all()
qs = qs.filter(schedule=self.day.schedule)
- qs = qs.filter(pk__in=SlotRoom.objects.filter(slot__in=self.slots_qs().values("pk")).values("room"))
+ qs = qs.filter(
+ pk__in=SlotRoom.objects.filter(slot__in=self.slots_qs().values("pk")).values("room"))
qs = qs.order_by("order")
return qs
-
+
def __iter__(self):
times = sorted(set(itertools.chain(*self.slots_qs().values_list("start", "end"))))
slots = Slot.objects.filter(pk__in=self.slots_qs().values("pk"))
@@ -38,7 +38,7 @@ class TimeTable(object):
row["slots"].append(slot)
if row["slots"] or next_time is None:
yield row
-
+
@staticmethod
def rowspan(times, start, end):
return times.index(end) - times.index(start)
diff --git a/symposion/schedule/urls.py b/symposion/schedule/urls.py
index 58890827b1f63a0be3d9c3c423789ae769b0875b..59e9b55f72846083dd0bd70d3398f1a02541733b 100644
--- a/symposion/schedule/urls.py
+++ b/symposion/schedule/urls.py
@@ -1,3 +1,4 @@
+# flake8: noqa
from django.conf.urls.defaults import url, patterns
diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py
index 35f9e4326c24acbd4d1a3a0c4b8bec988475681e..98771b597088f9913392131f2ee44d182f0ba16e 100644
--- a/symposion/schedule/views.py
+++ b/symposion/schedule/views.py
@@ -1,4 +1,3 @@
-from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404, HttpResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.template import loader, Context
@@ -12,7 +11,7 @@ from symposion.schedule.timetable import TimeTable
def fetch_schedule(slug):
qs = Schedule.objects.all()
-
+
if slug is None:
if qs.count() > 1:
raise Http404()
@@ -21,14 +20,14 @@ def fetch_schedule(slug):
raise Http404()
else:
schedule = get_object_or_404(qs, section__slug=slug)
-
+
return schedule
def schedule_conference(request):
-
+
schedules = Schedule.objects.filter(published=True, hidden=False)
-
+
sections = []
for schedule in schedules:
days_qs = Day.objects.filter(schedule=schedule)
@@ -37,7 +36,7 @@ def schedule_conference(request):
"schedule": schedule,
"days": days,
})
-
+
ctx = {
"sections": sections,
}
@@ -45,14 +44,14 @@ def schedule_conference(request):
def schedule_detail(request, slug=None):
-
+
schedule = fetch_schedule(slug)
if not schedule.published and not request.user.is_staff:
raise Http404()
-
+
days_qs = Day.objects.filter(schedule=schedule)
days = [TimeTable(day) for day in days_qs]
-
+
ctx = {
"schedule": schedule,
"days": days,
@@ -62,10 +61,10 @@ def schedule_detail(request, slug=None):
def schedule_list(request, slug=None):
schedule = fetch_schedule(slug)
-
+
presentations = Presentation.objects.filter(section=schedule.section)
presentations = presentations.exclude(cancelled=True)
-
+
ctx = {
"schedule": schedule,
"presentations": presentations,
@@ -75,32 +74,32 @@ def schedule_list(request, slug=None):
def schedule_list_csv(request, slug=None):
schedule = fetch_schedule(slug)
-
+
presentations = Presentation.objects.filter(section=schedule.section)
presentations = presentations.exclude(cancelled=True).order_by("id")
-
+
response = HttpResponse(mimetype="text/csv")
if slug:
file_slug = slug
else:
file_slug = "presentations"
response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug
-
+
response.write(loader.get_template("schedule/schedule_list.csv").render(Context({
"presentations": presentations,
-
+
})))
return response
@login_required
def schedule_edit(request, slug=None):
-
+
if not request.user.is_staff:
raise Http404()
-
+
schedule = fetch_schedule(slug)
-
+
days_qs = Day.objects.filter(schedule=schedule)
days = [TimeTable(day) for day in days_qs]
ctx = {
@@ -112,12 +111,12 @@ def schedule_edit(request, slug=None):
@login_required
def schedule_slot_edit(request, slug, slot_pk):
-
+
if not request.user.is_staff:
raise Http404()
-
+
slot = get_object_or_404(Slot, day__schedule__section__slug=slug, pk=slot_pk)
-
+
if request.method == "POST":
form = SlotEditForm(request.POST, slot=slot)
if form.is_valid():
@@ -145,13 +144,13 @@ def schedule_slot_edit(request, slug, slot_pk):
def schedule_presentation_detail(request, pk):
-
+
presentation = get_object_or_404(Presentation, pk=pk)
if presentation.slot:
schedule = presentation.slot.day.schedule
else:
schedule = None
-
+
ctx = {
"presentation": presentation,
"schedule": schedule,
diff --git a/symposion/speakers/admin.py b/symposion/speakers/admin.py
index 8162ac3f9d17b8a1dce1552471f5a7eb11dc861a..479c962608b9171d789a688fa2ca80d43bc1219c 100644
--- a/symposion/speakers/admin.py
+++ b/symposion/speakers/admin.py
@@ -4,6 +4,5 @@ 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"])
diff --git a/symposion/speakers/fixture_gen.py b/symposion/speakers/fixture_gen.py
index fc38463f254c6cbf0cdf004e14a304b4d092a58a..fdecb6b73ff8fa4a7932d78dea34aa33b209c0c0 100644
--- a/symposion/speakers/fixture_gen.py
+++ b/symposion/speakers/fixture_gen.py
@@ -10,7 +10,7 @@ def speakers():
guido = User.objects.create_user("guido", "guido@python.org", "pythonisawesome")
matz = User.objects.create_user("matz", "matz@ruby.org", "pythonsucks")
larry = User.objects.create_user("larryw", "larry@perl.org", "linenoisehere")
-
+
Speaker.objects.create(
user=guido,
name="Guido van Rossum",
@@ -19,8 +19,8 @@ def speakers():
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,
diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py
index 334dcea2f2d0d74c61d77db476765f533658b561..14ecb9a9a9f47f9a0a0df89f45dbaddc0ca9954d 100644
--- a/symposion/speakers/forms.py
+++ b/symposion/speakers/forms.py
@@ -6,7 +6,7 @@ from symposion.speakers.models import Speaker
class SpeakerForm(forms.ModelForm):
-
+
class Meta:
model = Speaker
fields = [
diff --git a/symposion/speakers/management/commands/export_speaker_data.py b/symposion/speakers/management/commands/export_speaker_data.py
index 5f9f565d89b5a9dc48017e11432076c33f4dc58e..00e82588e3c3806dc9320556868d79096138ac58 100644
--- a/symposion/speakers/management/commands/export_speaker_data.py
+++ b/symposion/speakers/management/commands/export_speaker_data.py
@@ -1,17 +1,17 @@
import csv
import os
-from django.core.management.base import BaseCommand, CommandError
+from django.core.management.base import BaseCommand
from symposion.speakers.models import Speaker
class Command(BaseCommand):
-
+
def handle(self, *args, **options):
csv_file = csv.writer(open(os.path.join(os.getcwd(), "speakers.csv"), "wb"))
csv_file.writerow(["Name", "Bio"])
-
+
for speaker in Speaker.objects.all():
csv_file.writerow([
speaker.name.encode("utf-8"),
diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py
index c86c9aa21cd32c15a6c466878bd56abd10fca7f3..b5705bcc95e5e2893f44dacfd4c247410d0a54c6 100644
--- a/symposion/speakers/models.py
+++ b/symposion/speakers/models.py
@@ -9,44 +9,47 @@ 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 Markdown.")
+ 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 "
+ ""
+ "Markdown."))
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
+ default=datetime.datetime.now,
+ editable=False
)
class Meta:
ordering = ['name']
-
+
def __unicode__(self):
if self.user:
return self.name
else:
return "?"
-
def get_absolute_url(self):
return reverse("speaker_edit")
-
+
@property
def email(self):
if self.user is not None:
return self.user.email
else:
return self.invite_email
-
+
@property
def all_presentations(self):
presentations = []
diff --git a/symposion/speakers/urls.py b/symposion/speakers/urls.py
index fa7055cf5748e6a706bc4b44a9e3b7e2faa11d07..0d1c7b1304d9958c3a24d8ba2632e99ea8c32f16 100644
--- a/symposion/speakers/urls.py
+++ b/symposion/speakers/urls.py
@@ -1,3 +1,4 @@
+# flake8: noqa
from django.conf.urls.defaults import *
diff --git a/symposion/speakers/views.py b/symposion/speakers/views.py
index 17eb13701312d4bd96ca2fe742cfdab5f084cb30..fbd4c57c5a3b4482cb15d1e23514981615ad6dc7 100644
--- a/symposion/speakers/views.py
+++ b/symposion/speakers/views.py
@@ -17,7 +17,7 @@ def speaker_create(request):
return redirect(request.user.speaker_profile)
except ObjectDoesNotExist:
pass
-
+
if request.method == "POST":
try:
speaker = Speaker.objects.get(invite_email=request.user.email)
@@ -26,7 +26,7 @@ def speaker_create(request):
speaker = None
found = False
form = SpeakerForm(request.POST, request.FILES, instance=speaker)
-
+
if form.is_valid():
speaker = form.save(commit=False)
speaker.user = request.user
@@ -37,7 +37,7 @@ def speaker_create(request):
return redirect("dashboard")
else:
form = SpeakerForm(initial={"name": request.user.get_full_name()})
-
+
return render(request, "speakers/speaker_create.html", {
"form": form,
})
@@ -48,15 +48,15 @@ def speaker_create_staff(request, pk):
user = get_object_or_404(User, pk=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)
speaker.user = user
@@ -65,7 +65,7 @@ def speaker_create_staff(request, pk):
return redirect("user_list")
else:
form = SpeakerForm(initial={"name": user.get_full_name()})
-
+
return render(request, "speakers/speaker_create.html", {
"form": form,
})
@@ -88,8 +88,8 @@ def speaker_create_token(request, token):
).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():
@@ -109,7 +109,7 @@ def speaker_edit(request, pk=None):
speaker = get_object_or_404(Speaker, pk=pk)
else:
raise Http404()
-
+
if request.method == "POST":
form = SpeakerForm(request.POST, request.FILES, instance=speaker)
if form.is_valid():
@@ -118,7 +118,7 @@ def speaker_edit(request, pk=None):
return redirect("dashboard")
else:
form = SpeakerForm(instance=speaker)
-
+
return render(request, "speakers/speaker_edit.html", {
"form": form,
})
@@ -129,7 +129,7 @@ def speaker_profile(request, pk):
presentations = speaker.all_presentations
if not presentations and not request.user.is_staff:
raise Http404()
-
+
return render(request, "speakers/speaker_profile.html", {
"speaker": speaker,
"presentations": presentations,
diff --git a/symposion/sponsorship/admin.py b/symposion/sponsorship/admin.py
index ad39a62676f5333d53b48f278591e8c18a8d0952..b64c1a132169214e187eec8e80eb625153a476b1 100644
--- a/symposion/sponsorship/admin.py
+++ b/symposion/sponsorship/admin.py
@@ -1,6 +1,7 @@
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):
@@ -24,7 +25,7 @@ class SponsorBenefitInline(admin.StackedInline):
class SponsorAdmin(admin.ModelAdmin):
-
+
save_on_top = True
fieldsets = [
(None, {
@@ -43,7 +44,7 @@ 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
form = super(SponsorAdmin, self).get_form(*args, **kwargs)
@@ -56,13 +57,13 @@ class SponsorAdmin(admin.ModelAdmin):
class BenefitAdmin(admin.ModelAdmin):
-
+
list_display = ["name", "type", "description"]
inlines = [BenefitLevelInline]
class SponsorLevelAdmin(admin.ModelAdmin):
-
+
inlines = [BenefitLevelInline]
diff --git a/symposion/sponsorship/forms.py b/symposion/sponsorship/forms.py
index 9584c7fb4f9c913dc81b4b92a39daeb4564fa7ad..33b52f422371802b9254aeb6ee9a910cbbfd9a4f 100644
--- a/symposion/sponsorship/forms.py
+++ b/symposion/sponsorship/forms.py
@@ -16,7 +16,7 @@ class SponsorApplicationForm(forms.ModelForm):
}
})
super(SponsorApplicationForm, self).__init__(*args, **kwargs)
-
+
class Meta:
model = Sponsor
fields = [
@@ -26,7 +26,7 @@ class SponsorApplicationForm(forms.ModelForm):
"contact_email",
"level"
]
-
+
def save(self, commit=True):
obj = super(SponsorApplicationForm, self).save(commit=False)
obj.applicant = self.user
@@ -47,26 +47,26 @@ 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
diff --git a/symposion/sponsorship/management/commands/reset_sponsor_benefits.py b/symposion/sponsorship/management/commands/reset_sponsor_benefits.py
index 2c515a2e0989ab6c41da646eb705489a5b60a072..3c17efab949d8f93baa4cd3ddf148223aebf2b56 100644
--- a/symposion/sponsorship/management/commands/reset_sponsor_benefits.py
+++ b/symposion/sponsorship/management/commands/reset_sponsor_benefits.py
@@ -1,15 +1,13 @@
from django.core.management.base import BaseCommand
-from django.contrib.auth.models import Group
-
-from symposion.sponsorship.models import Sponsor, SponsorBenefit
+from symposion.sponsorship.models import Sponsor, SponsorBenefit, SponsorLevel
class Command(BaseCommand):
-
+
def handle(self, *args, **options):
for sponsor in Sponsor.objects.all():
- level = None
+ level = None
try:
level = sponsor.level
except SponsorLevel.DoesNotExist:
@@ -19,17 +17,17 @@ class Command(BaseCommand):
# Create all needed benefits if they don't exist already
sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
sponsor=sponsor, benefit=benefit_level.benefit)
-
+
if created:
print "created", sponsor_benefit, "for", sponsor
-
+
# and set to default limits for this level.
sponsor_benefit.max_words = benefit_level.max_words
sponsor_benefit.other_limits = benefit_level.other_limits
-
+
# and set to active
sponsor_benefit.active = True
-
+
# @@@ We don't call sponsor_benefit.clean here. This means
# that if the sponsorship level for a sponsor is adjusted
# downwards, an existing too-long text entry can remain,
diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py
index 24a12d31728397c86e83ebd261b0e708d81d1b4e..e29ac3d11c8614dceea2719935631cfeff342573 100644
--- a/symposion/sponsorship/models.py
+++ b/symposion/sponsorship/models.py
@@ -14,29 +14,30 @@ from symposion.sponsorship.managers import SponsorManager
class SponsorLevel(models.Model):
-
+
conference = models.ForeignKey(Conference, verbose_name=_("conference"))
name = models.CharField(_("name"), max_length=100)
order = models.IntegerField(_("order"), default=0)
cost = models.PositiveIntegerField(_("cost"))
description = models.TextField(_("description"), blank=True, help_text=_("This is private."))
-
+
class Meta:
ordering = ["conference", "order"]
verbose_name = _("sponsor level")
verbose_name_plural = _("sponsor levels")
-
+
def __unicode__(self):
return self.name
-
+
def sponsors(self):
return self.sponsor_set.filter(active=True).order_by("added")
class Sponsor(models.Model):
-
- applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"), null=True)
-
+
+ applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"),
+ null=True)
+
name = models.CharField(_("Sponsor Name"), max_length=100)
external_url = models.URLField(_("external URL"))
annotation = models.TextField(_("annotation"), blank=True)
@@ -45,34 +46,36 @@ class Sponsor(models.Model):
level = models.ForeignKey(SponsorLevel, verbose_name=_("level"))
added = models.DateTimeField(_("added"), default=datetime.datetime.now)
active = models.BooleanField(_("active"), default=False)
-
+
# Denormalization (this assumes only one logo)
- sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True, editable=False)
-
+ sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True,
+ editable=False)
+
objects = SponsorManager()
-
+
def __unicode__(self):
return self.name
-
+
class Meta:
verbose_name = _("sponsor")
verbose_name_plural = _("sponsors")
-
+
def get_absolute_url(self):
if self.active:
return reverse("sponsor_detail", kwargs={"pk": self.pk})
return reverse("sponsor_list")
-
+
@property
def website_logo(self):
if self.sponsor_logo is None:
- benefits = self.sponsor_benefits.filter(benefit__type="weblogo", upload__isnull=False)[:1]
+ benefits = self.sponsor_benefits.filter(
+ benefit__type="weblogo", upload__isnull=False)[:1]
if benefits.count():
if benefits[0].upload:
self.sponsor_logo = benefits[0]
self.save()
return self.sponsor_logo.upload
-
+
@property
def listing_text(self):
if not hasattr(self, "_listing_text"):
@@ -82,46 +85,47 @@ class Sponsor(models.Model):
if benefits.count():
self._listing_text = benefits[0].text
return self._listing_text
-
+
def reset_benefits(self):
"""
Reset all benefits for this sponsor to the defaults for their
sponsorship level.
"""
level = None
-
+
try:
level = self.level
except SponsorLevel.DoesNotExist:
pass
-
+
allowed_benefits = []
if level:
for benefit_level in level.benefit_levels.all():
# Create all needed benefits if they don't exist already
sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
sponsor=self, benefit=benefit_level.benefit)
-
+
# and set to default limits for this level.
sponsor_benefit.max_words = benefit_level.max_words
sponsor_benefit.other_limits = benefit_level.other_limits
-
+
# and set to active
sponsor_benefit.active = True
-
+
# @@@ We don't call sponsor_benefit.clean here. This means
# that if the sponsorship level for a sponsor is adjusted
# downwards, an existing too-long text entry can remain,
# and won't raise a validation error until it's next
# edited.
sponsor_benefit.save()
-
+
allowed_benefits.append(sponsor_benefit.pk)
-
+
# Any remaining sponsor benefits that don't normally belong to
# this level are set to inactive
- self.sponsor_benefits.exclude(pk__in=allowed_benefits).update(active=False, max_words=None, other_limits="")
-
+ self.sponsor_benefits.exclude(pk__in=allowed_benefits)\
+ .update(active=False, max_words=None, other_limits="")
+
def send_coordinator_emails(self):
pass # @@@ should this just be done centrally?
@@ -147,59 +151,60 @@ 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
class BenefitLevel(models.Model):
-
+
benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit"))
level = models.ForeignKey(SponsorLevel, related_name="benefit_levels", verbose_name=_("level"))
-
+
# default limits for this benefit at given level
max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True)
other_limits = models.CharField(_("other limits"), max_length=200, blank=True)
-
+
class Meta:
ordering = ["level"]
-
+
def __unicode__(self):
return u"%s - %s" % (self.level, self.benefit)
class SponsorBenefit(models.Model):
-
+
sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("sponsor"))
benefit = models.ForeignKey(Benefit, related_name="sponsor_benefits", verbose_name=_("benefit"))
active = models.BooleanField(default=True)
-
+
# Limits: will initially be set to defaults from corresponding BenefitLevel
max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True)
other_limits = models.CharField(_("other limits"), max_length=200, blank=True)
-
+
# Data: zero or one of these fields will be used, depending on the
# 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())
if self.max_words and num_words > self.max_words:
raise ValidationError(
"Sponsorship level only allows for %s words, you provided %d." % (
self.max_words, num_words))
-
+
def data_fields(self):
"""
Return list of data field names which should be editable for
diff --git a/symposion/sponsorship/templatetags/sponsorship_tags.py b/symposion/sponsorship/templatetags/sponsorship_tags.py
index cd1f33ba025d1338402d0f9efff0ca2b09edca7a..d88243c4e846f744d80f196a272a1baf877d0afc 100644
--- a/symposion/sponsorship/templatetags/sponsorship_tags.py
+++ b/symposion/sponsorship/templatetags/sponsorship_tags.py
@@ -8,7 +8,7 @@ register = template.Library()
class SponsorsNode(template.Node):
-
+
@classmethod
def handle_token(cls, parser, token):
bits = token.split_contents()
@@ -18,27 +18,30 @@ class SponsorsNode(template.Node):
return cls(bits[3], bits[1])
else:
raise template.TemplateSyntaxError("%r takes 'as var' or 'level as var'" % bits[0])
-
+
def __init__(self, context_var, level=None):
if level:
self.level = template.Variable(level)
else:
self.level = None
self.context_var = context_var
-
+
def render(self, context):
conference = current_conference()
if self.level:
level = self.level.resolve(context)
- queryset = Sponsor.objects.filter(level__conference = conference, level__name__iexact = level, active = True).order_by("added")
+ queryset = Sponsor.objects.filter(
+ level__conference=conference, level__name__iexact=level, active=True)\
+ .order_by("added")
else:
- queryset = Sponsor.objects.filter(level__conference = conference, active = True).order_by("level__order", "added")
+ queryset = Sponsor.objects.filter(level__conference=conference, active=True)\
+ .order_by("level__order", "added")
context[self.context_var] = queryset
return u""
class SponsorLevelNode(template.Node):
-
+
@classmethod
def handle_token(cls, parser, token):
bits = token.split_contents()
@@ -46,10 +49,10 @@ class SponsorLevelNode(template.Node):
return cls(bits[2])
else:
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
-
+
def __init__(self, context_var):
self.context_var = context_var
-
+
def render(self, context):
conference = current_conference()
context[self.context_var] = SponsorLevel.objects.filter(conference=conference)
@@ -72,4 +75,3 @@ def sponsor_levels(parser, token):
{% sponsor_levels as levels %}
"""
return SponsorLevelNode.handle_token(parser, token)
-
\ No newline at end of file
diff --git a/symposion/sponsorship/urls.py b/symposion/sponsorship/urls.py
index e5d32bb0bec1266f84be261e9becc04bd72b9fdd..e6ba5fa3e99377b39d36133a41ad1ad0c31b822b 100644
--- a/symposion/sponsorship/urls.py
+++ b/symposion/sponsorship/urls.py
@@ -1,3 +1,4 @@
+# flake8: noqa
from django.conf.urls.defaults import patterns, url
from django.views.generic.simple import direct_to_template
diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py
index 22b4f2433a9ed68a62141624c7c1b8ea5259bae6..c7728aedecaf44784b5bc657fbc9f73175ceafbc 100644
--- a/symposion/sponsorship/views.py
+++ b/symposion/sponsorship/views.py
@@ -5,7 +5,8 @@ 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
@@ -18,7 +19,7 @@ def sponsor_apply(request):
return redirect("sponsor_detail", pk=sponsor.pk)
else:
form = SponsorApplicationForm(user=request.user)
-
+
return render_to_response("sponsorship/apply.html", {
"form": form,
}, context_instance=RequestContext(request))
@@ -28,7 +29,7 @@ def sponsor_apply(request):
def sponsor_add(request):
if not request.user.is_staff:
raise Http404()
-
+
if request.method == "POST":
form = SponsorApplicationForm(request.POST, user=request.user)
if form.is_valid():
@@ -38,7 +39,7 @@ def sponsor_add(request):
return redirect("sponsor_detail", pk=sponsor.pk)
else:
form = SponsorApplicationForm(user=request.user)
-
+
return render_to_response("sponsorship/add.html", {
"form": form,
}, context_instance=RequestContext(request))
@@ -47,31 +48,31 @@ def sponsor_add(request):
@login_required
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,
"form": form,
diff --git a/symposion/teams/admin.py b/symposion/teams/admin.py
index 14b9ec25aa9f1d9c1871800cff5076ed0f1bd62a..5175e302a213f0f62f16ad197a8aeb8c48ec13af 100644
--- a/symposion/teams/admin.py
+++ b/symposion/teams/admin.py
@@ -5,8 +5,7 @@ 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):
diff --git a/symposion/teams/backends.py b/symposion/teams/backends.py
index 9b4ddf7efde8a1c77a21d3340f1f468efe9a6141..23b001b6e30e2456df4130094bf9177b51378d2b 100644
--- a/symposion/teams/backends.py
+++ b/symposion/teams/backends.py
@@ -4,10 +4,10 @@ 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):
"""
Returns a set of permission strings that this user has through his/her
diff --git a/symposion/teams/forms.py b/symposion/teams/forms.py
index 6004ae217f0b5444f69890a73656af10d5a77216..a9080626b7e9d24b755a5f48f6fd29bf028598c2 100644
--- a/symposion/teams/forms.py
+++ b/symposion/teams/forms.py
@@ -9,40 +9,43 @@ from symposion.teams.models import Membership
class TeamInvitationForm(forms.Form):
-
- email = forms.EmailField(help_text="email address must be that of an account on this conference site")
-
+
+ email = forms.EmailField(help_text=("email address must be that of an account on this "
+ "conference site"))
+
def __init__(self, *args, **kwargs):
self.team = kwargs.pop("team")
super(TeamInvitationForm, self).__init__(*args, **kwargs)
-
+
def clean(self):
cleaned_data = super(TeamInvitationForm, self).clean()
email = cleaned_data.get("email")
-
+
if email is None:
raise forms.ValidationError("valid email address required")
-
+
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
# eventually we can invite them but for now assume they are
# already on the site
- raise forms.ValidationError(mark_safe("no account with email address %s found on this conference site" % escape(email)))
-
+ raise forms.ValidationError(
+ mark_safe("no account with email address %s found on this conference "
+ "site" % escape(email)))
+
state = self.team.get_state_for_user(user)
-
+
if state in ["member", "manager"]:
raise forms.ValidationError("user already in team")
-
+
if state in ["invited"]:
raise forms.ValidationError("user already invited to team")
-
+
self.user = user
self.state = state
-
+
return cleaned_data
-
+
def invite(self):
if self.state is None:
Membership.objects.create(team=self.team, user=self.user, state="invited")
diff --git a/symposion/teams/models.py b/symposion/teams/models.py
index 7f00f05514f519b998885a3a0c37dbdda7d69281..8c344cc419f8539e2436821d6228f0624f2278e8 100644
--- a/symposion/teams/models.py
+++ b/symposion/teams/models.py
@@ -20,19 +20,20 @@ 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):
return self.name
@@ -41,16 +42,16 @@ class Team(models.Model):
return self.memberships.get(user=user).state
except Membership.DoesNotExist:
return None
-
+
def applicants(self):
return self.memberships.filter(state="applied")
-
+
def invitees(self):
return self.memberships.filter(state="invited")
-
+
def members(self):
return self.memberships.filter(state="member")
-
+
def managers(self):
return self.memberships.filter(state="manager")
diff --git a/symposion/teams/templatetags/teams_tags.py b/symposion/teams/templatetags/teams_tags.py
index 7c9cd5b574d78607ea08fcec7cc973bd61a7b434..b82a0eba2a0dd186e17e2d0546a10b63e412e49d 100644
--- a/symposion/teams/templatetags/teams_tags.py
+++ b/symposion/teams/templatetags/teams_tags.py
@@ -6,7 +6,7 @@ register = template.Library()
class AvailableTeamsNode(template.Node):
-
+
@classmethod
def handle_token(cls, parser, token):
bits = token.split_contents()
@@ -14,10 +14,10 @@ class AvailableTeamsNode(template.Node):
return cls(bits[2])
else:
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
-
+
def __init__(self, context_var):
self.context_var = context_var
-
+
def render(self, context):
request = context["request"]
teams = []
diff --git a/symposion/teams/urls.py b/symposion/teams/urls.py
index 01145f41c8b1536087ed3199ae45aad6b3dccfeb..d70a600d40aadb5b83e4ded873f2fdb5534ba8f3 100644
--- a/symposion/teams/urls.py
+++ b/symposion/teams/urls.py
@@ -1,3 +1,4 @@
+# flake8: noqa
from django.conf.urls.defaults import *
@@ -7,7 +8,7 @@ urlpatterns = patterns("symposion.teams.views",
url(r"^(?P[\w\-]+)/join/$", "team_join", name="team_join"),
url(r"^(?P[\w\-]+)/leave/$", "team_leave", name="team_leave"),
url(r"^(?P[\w\-]+)/apply/$", "team_apply", name="team_apply"),
-
+
# membership specific
url(r"^promote/(?P\d+)/$", "team_promote", name="team_promote"),
url(r"^demote/(?P\d+)/$", "team_demote", name="team_demote"),
diff --git a/symposion/teams/views.py b/symposion/teams/views.py
index e885f9b4fc60aeb544676a0ed221827b68152640..f45a0cba764efcb54ae959223429da691ad882f7 100644
--- a/symposion/teams/views.py
+++ b/symposion/teams/views.py
@@ -10,7 +10,7 @@ from symposion.teams.forms import TeamInvitationForm
from symposion.teams.models import Team, Membership
-## perm checks
+# perm checks
#
# @@@ these can be moved
@@ -50,7 +50,7 @@ def can_invite(team, user):
return False
-## views
+# views
@login_required
@@ -59,7 +59,7 @@ def team_detail(request, slug):
state = team.get_state_for_user(request.user)
if team.access == "invitation" and state is None and not request.user.is_staff:
raise Http404()
-
+
if can_invite(team, request.user):
if request.method == "POST":
form = TeamInvitationForm(request.POST, team=team)
@@ -72,7 +72,7 @@ def team_detail(request, slug):
form = TeamInvitationForm(team=team)
else:
form = None
-
+
return render(request, "teams/team_detail.html", {
"team": team,
"state": state,
@@ -89,7 +89,7 @@ def team_join(request, slug):
state = team.get_state_for_user(request.user)
if team.access == "invitation" and state is None and not request.user.is_staff:
raise Http404()
-
+
if can_join(team, request.user) and request.method == "POST":
membership, created = Membership.objects.get_or_create(team=team, user=request.user)
membership.state = "member"
@@ -106,7 +106,7 @@ def team_leave(request, slug):
state = team.get_state_for_user(request.user)
if team.access == "invitation" and state is None and not request.user.is_staff:
raise Http404()
-
+
if can_leave(team, request.user) and request.method == "POST":
membership = Membership.objects.get(team=team, user=request.user)
membership.delete()
@@ -122,7 +122,7 @@ def team_apply(request, slug):
state = team.get_state_for_user(request.user)
if team.access == "invitation" and state is None and not request.user.is_staff:
raise Http404()
-
+
if can_apply(team, request.user) and request.method == "POST":
membership, created = Membership.objects.get_or_create(team=team, user=request.user)
membership.state = "applied"
diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py
index dc85687178da8d70bff0b0706a35ce87194e7642..59cdf09c7281efaf3ed9136aa0e82d1dfd40b093 100644
--- a/symposion/utils/mail.py
+++ b/symposion/utils/mail.py
@@ -7,9 +7,9 @@ from django.contrib.sites.models import Site
def send_email(to, kind, **kwargs):
-
+
current_site = Site.objects.get_current()
-
+
ctx = {
"current_site": current_site,
"STATIC_URL": settings.STATIC_URL,
@@ -19,12 +19,12 @@ def send_email(to, kind, **kwargs):
current_site.name,
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")
email.send()
diff --git a/symposion/views.py b/symposion/views.py
index 669e2847adae96c34a4ea63bbb2d1d393d5c202a..e1bfab7ee71dfaaa44d0b6768e20c64c5fd5e18f 100644
--- a/symposion/views.py
+++ b/symposion/views.py
@@ -12,12 +12,12 @@ import symposion.forms
class SignupView(account.views.SignupView):
-
+
form_class = symposion.forms.SignupForm
form_kwargs = {
"prefix": "signup",
}
-
+
def create_user(self, form, commit=True):
user_kwargs = {
"first_name": form.cleaned_data["first_name"],