Fix flake8 warnings

This commit is contained in:
Carlos Henrique Romano 2014-07-30 15:19:26 -03:00
parent 96596dc5dd
commit 36ab6d599f
66 changed files with 614 additions and 585 deletions

View file

@ -13,8 +13,8 @@ def default_can_edit(request, *args, **kwargs):
def load_can_edit(): def load_can_edit():
import_path = getattr(settings, "BOXES_CAN_EDIT_CALLABLE", None) import_path = getattr(settings, "BOXES_CAN_EDIT_CALLABLE", None)
if import_path is None: if import_path is None:
return default_can_edit return default_can_edit
return load_path_attr(import_path) return load_path_attr(import_path)

View file

@ -4,7 +4,7 @@ from symposion.boxes.models import Box
class BoxForm(forms.ModelForm): class BoxForm(forms.ModelForm):
class Meta: class Meta:
model = Box model = Box
fields = ["content"] fields = ["content"]

View file

@ -1,5 +1,3 @@
import datetime
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -10,16 +8,16 @@ from markitup.fields import MarkupField
class Box(models.Model): class Box(models.Model):
label = models.CharField(max_length=100, db_index=True) label = models.CharField(max_length=100, db_index=True)
content = MarkupField(blank=True) content = MarkupField(blank=True)
created_by = models.ForeignKey(User, related_name="boxes") created_by = models.ForeignKey(User, related_name="boxes")
last_updated_by = models.ForeignKey(User, related_name="updated_boxes") last_updated_by = models.ForeignKey(User, related_name="updated_boxes")
def __unicode__(self): def __unicode__(self):
return self.label return self.label
class Meta: class Meta:
verbose_name_plural = "boxes" verbose_name_plural = "boxes"

View file

@ -1,10 +1,5 @@
from django import template from django import template
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse 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.models import Box
from symposion.boxes.forms import BoxForm from symposion.boxes.forms import BoxForm
@ -16,22 +11,22 @@ register = template.Library()
@register.inclusion_tag("boxes/box.html", takes_context=True) @register.inclusion_tag("boxes/box.html", takes_context=True)
def box(context, label, show_edit=True, *args, **kwargs): def box(context, label, show_edit=True, *args, **kwargs):
request = context["request"] request = context["request"]
can_edit = load_can_edit()(request, *args, **kwargs) can_edit = load_can_edit()(request, *args, **kwargs)
try: try:
box = Box.objects.get(label=label) box = Box.objects.get(label=label)
except Box.DoesNotExist: except Box.DoesNotExist:
box = None box = None
if can_edit and show_edit: if can_edit and show_edit:
form = BoxForm(instance=box, prefix=label) form = BoxForm(instance=box, prefix=label)
form_action = reverse("box_edit", args=[label]) form_action = reverse("box_edit", args=[label])
else: else:
form = None form = None
form_action = None form_action = None
return { return {
"request": request, "request": request,
"label": label, "label": label,

View file

@ -1,6 +1,7 @@
# flake8: noqa
from django.conf.urls.defaults import url, patterns from django.conf.urls.defaults import url, patterns
urlpatterns = patterns("symposion.boxes.views", urlpatterns = patterns("symposion.boxes.views",
url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"), url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"),
) )

View file

@ -7,7 +7,8 @@ from symposion.boxes.forms import BoxForm
from symposion.boxes.models import Box 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): def get_auth_vars(request):
auth_vars = {} auth_vars = {}
if request.method == "POST": if request.method == "POST":
@ -20,17 +21,17 @@ def get_auth_vars(request):
@require_POST @require_POST
def box_edit(request, label): def box_edit(request, label):
if not load_can_edit()(request, **get_auth_vars(request)): if not load_can_edit()(request, **get_auth_vars(request)):
return HttpResponseForbidden() return HttpResponseForbidden()
next = request.GET.get("next") next = request.GET.get("next")
try: try:
box = Box.objects.get(label=label) box = Box.objects.get(label=label)
except Box.DoesNotExist: except Box.DoesNotExist:
box = None box = None
form = BoxForm(request.POST, instance=box, prefix=label) form = BoxForm(request.POST, instance=box, prefix=label)
if form.is_valid(): if form.is_valid():

View file

@ -4,6 +4,7 @@ import reversion
from .models import Page from .models import Page
class PageAdmin(reversion.VersionAdmin): class PageAdmin(reversion.VersionAdmin):
pass pass

View file

@ -6,7 +6,7 @@ from .models import Page
class PageForm(forms.ModelForm): class PageForm(forms.ModelForm):
class Meta: class Meta:
model = Page model = Page
fields = ["title", "body", "path"] fields = ["title", "body", "path"]
@ -17,5 +17,5 @@ class PageForm(forms.ModelForm):
class FileUploadForm(forms.Form): class FileUploadForm(forms.Form):
file = forms.FileField() file = forms.FileField()

View file

@ -2,8 +2,9 @@ from datetime import datetime
from django.db import models from django.db import models
class PublishedPageManager(models.Manager): class PublishedPageManager(models.Manager):
def get_query_set(self): def get_query_set(self):
qs = super(PublishedPageManager, self).get_query_set() qs = super(PublishedPageManager, self).get_query_set()
return qs.filter(publish_date__lte=datetime.now()) return qs.filter(publish_date__lte=datetime.now())

View file

@ -18,12 +18,12 @@ from .managers import PublishedPageManager
class Page(models.Model): class Page(models.Model):
STATUS_CHOICES = ( STATUS_CHOICES = (
(1, _("Draft")), (1, _("Draft")),
(2, _("Public")), (2, _("Public")),
) )
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
path = models.CharField(max_length=100, unique=True) path = models.CharField(max_length=100, unique=True)
body = MarkupField() body = MarkupField()
@ -32,28 +32,29 @@ class Page(models.Model):
created = models.DateTimeField(editable=False, default=datetime.datetime.now) created = models.DateTimeField(editable=False, default=datetime.datetime.now)
updated = models.DateTimeField(editable=False, default=datetime.datetime.now) updated = models.DateTimeField(editable=False, default=datetime.datetime.now)
tags = TaggableManager(blank=True) tags = TaggableManager(blank=True)
published = PublishedPageManager() published = PublishedPageManager()
def __unicode__(self): def __unicode__(self):
return self.title return self.title
@models.permalink @models.permalink
def get_absolute_url(self): def get_absolute_url(self):
return ("cms_page", [self.path]) return ("cms_page", [self.path])
@property @property
def is_community(self): def is_community(self):
return self.path.lower().startswith("community/") return self.path.lower().startswith("community/")
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.updated = datetime.datetime.now() self.updated = datetime.datetime.now()
super(Page, self).save(*args, **kwargs) super(Page, self).save(*args, **kwargs)
def clean_fields(self, exclude=None): def clean_fields(self, exclude=None):
super(Page, self).clean_fields(exclude) super(Page, self).clean_fields(exclude)
if not re.match(settings.SYMPOSION_PAGE_REGEX, self.path): 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) reversion.register(Page)
@ -64,9 +65,9 @@ def generate_filename(instance, filename):
class File(models.Model): class File(models.Model):
file = models.FileField(upload_to=generate_filename) file = models.FileField(upload_to=generate_filename)
created = models.DateTimeField(default=datetime.datetime.now) created = models.DateTimeField(default=datetime.datetime.now)
def download_url(self): def download_url(self):
return reverse("file_download", args=[self.pk, os.path.basename(self.file.name).lower()]) return reverse("file_download", args=[self.pk, os.path.basename(self.file.name).lower()])

View file

@ -1,3 +1,4 @@
# flake8: noqa
from django.conf.urls.defaults import url, patterns from django.conf.urls.defaults import url, patterns
PAGE_RE = r"(([\w-]{1,})(/[\w-]{1,})*)/" PAGE_RE = r"(([\w-]{1,})(/[\w-]{1,})*)/"

View file

@ -23,20 +23,20 @@ def can_upload(user):
def page(request, path): def page(request, path):
try: try:
page = Page.published.get(path=path) page = Page.published.get(path=path)
except Page.DoesNotExist: except Page.DoesNotExist:
page = None page = None
editable = can_edit(page, request.user) editable = can_edit(page, request.user)
if page is None: if page is None:
if editable: if editable:
return redirect("cms_page_edit", path=path) return redirect("cms_page_edit", path=path)
else: else:
raise Http404 raise Http404
return render(request, "cms/page_detail.html", { return render(request, "cms/page_detail.html", {
"page": page, "page": page,
"editable": editable, "editable": editable,
@ -45,15 +45,15 @@ def page(request, path):
@login_required @login_required
def page_edit(request, path): def page_edit(request, path):
try: try:
page = Page.published.get(path=path) page = Page.published.get(path=path)
except Page.DoesNotExist: except Page.DoesNotExist:
page = None page = None
if not can_edit(page, request.user): if not can_edit(page, request.user):
raise Http404 raise Http404
if request.method == "POST": if request.method == "POST":
form = PageForm(request.POST, instance=page) form = PageForm(request.POST, instance=page)
if form.is_valid(): if form.is_valid():
@ -65,7 +65,7 @@ def page_edit(request, path):
print form.errors print form.errors
else: else:
form = PageForm(instance=page, initial={"path": path}) form = PageForm(instance=page, initial={"path": path})
return render(request, "cms/page_edit.html", { return render(request, "cms/page_edit.html", {
"path": path, "path": path,
"form": form "form": form
@ -75,7 +75,7 @@ def page_edit(request, path):
def file_index(request): def file_index(request):
if not can_upload(request.user): if not can_upload(request.user):
raise Http404 raise Http404
ctx = { ctx = {
"files": File.objects.all(), "files": File.objects.all(),
} }
@ -85,7 +85,7 @@ def file_index(request):
def file_create(request): def file_create(request):
if not can_upload(request.user): if not can_upload(request.user):
raise Http404 raise Http404
if request.method == "POST": if request.method == "POST":
form = FileUploadForm(request.POST, request.FILES) form = FileUploadForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
@ -97,7 +97,7 @@ def file_create(request):
return redirect("file_index") return redirect("file_index")
else: else:
form = FileUploadForm() form = FileUploadForm()
ctx = { ctx = {
"form": form, "form": form,
} }
@ -106,7 +106,7 @@ def file_create(request):
def file_download(request, pk, *args): def file_download(request, pk, *args):
file = get_object_or_404(File, pk=pk) file = get_object_or_404(File, pk=pk)
if getattr(settings, "USE_X_ACCEL_REDIRECT", False): if getattr(settings, "USE_X_ACCEL_REDIRECT", False):
response = HttpResponse() response = HttpResponse()
response["X-Accel-Redirect"] = file.file.url response["X-Accel-Redirect"] = file.file.url
@ -115,14 +115,14 @@ def file_download(request, pk, *args):
del response["content-type"] del response["content-type"]
else: else:
response = static.serve(request, file.file.name, document_root=settings.MEDIA_ROOT) response = static.serve(request, file.file.name, document_root=settings.MEDIA_ROOT)
return response return response
def file_delete(request, pk): def file_delete(request, pk):
if not can_upload(request.user): if not can_upload(request.user):
raise Http404 raise Http404
file = get_object_or_404(File, pk=pk) file = get_object_or_404(File, pk=pk)
if request.method == "POST": if request.method == "POST":
file.delete() file.delete()

View file

@ -1,8 +1,6 @@
from django.conf import settings
from appconf import AppConf from appconf import AppConf
class SymposionAppConf(AppConf): class SymposionAppConf(AppConf):
VOTE_THRESHOLD = 3 VOTE_THRESHOLD = 3

View file

@ -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(Conference, list_display=("title", "start_date", "end_date"))
admin.site.register( admin.site.register(
Section, Section,
prepopulated_fields = {"slug": ("name",)}, prepopulated_fields={"slug": ("name",)},
list_display = ("name", "conference", "start_date", "end_date") list_display=("name", "conference", "start_date", "end_date")
) )

View file

@ -11,24 +11,24 @@ class Conference(models.Model):
""" """
the full conference for a specific year, e.g. US PyCon 2012. the full conference for a specific year, e.g. US PyCon 2012.
""" """
title = models.CharField(_("title"), max_length=100) title = models.CharField(_("title"), max_length=100)
# when the conference runs # when the conference runs
start_date = models.DateField(_("start date"), null=True, blank=True) start_date = models.DateField(_("start date"), null=True, blank=True)
end_date = models.DateField(_("end date"), null=True, blank=True) end_date = models.DateField(_("end date"), null=True, blank=True)
# timezone the conference is in # timezone the conference is in
timezone = TimeZoneField(_("timezone"), blank=True) timezone = TimeZoneField(_("timezone"), blank=True)
def __unicode__(self): def __unicode__(self):
return self.title return self.title
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super(Conference, self).save(*args, **kwargs) super(Conference, self).save(*args, **kwargs)
if self.id in CONFERENCE_CACHE: if self.id in CONFERENCE_CACHE:
del CONFERENCE_CACHE[self.id] del CONFERENCE_CACHE[self.id]
def delete(self): def delete(self):
pk = self.pk pk = self.pk
super(Conference, self).delete() super(Conference, self).delete()
@ -36,7 +36,7 @@ class Conference(models.Model):
del CONFERENCE_CACHE[pk] del CONFERENCE_CACHE[pk]
except KeyError: except KeyError:
pass pass
class Meta(object): class Meta(object):
verbose_name = _("conference") verbose_name = _("conference")
verbose_name_plural = _("conferences") verbose_name_plural = _("conferences")
@ -48,19 +48,19 @@ class Section(models.Model):
"Talks", "Expo", "Sprints", that may have its own review and "Talks", "Expo", "Sprints", that may have its own review and
scheduling process. scheduling process.
""" """
conference = models.ForeignKey(Conference, verbose_name=_("conference")) conference = models.ForeignKey(Conference, verbose_name=_("conference"))
name = models.CharField(_("name"), max_length=100) name = models.CharField(_("name"), max_length=100)
slug = models.SlugField() slug = models.SlugField()
# when the section runs # when the section runs
start_date = models.DateField(_("start date"), null=True, blank=True) start_date = models.DateField(_("start date"), null=True, blank=True)
end_date = models.DateField(_("end date"), null=True, blank=True) end_date = models.DateField(_("end date"), null=True, blank=True)
def __unicode__(self): def __unicode__(self):
return "%s %s" % (self.conference, self.name) return "%s %s" % (self.conference, self.name)
class Meta(object): class Meta(object):
verbose_name = _("section") verbose_name = _("section")
verbose_name_plural = _("sections") verbose_name_plural = _("sections")

View file

@ -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", urlpatterns = patterns("symposion.conference.views",

View file

@ -7,10 +7,10 @@ from django.contrib.auth.models import User
@login_required @login_required
def user_list(request): def user_list(request):
if not request.user.is_staff: if not request.user.is_staff:
raise Http404() raise Http404()
return render(request, "conference/user_list.html", { return render(request, "conference/user_list.html", {
"users": User.objects.all(), "users": User.objects.all(),
}) })

View file

@ -4,7 +4,7 @@ import account.forms
class SignupForm(account.forms.SignupForm): class SignupForm(account.forms.SignupForm):
first_name = forms.CharField() first_name = forms.CharField()
last_name = forms.CharField() last_name = forms.CharField()
email_confirm = forms.EmailField(label="Confirm Email") email_confirm = forms.EmailField(label="Confirm Email")
@ -20,11 +20,12 @@ class SignupForm(account.forms.SignupForm):
"password", "password",
"password_confirm" "password_confirm"
] ]
def clean_email_confirm(self): def clean_email_confirm(self):
email = self.cleaned_data.get("email") email = self.cleaned_data.get("email")
email_confirm = self.cleaned_data["email_confirm"] email_confirm = self.cleaned_data["email_confirm"]
if email: if email:
if email != email_confirm: 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 return email_confirm

View file

@ -1,14 +1,13 @@
import html5lib
from html5lib import html5parser, sanitizer from html5lib import html5parser, sanitizer
import markdown import markdown
def parse(text): def parse(text):
# First run through the Markdown parser # First run through the Markdown parser
text = markdown.markdown(text, extensions=["extra"], safe_mode=False) text = markdown.markdown(text, extensions=["extra"], safe_mode=False)
# Sanitize using html5lib # Sanitize using html5lib
bits = [] bits = []
parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer) parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer)

View file

@ -3,9 +3,8 @@ import csv
from django.http import HttpResponse from django.http import HttpResponse
def export_as_csv_action( def export_as_csv_action(description="Export selected objects as CSV file",
description="Export selected objects as CSV file", fields=None, exclude=None, header=True):
fields=None, exclude=None, header=True):
""" """
This function returns an export csv action This function returns an export csv action
'fields' and 'exclude' work like in Django ModelForm 'fields' and 'exclude' work like in Django ModelForm
@ -24,12 +23,14 @@ def export_as_csv_action(
excludeset = set(exclude) excludeset = set(exclude)
field_names = field_names - excludeset field_names = field_names - excludeset
response = HttpResponse(mimetype="text/csv") 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) writer = csv.writer(response)
if header: if header:
writer.writerow(list(field_names)) writer.writerow(list(field_names))
for obj in queryset: 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 return response
export_as_csv.short_description = description export_as_csv.short_description = description
return export_as_csv return export_as_csv

View file

@ -9,15 +9,15 @@ from symposion.proposals.models import SupportingDocument
class AddSpeakerForm(forms.Form): class AddSpeakerForm(forms.Form):
email = forms.EmailField( email = forms.EmailField(
label="Email address of new speaker (use their email address, not yours)" label="Email address of new speaker (use their email address, not yours)"
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.proposal = kwargs.pop("proposal") self.proposal = kwargs.pop("proposal")
super(AddSpeakerForm, self).__init__(*args, **kwargs) super(AddSpeakerForm, self).__init__(*args, **kwargs)
def clean_email(self): def clean_email(self):
value = self.cleaned_data["email"] value = self.cleaned_data["email"]
exists = self.proposal.additional_speakers.filter( exists = self.proposal.additional_speakers.filter(
@ -32,7 +32,7 @@ class AddSpeakerForm(forms.Form):
class SupportingDocumentCreateForm(forms.ModelForm): class SupportingDocumentCreateForm(forms.ModelForm):
class Meta: class Meta:
model = SupportingDocument model = SupportingDocument
fields = [ fields = [

View file

@ -3,15 +3,14 @@ from django.db.models.query import QuerySet
class CachingM2MQuerySet(QuerySet): class CachingM2MQuerySet(QuerySet):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(CachingM2MQuerySet, self).__init__(*args, **kwargs) super(CachingM2MQuerySet, self).__init__(*args, **kwargs)
self.cached_m2m_field = kwargs["m2m_field"] self.cached_m2m_field = kwargs["m2m_field"]
def iterator(self): def iterator(self):
parent_iter = super(CachingM2MQuerySet, self).iterator() parent_iter = super(CachingM2MQuerySet, self).iterator()
m2m_model = getattr(self.model, self.cached_m2m_field).through
for obj in parent_iter: for obj in parent_iter:
if obj.id in cached_objects: if obj.id in cached_objects:
setattr(obj, "_cached_m2m_%s" % self.cached_m2m_field) setattr(obj, "_cached_m2m_%s" % self.cached_m2m_field)
@ -21,7 +20,3 @@ class CachingM2MQuerySet(QuerySet):
class ProposalManager(models.Manager): class ProposalManager(models.Manager):
def cache_m2m(self, m2m_field): def cache_m2m(self, m2m_field):
return CachingM2MQuerySet(self.model, using=self._db, m2m_field=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)

View file

@ -21,20 +21,20 @@ from symposion.conference.models import Section
class ProposalSection(models.Model): class ProposalSection(models.Model):
""" """
configuration of proposal submissions for a specific Section. configuration of proposal submissions for a specific Section.
a section is available for proposals iff: a section is available for proposals iff:
* it is after start (if there is one) and * it is after start (if there is one) and
* it is before end (if there is one) and * it is before end (if there is one) and
* closed is NULL or False * closed is NULL or False
""" """
section = models.OneToOneField(Section) section = models.OneToOneField(Section)
start = models.DateTimeField(null=True, blank=True) start = models.DateTimeField(null=True, blank=True)
end = models.DateTimeField(null=True, blank=True) end = models.DateTimeField(null=True, blank=True)
closed = models.NullBooleanField() closed = models.NullBooleanField()
published = models.NullBooleanField() published = models.NullBooleanField()
@classmethod @classmethod
def available(cls): def available(cls):
now = datetime.datetime.now() now = datetime.datetime.now()
@ -43,7 +43,7 @@ class ProposalSection(models.Model):
Q(end__gt=now) | Q(end=None), Q(end__gt=now) | Q(end=None),
Q(closed=False) | Q(closed=None), Q(closed=False) | Q(closed=None),
) )
def is_available(self): def is_available(self):
if self.closed: if self.closed:
return False return False
@ -53,7 +53,7 @@ class ProposalSection(models.Model):
if self.end and self.end < now: if self.end and self.end < now:
return False return False
return True return True
def __unicode__(self): def __unicode__(self):
return self.section.name return self.section.name
@ -61,68 +61,77 @@ class ProposalSection(models.Model):
class ProposalKind(models.Model): class ProposalKind(models.Model):
""" """
e.g. talk vs panel vs tutorial vs poster e.g. talk vs panel vs tutorial vs poster
Note that if you have different deadlines, reviewers, etc. you'll want Note that if you have different deadlines, reviewers, etc. you'll want
to distinguish the section as well as the kind. to distinguish the section as well as the kind.
""" """
section = models.ForeignKey(Section, related_name="proposal_kinds") section = models.ForeignKey(Section, related_name="proposal_kinds")
name = models.CharField(_("Name"), max_length=100) name = models.CharField(_("Name"), max_length=100)
slug = models.SlugField() slug = models.SlugField()
def __unicode__(self): def __unicode__(self):
return self.name return self.name
class ProposalBase(models.Model): class ProposalBase(models.Model):
objects = InheritanceManager() objects = InheritanceManager()
kind = models.ForeignKey(ProposalKind) kind = models.ForeignKey(ProposalKind)
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
description = models.TextField( description = models.TextField(
_("Brief Description"), _("Brief Description"),
max_length=400, # @@@ need to enforce 400 in UI 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( abstract = MarkupField(
_("Detailed Abstract"), _("Detailed Abstract"),
help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit using <a href='http://daringfireball.net/projects/markdown/basics' target='_blank'>Markdown</a>.") help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit "
"using <a href='http://daringfireball.net/projects/markdown/basics' "
"target='_blank'>Markdown</a>.")
) )
additional_notes = MarkupField( additional_notes = MarkupField(
blank=True, blank=True,
help_text=_("Anything else you'd like the program committee to know when making their selection: your past experience, etc. This is not made public. Edit using <a href='http://daringfireball.net/projects/markdown/basics' target='_blank'>Markdown</a>.") help_text=_("Anything else you'd like the program committee to know when making their "
"selection: your past experience, etc. This is not made public. Edit using "
"<a href='http://daringfireball.net/projects/markdown/basics' "
"target='_blank'>Markdown</a>.")
) )
submitted = models.DateTimeField( submitted = models.DateTimeField(
default=datetime.datetime.now, default=datetime.datetime.now,
editable=False, editable=False,
) )
speaker = models.ForeignKey("speakers.Speaker", related_name="proposals") 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) cancelled = models.BooleanField(default=False)
def can_edit(self): def can_edit(self):
return True return True
@property @property
def section(self): def section(self):
return self.kind.section return self.kind.section
@property @property
def speaker_email(self): def speaker_email(self):
return self.speaker.email return self.speaker.email
@property @property
def number(self): def number(self):
return str(self.pk).zfill(3) return str(self.pk).zfill(3)
def speakers(self): def speakers(self):
yield self.speaker 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 yield speaker
def notification_email_context(self): def notification_email_context(self):
return { return {
"title": self.title, "title": self.title,
@ -135,21 +144,21 @@ reversion.register(ProposalBase)
class AdditionalSpeaker(models.Model): class AdditionalSpeaker(models.Model):
SPEAKING_STATUS_PENDING = 1 SPEAKING_STATUS_PENDING = 1
SPEAKING_STATUS_ACCEPTED = 2 SPEAKING_STATUS_ACCEPTED = 2
SPEAKING_STATUS_DECLINED = 3 SPEAKING_STATUS_DECLINED = 3
SPEAKING_STATUS = [ SPEAKING_STATUS = [
(SPEAKING_STATUS_PENDING, _("Pending")), (SPEAKING_STATUS_PENDING, _("Pending")),
(SPEAKING_STATUS_ACCEPTED, _("Accepted")), (SPEAKING_STATUS_ACCEPTED, _("Accepted")),
(SPEAKING_STATUS_DECLINED, _("Declined")), (SPEAKING_STATUS_DECLINED, _("Declined")),
] ]
speaker = models.ForeignKey("speakers.Speaker") speaker = models.ForeignKey("speakers.Speaker")
proposalbase = models.ForeignKey(ProposalBase) proposalbase = models.ForeignKey(ProposalBase)
status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING) status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING)
class Meta: class Meta:
db_table = "proposals_proposalbase_additional_speakers" db_table = "proposals_proposalbase_additional_speakers"
unique_together = ("speaker", "proposalbase") unique_together = ("speaker", "proposalbase")
@ -162,14 +171,15 @@ def uuid_filename(instance, filename):
class SupportingDocument(models.Model): class SupportingDocument(models.Model):
proposal = models.ForeignKey(ProposalBase, related_name="supporting_documents") proposal = models.ForeignKey(ProposalBase, related_name="supporting_documents")
uploaded_by = models.ForeignKey(User) uploaded_by = models.ForeignKey(User)
created_at = models.DateTimeField(default=datetime.datetime.now) created_at = models.DateTimeField(default=datetime.datetime.now)
file = models.FileField(upload_to=uuid_filename) file = models.FileField(upload_to=uuid_filename)
description = models.CharField(max_length=140) description = models.CharField(max_length=140)
def download_url(self): 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()])

View file

@ -7,7 +7,7 @@ register = template.Library()
class AssociatedProposalsNode(template.Node): class AssociatedProposalsNode(template.Node):
@classmethod @classmethod
def handle_token(cls, parser, token): def handle_token(cls, parser, token):
bits = token.split_contents() bits = token.split_contents()
@ -15,10 +15,10 @@ class AssociatedProposalsNode(template.Node):
return cls(bits[2]) return cls(bits[2])
else: else:
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
def __init__(self, context_var): def __init__(self, context_var):
self.context_var = context_var self.context_var = context_var
def render(self, context): def render(self, context):
request = context["request"] request = context["request"]
if request.user.speaker_profile: if request.user.speaker_profile:
@ -32,7 +32,7 @@ class AssociatedProposalsNode(template.Node):
class PendingProposalsNode(template.Node): class PendingProposalsNode(template.Node):
@classmethod @classmethod
def handle_token(cls, parser, token): def handle_token(cls, parser, token):
bits = token.split_contents() bits = token.split_contents()
@ -40,10 +40,10 @@ class PendingProposalsNode(template.Node):
return cls(bits[2]) return cls(bits[2])
else: else:
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
def __init__(self, context_var): def __init__(self, context_var):
self.context_var = context_var self.context_var = context_var
def render(self, context): def render(self, context):
request = context["request"] request = context["request"]
if request.user.speaker_profile: if request.user.speaker_profile:
@ -70,4 +70,3 @@ def associated_proposals(parser, token):
{% associated_proposals as associated_proposals %} {% associated_proposals as associated_proposals %}
""" """
return AssociatedProposalsNode.handle_token(parser, token) return AssociatedProposalsNode.handle_token(parser, token)

View file

@ -1,3 +1,4 @@
# flake8: noqa
from django.conf.urls.defaults import * 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+)/leave/$", "proposal_leave", name="proposal_leave"),
url(r"^(\d+)/join/$", "proposal_pending_join", name="proposal_pending_join"), 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+)/decline/$", "proposal_pending_decline", name="proposal_pending_decline"),
url(r"^(\d+)/document/create/$", "document_create", name="proposal_document_create"), 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+)/delete/$", "document_delete", name="proposal_document_delete"),
url(r"^document/(\d+)/([^/]+)$", "document_download", name="proposal_document_download"), url(r"^document/(\d+)/([^/]+)$", "document_download", name="proposal_document_download"),

View file

@ -37,21 +37,21 @@ def proposal_submit(request):
request.user.speaker_profile request.user.speaker_profile
except ObjectDoesNotExist: except ObjectDoesNotExist:
return redirect("dashboard") return redirect("dashboard")
kinds = [] kinds = []
for proposal_section in ProposalSection.available(): for proposal_section in ProposalSection.available():
for kind in proposal_section.section.proposal_kinds.all(): for kind in proposal_section.section.proposal_kinds.all():
kinds.append(kind) kinds.append(kind)
return render(request, "proposals/proposal_submit.html", { return render(request, "proposals/proposal_submit.html", {
"kinds": kinds, "kinds": kinds,
}) })
def proposal_submit_kind(request, kind_slug): def proposal_submit_kind(request, kind_slug):
kind = get_object_or_404(ProposalKind, slug=kind_slug) kind = get_object_or_404(ProposalKind, slug=kind_slug)
if not request.user.is_authenticated(): if not request.user.is_authenticated():
return redirect("home") # @@@ unauth'd speaker info page? return redirect("home") # @@@ unauth'd speaker info page?
else: else:
@ -59,12 +59,12 @@ def proposal_submit_kind(request, kind_slug):
speaker_profile = request.user.speaker_profile speaker_profile = request.user.speaker_profile
except ObjectDoesNotExist: except ObjectDoesNotExist:
return redirect("dashboard") return redirect("dashboard")
if not kind.section.proposalsection.is_available(): if not kind.section.proposalsection.is_available():
return redirect("proposal_submit") return redirect("proposal_submit")
form_class = get_form(settings.PROPOSAL_FORMS[kind_slug]) form_class = get_form(settings.PROPOSAL_FORMS[kind_slug])
if request.method == "POST": if request.method == "POST":
form = form_class(request.POST) form = form_class(request.POST)
if form.is_valid(): if form.is_valid():
@ -79,7 +79,7 @@ def proposal_submit_kind(request, kind_slug):
return redirect("dashboard") return redirect("dashboard")
else: else:
form = form_class() form = form_class()
return render(request, "proposals/proposal_submit_kind.html", { return render(request, "proposals/proposal_submit_kind.html", {
"kind": kind, "kind": kind,
"form": form, "form": form,
@ -91,17 +91,17 @@ def proposal_speaker_manage(request, pk):
queryset = ProposalBase.objects.select_related("speaker") queryset = ProposalBase.objects.select_related("speaker")
proposal = get_object_or_404(queryset, pk=pk) proposal = get_object_or_404(queryset, pk=pk)
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
if proposal.speaker != request.user.speaker_profile: if proposal.speaker != request.user.speaker_profile:
raise Http404() raise Http404()
if request.method == "POST": if request.method == "POST":
add_speaker_form = AddSpeakerForm(request.POST, proposal=proposal) add_speaker_form = AddSpeakerForm(request.POST, proposal=proposal)
if add_speaker_form.is_valid(): if add_speaker_form.is_valid():
message_ctx = { message_ctx = {
"proposal": proposal, "proposal": proposal,
} }
def create_speaker_token(email_address): def create_speaker_token(email_address):
# create token and look for an existing speaker to prevent # create token and look for an existing speaker to prevent
# duplicate tokens and confusing the pending speaker # 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 # fire off email to user to create profile
send_email( send_email(
[email_address], "speaker_no_profile", [email_address], "speaker_no_profile",
context = message_ctx context=message_ctx
) )
else: else:
# fire off email to user letting them they are loved. # fire off email to user letting them they are loved.
send_email( send_email(
[email_address], "speaker_addition", [email_address], "speaker_addition",
context = message_ctx context=message_ctx
) )
else: else:
speaker, token = create_speaker_token(email_address) speaker, token = create_speaker_token(email_address)
@ -150,9 +150,10 @@ def proposal_speaker_manage(request, pk):
# account and speaker profile # account and speaker profile
send_email( send_email(
[email_address], "speaker_invite", [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.") messages.success(request, "Speaker invited to proposal.")
return redirect("proposal_speaker_manage", proposal.pk) return redirect("proposal_speaker_manage", proposal.pk)
else: else:
@ -173,14 +174,14 @@ def proposal_edit(request, pk):
if request.user != proposal.speaker.user: if request.user != proposal.speaker.user:
raise Http404() raise Http404()
if not proposal.can_edit(): if not proposal.can_edit():
ctx = { ctx = {
"title": "Proposal editing closed", "title": "Proposal editing closed",
"body": "Proposal editing is closed for this session type." "body": "Proposal editing is closed for this session type."
} }
return render(request, "proposals/proposal_error.html", ctx) return render(request, "proposals/proposal_error.html", ctx)
form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug]) form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug])
if request.method == "POST": if request.method == "POST":
@ -206,7 +207,7 @@ def proposal_edit(request, pk):
return redirect("proposal_detail", proposal.pk) return redirect("proposal_detail", proposal.pk)
else: else:
form = form_class(instance=proposal) form = form_class(instance=proposal)
return render(request, "proposals/proposal_edit.html", { return render(request, "proposals/proposal_edit.html", {
"proposal": proposal, "proposal": proposal,
"form": form, "form": form,
@ -218,22 +219,22 @@ def proposal_detail(request, pk):
queryset = ProposalBase.objects.select_related("speaker", "speaker__user") queryset = ProposalBase.objects.select_related("speaker", "speaker__user")
proposal = get_object_or_404(queryset, pk=pk) proposal = get_object_or_404(queryset, pk=pk)
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
if request.user not in [p.user for p in proposal.speakers()]: if request.user not in [p.user for p in proposal.speakers()]:
raise Http404() raise Http404()
if "symposion.reviews" in settings.INSTALLED_APPS: if "symposion.reviews" in settings.INSTALLED_APPS:
from symposion.reviews.forms import SpeakerCommentForm from symposion.reviews.forms import SpeakerCommentForm
message_form = SpeakerCommentForm() message_form = SpeakerCommentForm()
if request.method == "POST": if request.method == "POST":
message_form = SpeakerCommentForm(request.POST) message_form = SpeakerCommentForm(request.POST)
if message_form.is_valid(): if message_form.is_valid():
message = message_form.save(commit=False) message = message_form.save(commit=False)
message.user = request.user message.user = request.user
message.proposal = proposal message.proposal = proposal
message.save() message.save()
ProposalMessage = SpeakerCommentForm.Meta.model ProposalMessage = SpeakerCommentForm.Meta.model
reviewers = User.objects.filter( reviewers = User.objects.filter(
id__in=ProposalMessage.objects.filter( id__in=ProposalMessage.objects.filter(
@ -242,7 +243,7 @@ def proposal_detail(request, pk):
user=request.user user=request.user
).distinct().values_list("user", flat=True) ).distinct().values_list("user", flat=True)
) )
for reviewer in reviewers: for reviewer in reviewers:
ctx = { ctx = {
"proposal": proposal, "proposal": proposal,
@ -253,13 +254,13 @@ def proposal_detail(request, pk):
[reviewer.email], "proposal_new_message", [reviewer.email], "proposal_new_message",
context=ctx context=ctx
) )
return redirect(request.path) return redirect(request.path)
else: else:
message_form = SpeakerCommentForm() message_form = SpeakerCommentForm()
else: else:
message_form = None message_form = None
return render(request, "proposals/proposal_detail.html", { return render(request, "proposals/proposal_detail.html", {
"proposal": proposal, "proposal": proposal,
"message_form": message_form "message_form": message_form
@ -271,7 +272,7 @@ def proposal_cancel(request, pk):
queryset = ProposalBase.objects.select_related("speaker") queryset = ProposalBase.objects.select_related("speaker")
proposal = get_object_or_404(queryset, pk=pk) proposal = get_object_or_404(queryset, pk=pk)
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
if proposal.speaker.user != request.user: if proposal.speaker.user != request.user:
return HttpResponseForbidden() return HttpResponseForbidden()
@ -281,7 +282,7 @@ def proposal_cancel(request, pk):
# @@@ fire off email to submitter and other speakers # @@@ fire off email to submitter and other speakers
messages.success(request, "%s has been cancelled" % proposal.title) messages.success(request, "%s has been cancelled" % proposal.title)
return redirect("dashboard") return redirect("dashboard")
return render(request, "proposals/proposal_cancel.html", { return render(request, "proposals/proposal_cancel.html", {
"proposal": proposal, "proposal": proposal,
}) })
@ -311,7 +312,8 @@ def proposal_leave(request, pk):
@login_required @login_required
def proposal_pending_join(request, pk): def proposal_pending_join(request, pk):
proposal = get_object_or_404(ProposalBase, pk=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: if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING:
speaking.status = AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED speaking.status = AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED
speaking.save() speaking.save()
@ -324,7 +326,8 @@ def proposal_pending_join(request, pk):
@login_required @login_required
def proposal_pending_decline(request, pk): def proposal_pending_decline(request, pk):
proposal = get_object_or_404(ProposalBase, pk=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: if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING:
speaking.status = AdditionalSpeaker.SPEAKING_STATUS_DECLINED speaking.status = AdditionalSpeaker.SPEAKING_STATUS_DECLINED
speaking.save() speaking.save()
@ -339,10 +342,10 @@ def document_create(request, proposal_pk):
queryset = ProposalBase.objects.select_related("speaker") queryset = ProposalBase.objects.select_related("speaker")
proposal = get_object_or_404(queryset, pk=proposal_pk) proposal = get_object_or_404(queryset, pk=proposal_pk)
proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk)
if proposal.cancelled: if proposal.cancelled:
return HttpResponseForbidden() return HttpResponseForbidden()
if request.method == "POST": if request.method == "POST":
form = SupportingDocumentCreateForm(request.POST, request.FILES) form = SupportingDocumentCreateForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
@ -353,7 +356,7 @@ def document_create(request, proposal_pk):
return redirect("proposal_detail", proposal.pk) return redirect("proposal_detail", proposal.pk)
else: else:
form = SupportingDocumentCreateForm() form = SupportingDocumentCreateForm()
return render(request, "proposals/document_create.html", { return render(request, "proposals/document_create.html", {
"proposal": proposal, "proposal": proposal,
"form": form, "form": form,
@ -378,8 +381,8 @@ def document_download(request, pk, *args):
def document_delete(request, pk): def document_delete(request, pk):
document = get_object_or_404(SupportingDocument, pk=pk, uploaded_by=request.user) document = get_object_or_404(SupportingDocument, pk=pk, uploaded_by=request.user)
proposal_pk = document.proposal.pk proposal_pk = document.proposal.pk
if request.method == "POST": if request.method == "POST":
document.delete() document.delete()
return redirect("proposal_detail", proposal_pk) return redirect("proposal_detail", proposal_pk)

View file

@ -1,5 +1,3 @@
from django.contrib.contenttypes.models import ContentType
from symposion.proposals.models import ProposalSection from symposion.proposals.models import ProposalSection

View file

@ -9,13 +9,13 @@ class ReviewForm(forms.ModelForm):
class Meta: class Meta:
model = Review model = Review
fields = ["vote", "comment"] fields = ["vote", "comment"]
widgets = { "comment": MarkItUpWidget() } widgets = {"comment": MarkItUpWidget()}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ReviewForm, self).__init__(*args, **kwargs) super(ReviewForm, self).__init__(*args, **kwargs)
self.fields["vote"] = forms.ChoiceField( self.fields["vote"] = forms.ChoiceField(
widget = forms.RadioSelect(), widget=forms.RadioSelect(),
choices = VOTES.CHOICES choices=VOTES.CHOICES
) )
@ -23,14 +23,14 @@ class ReviewCommentForm(forms.ModelForm):
class Meta: class Meta:
model = Comment model = Comment
fields = ["text"] fields = ["text"]
widgets = { "text": MarkItUpWidget() } widgets = {"text": MarkItUpWidget()}
class SpeakerCommentForm(forms.ModelForm): class SpeakerCommentForm(forms.ModelForm):
class Meta: class Meta:
model = ProposalMessage model = ProposalMessage
fields = ["message"] fields = ["message"]
widgets = { "message": MarkItUpWidget() } widgets = {"message": MarkItUpWidget()}
class BulkPresentationForm(forms.Form): class BulkPresentationForm(forms.Form):

View file

@ -1,8 +1,3 @@
import csv
import os
import random
from django.contrib.auth import models
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from symposion.reviews.models import ReviewAssignment from symposion.reviews.models import ReviewAssignment

View file

@ -1,11 +1,9 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.contrib.auth.models import Group
from symposion.reviews.models import ProposalResult from symposion.reviews.models import ProposalResult
class Command(BaseCommand): class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
ProposalResult.full_calculate() ProposalResult.full_calculate()

View file

@ -7,14 +7,14 @@ from symposion.proposals.models import ProposalSection
class Command(BaseCommand): class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
ct, created = ContentType.objects.get_or_create( ct, created = ContentType.objects.get_or_create(
model="", model="",
app_label="reviews", app_label="reviews",
defaults={"name": "reviews"} defaults={"name": "reviews"}
) )
for ps in ProposalSection.objects.all(): for ps in ProposalSection.objects.all():
for action in ["review", "manage"]: for action in ["review", "manage"]:
perm, created = Permission.objects.get_or_create( perm, created = Permission.objects.get_or_create(

View file

@ -5,11 +5,12 @@ from symposion.reviews.models import ProposalResult, promote_proposal
class Command(BaseCommand): class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
accepted_proposals = ProposalResult.objects.filter(status="accepted") accepted_proposals = ProposalResult.objects.filter(status="accepted")
accepted_proposals = accepted_proposals.order_by("proposal") accepted_proposals = accepted_proposals.order_by("proposal")
for result in accepted_proposals: for result in accepted_proposals:
promote_proposal(result.proposal) 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))")

View file

@ -15,11 +15,11 @@ from symposion.schedule.models import Presentation
class ProposalScoreExpression(object): class ProposalScoreExpression(object):
def as_sql(self, qn, connection=None): def as_sql(self, qn, connection=None):
sql = "((3 * plus_one + plus_zero) - (minus_zero + 3 * minus_one))" sql = "((3 * plus_one + plus_zero) - (minus_zero + 3 * minus_one))"
return sql, [] return sql, []
def prepare_database_save(self, unused): def prepare_database_save(self, unused):
return self return self
@ -29,7 +29,7 @@ class Votes(object):
PLUS_ZERO = "+0" PLUS_ZERO = "+0"
MINUS_ZERO = u"0" MINUS_ZERO = u"0"
MINUS_ONE = u"1" MINUS_ONE = u"1"
CHOICES = [ CHOICES = [
(PLUS_ONE, u"+1 — Good proposal and I will argue for it to be accepted."), (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."), (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 AUTO_ASSIGNED_LATER = 2
NUM_REVIEWERS = 3 NUM_REVIEWERS = 3
ORIGIN_CHOICES = [ ORIGIN_CHOICES = [
(AUTO_ASSIGNED_INITIAL, "auto-assigned, initial"), (AUTO_ASSIGNED_INITIAL, "auto-assigned, initial"),
(OPT_IN, "opted-in"), (OPT_IN, "opted-in"),
(AUTO_ASSIGNED_LATER, "auto-assigned, later"), (AUTO_ASSIGNED_LATER, "auto-assigned, later"),
] ]
proposal = models.ForeignKey("proposals.ProposalBase") proposal = models.ForeignKey("proposals.ProposalBase")
user = models.ForeignKey(User) user = models.ForeignKey(User)
origin = models.IntegerField(choices=ORIGIN_CHOICES) origin = models.IntegerField(choices=ORIGIN_CHOICES)
assigned_at = models.DateTimeField(default=datetime.now) assigned_at = models.DateTimeField(default=datetime.now)
opted_out = models.BooleanField() opted_out = models.BooleanField()
@classmethod @classmethod
def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL): def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL):
speakers = [proposal.speaker] + list(proposal.additional_speakers.all()) speakers = [proposal.speaker] + list(proposal.additional_speakers.all())
@ -94,7 +94,7 @@ class ReviewAssignment(models.Model):
class ProposalMessage(models.Model): class ProposalMessage(models.Model):
proposal = models.ForeignKey("proposals.ProposalBase", related_name="messages") proposal = models.ForeignKey("proposals.ProposalBase", related_name="messages")
user = models.ForeignKey(User) user = models.ForeignKey(User)
message = MarkupField() message = MarkupField()
submitted_at = models.DateTimeField(default=datetime.now, editable=False) submitted_at = models.DateTimeField(default=datetime.now, editable=False)
@ -104,24 +104,24 @@ class ProposalMessage(models.Model):
class Review(models.Model): class Review(models.Model):
VOTES = VOTES VOTES = VOTES
proposal = models.ForeignKey("proposals.ProposalBase", related_name="reviews") proposal = models.ForeignKey("proposals.ProposalBase", related_name="reviews")
user = models.ForeignKey(User) user = models.ForeignKey(User)
# No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel
# like some complicated encoding system. # like some complicated encoding system.
vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES) vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES)
comment = MarkupField() comment = MarkupField()
submitted_at = models.DateTimeField(default=datetime.now, editable=False) submitted_at = models.DateTimeField(default=datetime.now, editable=False)
def save(self, **kwargs): def save(self, **kwargs):
if self.vote: if self.vote:
vote, created = LatestVote.objects.get_or_create( vote, created = LatestVote.objects.get_or_create(
proposal = self.proposal, proposal=self.proposal,
user = self.user, user=self.user,
defaults = dict( defaults=dict(
vote = self.vote, vote=self.vote,
submitted_at = self.submitted_at, submitted_at=self.submitted_at,
) )
) )
if not created: if not created:
@ -130,7 +130,7 @@ class Review(models.Model):
else: else:
self.proposal.result.update_vote(self.vote) self.proposal.result.update_vote(self.vote)
super(Review, self).save(**kwargs) super(Review, self).save(**kwargs)
def delete(self): def delete(self):
model = self.__class__ model = self.__class__
user_reviews = model._default_manager.filter( user_reviews = model._default_manager.filter(
@ -152,7 +152,8 @@ class Review(models.Model):
if self == latest: if self == latest:
# self is the latest review; revert the latest vote to the # self is the latest review; revert the latest vote to the
# previous vote # 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) self.proposal.result.update_vote(self.vote, previous=previous.vote, removal=True)
lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user) lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user)
lv.update( lv.update(
@ -166,7 +167,7 @@ class Review(models.Model):
self.proposal.result.save() self.proposal.result.save()
# in all cases we need to delete the review; let's do it! # in all cases we need to delete the review; let's do it!
super(Review, self).delete() super(Review, self).delete()
def css_class(self): def css_class(self):
return { return {
self.VOTES.PLUS_ONE: "plus-one", self.VOTES.PLUS_ONE: "plus-one",
@ -174,7 +175,7 @@ class Review(models.Model):
self.VOTES.MINUS_ZERO: "minus-zero", self.VOTES.MINUS_ZERO: "minus-zero",
self.VOTES.MINUS_ONE: "minus-one", self.VOTES.MINUS_ONE: "minus-one",
}[self.vote] }[self.vote]
@property @property
def section(self): def section(self):
return self.proposal.kind.section.slug return self.proposal.kind.section.slug
@ -182,18 +183,18 @@ class Review(models.Model):
class LatestVote(models.Model): class LatestVote(models.Model):
VOTES = VOTES VOTES = VOTES
proposal = models.ForeignKey("proposals.ProposalBase", related_name="votes") proposal = models.ForeignKey("proposals.ProposalBase", related_name="votes")
user = models.ForeignKey(User) user = models.ForeignKey(User)
# No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel
# like some complicated encoding system. # like some complicated encoding system.
vote = models.CharField(max_length=2, choices=VOTES.CHOICES) vote = models.CharField(max_length=2, choices=VOTES.CHOICES)
submitted_at = models.DateTimeField(default=datetime.now, editable=False) submitted_at = models.DateTimeField(default=datetime.now, editable=False)
class Meta: class Meta:
unique_together = [("proposal", "user")] unique_together = [("proposal", "user")]
def css_class(self): def css_class(self):
return { return {
self.VOTES.PLUS_ONE: "plus-one", self.VOTES.PLUS_ONE: "plus-one",
@ -223,7 +224,7 @@ class ProposalResult(models.Model):
("undecided", "undecided"), ("undecided", "undecided"),
("standby", "standby"), ("standby", "standby"),
], default="undecided") ], default="undecided")
@classmethod @classmethod
def full_calculate(cls): def full_calculate(cls):
for proposal in ProposalBase.objects.all(): for proposal in ProposalBase.objects.all():
@ -231,24 +232,24 @@ class ProposalResult(models.Model):
result.comment_count = Review.objects.filter(proposal=proposal).count() result.comment_count = Review.objects.filter(proposal=proposal).count()
result.vote_count = LatestVote.objects.filter(proposal=proposal).count() result.vote_count = LatestVote.objects.filter(proposal=proposal).count()
result.plus_one = LatestVote.objects.filter( result.plus_one = LatestVote.objects.filter(
proposal = proposal, proposal=proposal,
vote = VOTES.PLUS_ONE vote=VOTES.PLUS_ONE
).count() ).count()
result.plus_zero = LatestVote.objects.filter( result.plus_zero = LatestVote.objects.filter(
proposal = proposal, proposal=proposal,
vote = VOTES.PLUS_ZERO vote=VOTES.PLUS_ZERO
).count() ).count()
result.minus_zero = LatestVote.objects.filter( result.minus_zero = LatestVote.objects.filter(
proposal = proposal, proposal=proposal,
vote = VOTES.MINUS_ZERO vote=VOTES.MINUS_ZERO
).count() ).count()
result.minus_one = LatestVote.objects.filter( result.minus_one = LatestVote.objects.filter(
proposal = proposal, proposal=proposal,
vote = VOTES.MINUS_ONE vote=VOTES.MINUS_ONE
).count() ).count()
result.save() result.save()
cls._default_manager.filter(pk=result.pk).update(score=ProposalScoreExpression()) cls._default_manager.filter(pk=result.pk).update(score=ProposalScoreExpression())
def update_vote(self, vote, previous=None, removal=False): def update_vote(self, vote, previous=None, removal=False):
mapping = { mapping = {
VOTES.PLUS_ONE: "plus_one", VOTES.PLUS_ONE: "plus_one",
@ -283,7 +284,7 @@ class Comment(models.Model):
proposal = models.ForeignKey("proposals.ProposalBase", related_name="comments") proposal = models.ForeignKey("proposals.ProposalBase", related_name="comments")
commenter = models.ForeignKey(User) commenter = models.ForeignKey(User)
text = MarkupField() text = MarkupField()
# Or perhaps more accurately, can the user see this comment. # Or perhaps more accurately, can the user see this comment.
public = models.BooleanField(choices=[ public = models.BooleanField(choices=[
(True, "public"), (True, "public"),
@ -293,7 +294,7 @@ class Comment(models.Model):
class NotificationTemplate(models.Model): class NotificationTemplate(models.Model):
label = models.CharField(max_length=100) label = models.CharField(max_length=100)
from_address = models.EmailField() from_address = models.EmailField()
subject = models.CharField(max_length=100) subject = models.CharField(max_length=100)
@ -301,15 +302,16 @@ class NotificationTemplate(models.Model):
class ResultNotification(models.Model): class ResultNotification(models.Model):
proposal = models.ForeignKey("proposals.ProposalBase", related_name="notifications") 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) timestamp = models.DateTimeField(default=datetime.now)
to_address = models.EmailField() to_address = models.EmailField()
from_address = models.EmailField() from_address = models.EmailField()
subject = models.CharField(max_length=100) subject = models.CharField(max_length=100)
body = models.TextField() body = models.TextField()
@property @property
def email_args(self): def email_args(self):
return (self.subject, self.body, self.from_address, [self.to_address]) return (self.subject, self.body, self.from_address, [self.to_address])
@ -321,18 +323,18 @@ def promote_proposal(proposal):
presentation = proposal.presentation presentation = proposal.presentation
else: else:
presentation = Presentation( presentation = Presentation(
title = proposal.title, title=proposal.title,
description = proposal.description, description=proposal.description,
abstract = proposal.abstract, abstract=proposal.abstract,
speaker = proposal.speaker, speaker=proposal.speaker,
section = proposal.section, section=proposal.section,
proposal_base = proposal, proposal_base=proposal,
) )
presentation.save() presentation.save()
for speaker in proposal.additional_speakers.all(): for speaker in proposal.additional_speakers.all():
presentation.additional_speakers.add(speaker) presentation.additional_speakers.add(speaker)
presentation.save() presentation.save()
return presentation return presentation

View file

@ -1,6 +1,6 @@
from django import template from django import template
from symposion.reviews.models import Review, ReviewAssignment from symposion.reviews.models import ReviewAssignment
register = template.Library() register = template.Library()

View file

@ -15,44 +15,44 @@ class login(object):
success, success,
"login with username=%r, password=%r failed" % (user, password) "login with username=%r, password=%r failed" % (user, password)
) )
def __enter__(self): def __enter__(self):
pass pass
def __exit__(self, *args): def __exit__(self, *args):
self.testcase.client.logout() self.testcase.client.logout()
class ReviewTests(TestCase): class ReviewTests(TestCase):
fixtures = ["proposals"] fixtures = ["proposals"]
def get(self, url_name, *args, **kwargs): def get(self, url_name, *args, **kwargs):
return self.client.get(reverse(url_name, args=args, kwargs=kwargs)) return self.client.get(reverse(url_name, args=args, kwargs=kwargs))
def post(self, url_name, *args, **kwargs): def post(self, url_name, *args, **kwargs):
data = kwargs.pop("data") data = kwargs.pop("data")
return self.client.post(reverse(url_name, args=args, kwargs=kwargs), data) return self.client.post(reverse(url_name, args=args, kwargs=kwargs), data)
def login(self, user, password): def login(self, user, password):
return login(self, user, password) return login(self, user, password)
def test_detail_perms(self): def test_detail_perms(self):
guidos_proposal = Proposal.objects.all()[0] guidos_proposal = Proposal.objects.all()[0]
response = self.get("review_detail", pk=guidos_proposal.pk) response = self.get("review_detail", pk=guidos_proposal.pk)
# Not logged in # Not logged in
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
with self.login("guido", "pythonisawesome"): with self.login("guido", "pythonisawesome"):
response = self.get("review_detail", pk=guidos_proposal.pk) response = self.get("review_detail", pk=guidos_proposal.pk)
# Guido can see his own proposal. # Guido can see his own proposal.
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
with self.login("matz", "pythonsucks"): with self.login("matz", "pythonsucks"):
response = self.get("review_detail", pk=guidos_proposal.pk) response = self.get("review_detail", pk=guidos_proposal.pk)
# Matz can't see guido's proposal # Matz can't see guido's proposal
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
larry = User.objects.get(username="larryw") larry = User.objects.get(username="larryw")
# Larry is a trustworthy guy, he's a reviewer. # Larry is a trustworthy guy, he's a reviewer.
larry.groups.add(Group.objects.get(name="reviewers")) larry.groups.add(Group.objects.get(name="reviewers"))
@ -60,10 +60,10 @@ class ReviewTests(TestCase):
response = self.get("review_detail", pk=guidos_proposal.pk) response = self.get("review_detail", pk=guidos_proposal.pk)
# Reviewers can see a review detail page. # Reviewers can see a review detail page.
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_reviewing(self): def test_reviewing(self):
guidos_proposal = Proposal.objects.all()[0] guidos_proposal = Proposal.objects.all()[0]
with self.login("guido", "pythonisawesome"): with self.login("guido", "pythonisawesome"):
response = self.post("review_review", pk=guidos_proposal.pk, data={ response = self.post("review_review", pk=guidos_proposal.pk, data={
"vote": "+1", "vote": "+1",
@ -72,7 +72,7 @@ class ReviewTests(TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
# ... no vote recorded # ... no vote recorded
self.assertEqual(guidos_proposal.reviews.count(), 0) self.assertEqual(guidos_proposal.reviews.count(), 0)
larry = User.objects.get(username="larryw") larry = User.objects.get(username="larryw")
# Larry is a trustworthy guy, he's a reviewer. # Larry is a trustworthy guy, he's a reviewer.
larry.groups.add(Group.objects.get(name="reviewers")) larry.groups.add(Group.objects.get(name="reviewers"))
@ -90,7 +90,7 @@ class ReviewTests(TestCase):
self.assertEqual(guidos_proposal.comments.count(), 1) self.assertEqual(guidos_proposal.comments.count(), 1)
comment = guidos_proposal.comments.get() comment = guidos_proposal.comments.get()
self.assertFalse(comment.public) self.assertFalse(comment.public)
response = self.post("review_review", pk=guidos_proposal.pk, data={ response = self.post("review_review", pk=guidos_proposal.pk, data={
"vote": "+1", "vote": "+1",
"text": "Actually Perl is dead, we really need a talk on the future", "text": "Actually Perl is dead, we really need a talk on the future",
@ -100,21 +100,21 @@ class ReviewTests(TestCase):
assignment = ReviewAssignment.objects.get() assignment = ReviewAssignment.objects.get()
self.assertEqual(assignment.review, Review.objects.order_by("-id")[0]) self.assertEqual(assignment.review, Review.objects.order_by("-id")[0])
self.assertEqual(guidos_proposal.comments.count(), 2) self.assertEqual(guidos_proposal.comments.count(), 2)
# Larry's a big fan... # Larry's a big fan...
response = self.post("review_review", pk=guidos_proposal.pk, data={ response = self.post("review_review", pk=guidos_proposal.pk, data={
"vote": "+20", "vote": "+20",
}) })
self.assertEqual(guidos_proposal.reviews.count(), 2) self.assertEqual(guidos_proposal.reviews.count(), 2)
def test_speaker_commenting(self): def test_speaker_commenting(self):
guidos_proposal = Proposal.objects.all()[0] guidos_proposal = Proposal.objects.all()[0]
with self.login("guido", "pythonisawesome"): with self.login("guido", "pythonisawesome"):
response = self.get("review_comment", pk=guidos_proposal.pk) response = self.get("review_comment", pk=guidos_proposal.pk)
# Guido can comment on his proposal. # Guido can comment on his proposal.
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
response = self.post("review_comment", pk=guidos_proposal.pk, data={ response = self.post("review_comment", pk=guidos_proposal.pk, data={
"text": "FYI I can do this as a 30-minute or 45-minute talk.", "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) self.assertEqual(guidos_proposal.comments.count(), 1)
comment = guidos_proposal.comments.get() comment = guidos_proposal.comments.get()
self.assertTrue(comment.public) self.assertTrue(comment.public)
larry = User.objects.get(username="larryw") larry = User.objects.get(username="larryw")
# Larry is a trustworthy guy, he's a reviewer. # Larry is a trustworthy guy, he's a reviewer.
larry.groups.add(Group.objects.get(name="reviewers")) larry.groups.add(Group.objects.get(name="reviewers"))
@ -130,13 +130,13 @@ class ReviewTests(TestCase):
response = self.get("review_comment", pk=guidos_proposal.pk) response = self.get("review_comment", pk=guidos_proposal.pk)
# Larry can comment, since he's a reviewer # Larry can comment, since he's a reviewer
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
response = self.post("review_comment", pk=guidos_proposal.pk, data={ response = self.post("review_comment", pk=guidos_proposal.pk, data={
"text": "Thanks for the heads-up Guido." "text": "Thanks for the heads-up Guido."
}) })
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(guidos_proposal.comments.count(), 2) self.assertEqual(guidos_proposal.comments.count(), 2)
with self.login("matz", "pythonsucks"): with self.login("matz", "pythonsucks"):
response = self.get("review_comment", pk=guidos_proposal.pk) response = self.get("review_comment", pk=guidos_proposal.pk)
# Matz can't comment. # Matz can't comment.

View file

@ -1,3 +1,4 @@
# flake8: noqa
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url
@ -14,9 +15,9 @@ urlpatterns = patterns("symposion.reviews.views",
url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/$", "result_notification", name="result_notification"), url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/$", "result_notification", name="result_notification"),
url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/prepare/$", "result_notification_prepare", name="result_notification_prepare"), url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/prepare/$", "result_notification_prepare", name="result_notification_prepare"),
url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/send/$", "result_notification_send", name="result_notification_send"), url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/send/$", "result_notification_send", name="result_notification_send"),
url(r"^review/(?P<pk>\d+)/$", "review_detail", name="review_detail"), url(r"^review/(?P<pk>\d+)/$", "review_detail", name="review_detail"),
url(r"^(?P<pk>\d+)/delete/$", "review_delete", name="review_delete"), url(r"^(?P<pk>\d+)/delete/$", "review_delete", name="review_delete"),
url(r"^assignments/$", "review_assignments", name="review_assignments"), url(r"^assignments/$", "review_assignments", name="review_assignments"),
url(r"^assignment/(?P<pk>\d+)/opt-out/$", "review_assignment_opt_out", name="review_assignment_opt_out"), url(r"^assignment/(?P<pk>\d+)/opt-out/$", "review_assignment_opt_out", name="review_assignment_opt_out"),

View file

@ -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, Returns whether or not ther user has permission to review this proposal,
with the specified requirements. 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 proposal. If ``reviewer`` is ``True`` the speaker can be a part of the
reviewer group. reviewer group.
""" """
if user.is_superuser: if user.is_superuser:
return True return True
if speaker: if speaker:
if (user == proposal.speaker.user or if user == proposal.speaker.user or \
proposal.additional_speakers.filter(user=user).exists()): proposal.additional_speakers.filter(user=user).exists():
return True return True
if reviewer: if reviewer:
if user.groups.filter(name="reviewers").exists(): if user.groups.filter(name="reviewers").exists():

View file

@ -1,5 +1,3 @@
import re
from django.core.mail import send_mass_mail from django.core.mail import send_mass_mail
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponseBadRequest, HttpResponseNotAllowed 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): def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
for obj in queryset: for obj in queryset:
# @@@ this sucks; we can do better # @@@ this sucks; we can do better
if check_speaker: if check_speaker:
if request.user in [s.user for s in obj.speakers()]: if request.user in [s.user for s in obj.speakers()]:
continue continue
try: try:
obj.result obj.result
except ProposalResult.DoesNotExist: except ProposalResult.DoesNotExist:
ProposalResult.objects.get_or_create(proposal=obj) ProposalResult.objects.get_or_create(proposal=obj)
obj.comment_count = obj.result.comment_count obj.comment_count = obj.result.comment_count
obj.total_votes = obj.result.vote_count obj.total_votes = obj.result.vote_count
obj.plus_one = obj.result.plus_one 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_zero = obj.result.minus_zero
obj.minus_one = obj.result.minus_one obj.minus_one = obj.result.minus_one
lookup_params = dict(proposal=obj) lookup_params = dict(proposal=obj)
if user_pk: if user_pk:
lookup_params["user__pk"] = user_pk lookup_params["user__pk"] = user_pk
else: else:
lookup_params["user"] = request.user lookup_params["user"] = request.user
try: try:
obj.user_vote = LatestVote.objects.get(**lookup_params).vote obj.user_vote = LatestVote.objects.get(**lookup_params).vote
obj.user_vote_css = LatestVote.objects.get(**lookup_params).css_class() obj.user_vote_css = LatestVote.objects.get(**lookup_params).css_class()
except LatestVote.DoesNotExist: except LatestVote.DoesNotExist:
obj.user_vote = None obj.user_vote = None
obj.user_vote_css = "no-vote" obj.user_vote_css = "no-vote"
yield obj yield obj
# Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has yet to review # Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has
# depending on the link user clicks in dashboard # yet to review depending on the link user clicks in dashboard
@login_required @login_required
def review_section(request, section_slug, assigned=False, reviewed="all"): def review_section(request, section_slug, assigned=False, reviewed="all"):
if not request.user.has_perm("reviews.can_review_%s" % section_slug): if not request.user.has_perm("reviews.can_review_%s" % section_slug):
return access_not_permitted(request) return access_not_permitted(request)
section = get_object_or_404(ProposalSection, section__slug=section_slug) section = get_object_or_404(ProposalSection, section__slug=section_slug)
queryset = ProposalBase.objects.filter(kind__section=section) queryset = ProposalBase.objects.filter(kind__section=section)
if assigned: 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) queryset = queryset.filter(id__in=assignments)
# passing reviewed in from reviews.urls and out to review_list for # passing reviewed in from reviews.urls and out to review_list for
# appropriate template header rendering # appropriate template header rendering
if reviewed == "all": if reviewed == "all":
@ -88,35 +87,36 @@ def review_section(request, section_slug, assigned=False, reviewed="all"):
else: else:
queryset = queryset.exclude(reviews__user=request.user).exclude(speaker=request.user) queryset = queryset.exclude(reviews__user=request.user).exclude(speaker=request.user)
reviewed = "user_not_reviewed" reviewed = "user_not_reviewed"
proposals = proposals_generator(request, queryset) proposals = proposals_generator(request, queryset)
ctx = { ctx = {
"proposals": proposals, "proposals": proposals,
"section": section, "section": section,
"reviewed": reviewed, "reviewed": reviewed,
} }
return render(request, "reviews/review_list.html", ctx) return render(request, "reviews/review_list.html", ctx)
@login_required @login_required
def review_list(request, section_slug, user_pk): def review_list(request, section_slug, user_pk):
# if they're not a reviewer admin and they aren't the person whose # 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 # 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.has_perm("reviews.can_manage_%s" % section_slug):
if not request.user.pk == user_pk: if not request.user.pk == user_pk:
return access_not_permitted(request) return access_not_permitted(request)
queryset = ProposalBase.objects.select_related("speaker__user", "result") queryset = ProposalBase.objects.select_related("speaker__user", "result")
reviewed = LatestVote.objects.filter(user__pk=user_pk).values_list("proposal", flat=True) reviewed = LatestVote.objects.filter(user__pk=user_pk).values_list("proposal", flat=True)
queryset = queryset.filter(pk__in=reviewed) queryset = queryset.filter(pk__in=reviewed)
proposals = queryset.order_by("submitted") proposals = queryset.order_by("submitted")
admin = request.user.has_perm("reviews.can_manage_%s" % section_slug) admin = request.user.has_perm("reviews.can_manage_%s" % section_slug)
proposals = proposals_generator(request, proposals, user_pk=user_pk, check_speaker=not admin) proposals = proposals_generator(request, proposals, user_pk=user_pk, check_speaker=not admin)
ctx = { ctx = {
"proposals": proposals, "proposals": proposals,
} }
@ -125,41 +125,41 @@ def review_list(request, section_slug, user_pk):
@login_required @login_required
def review_admin(request, section_slug): def review_admin(request, section_slug):
if not request.user.has_perm("reviews.can_manage_%s" % section_slug): if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request) return access_not_permitted(request)
def reviewers(): def reviewers():
already_seen = set() already_seen = set()
for team in Team.objects.filter(permissions__codename="can_review_%s" % section_slug): 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")): for membership in team.memberships.filter(Q(state="member") | Q(state="manager")):
user = membership.user user = membership.user
if user.pk in already_seen: if user.pk in already_seen:
continue continue
already_seen.add(user.pk) already_seen.add(user.pk)
user.comment_count = Review.objects.filter(user=user).count() user.comment_count = Review.objects.filter(user=user).count()
user.total_votes = LatestVote.objects.filter(user=user).count() user.total_votes = LatestVote.objects.filter(user=user).count()
user.plus_one = LatestVote.objects.filter( user.plus_one = LatestVote.objects.filter(
user = user, user=user,
vote = LatestVote.VOTES.PLUS_ONE vote=LatestVote.VOTES.PLUS_ONE
).count() ).count()
user.plus_zero = LatestVote.objects.filter( user.plus_zero = LatestVote.objects.filter(
user = user, user=user,
vote = LatestVote.VOTES.PLUS_ZERO vote=LatestVote.VOTES.PLUS_ZERO
).count() ).count()
user.minus_zero = LatestVote.objects.filter( user.minus_zero = LatestVote.objects.filter(
user = user, user=user,
vote = LatestVote.VOTES.MINUS_ZERO vote=LatestVote.VOTES.MINUS_ZERO
).count() ).count()
user.minus_one = LatestVote.objects.filter( user.minus_one = LatestVote.objects.filter(
user = user, user=user,
vote = LatestVote.VOTES.MINUS_ONE vote=LatestVote.VOTES.MINUS_ONE
).count() ).count()
yield user yield user
ctx = { ctx = {
"section_slug": section_slug, "section_slug": section_slug,
"reviewers": reviewers(), "reviewers": reviewers(),
@ -169,50 +169,50 @@ def review_admin(request, section_slug):
@login_required @login_required
def review_detail(request, pk): def review_detail(request, pk):
proposals = ProposalBase.objects.select_related("result").select_subclasses() proposals = ProposalBase.objects.select_related("result").select_subclasses()
proposal = get_object_or_404(proposals, pk=pk) proposal = get_object_or_404(proposals, pk=pk)
if not request.user.has_perm("reviews.can_review_%s" % proposal.kind.section.slug): if not request.user.has_perm("reviews.can_review_%s" % proposal.kind.section.slug):
return access_not_permitted(request) return access_not_permitted(request)
speakers = [s.user for s in proposal.speakers()] speakers = [s.user for s in proposal.speakers()]
if not request.user.is_superuser and request.user in speakers: if not request.user.is_superuser and request.user in speakers:
return access_not_permitted(request) return access_not_permitted(request)
admin = request.user.is_staff admin = request.user.is_staff
try: try:
latest_vote = LatestVote.objects.get(proposal=proposal, user=request.user) latest_vote = LatestVote.objects.get(proposal=proposal, user=request.user)
except LatestVote.DoesNotExist: except LatestVote.DoesNotExist:
latest_vote = None latest_vote = None
if request.method == "POST": if request.method == "POST":
if request.user in speakers: if request.user in speakers:
return access_not_permitted(request) return access_not_permitted(request)
if "vote_submit" in request.POST: if "vote_submit" in request.POST:
review_form = ReviewForm(request.POST) review_form = ReviewForm(request.POST)
if review_form.is_valid(): if review_form.is_valid():
review = review_form.save(commit=False) review = review_form.save(commit=False)
review.user = request.user review.user = request.user
review.proposal = proposal review.proposal = proposal
review.save() review.save()
return redirect(request.path) return redirect(request.path)
else: else:
message_form = SpeakerCommentForm() message_form = SpeakerCommentForm()
elif "message_submit" in request.POST: elif "message_submit" in request.POST:
message_form = SpeakerCommentForm(request.POST) message_form = SpeakerCommentForm(request.POST)
if message_form.is_valid(): if message_form.is_valid():
message = message_form.save(commit=False) message = message_form.save(commit=False)
message.user = request.user message.user = request.user
message.proposal = proposal message.proposal = proposal
message.save() message.save()
for speaker in speakers: for speaker in speakers:
if speaker and speaker.email: if speaker and speaker.email:
ctx = { ctx = {
@ -222,9 +222,9 @@ def review_detail(request, pk):
} }
send_email( send_email(
[speaker.email], "proposal_new_message", [speaker.email], "proposal_new_message",
context = ctx context=ctx
) )
return redirect(request.path) return redirect(request.path)
else: else:
initial = {} initial = {}
@ -237,7 +237,7 @@ def review_detail(request, pk):
elif "result_submit" in request.POST: elif "result_submit" in request.POST:
if admin: if admin:
result = request.POST["result_submit"] result = request.POST["result_submit"]
if result == "accept": if result == "accept":
proposal.result.status = "accepted" proposal.result.status = "accepted"
proposal.result.save() proposal.result.save()
@ -250,7 +250,7 @@ def review_detail(request, pk):
elif result == "standby": elif result == "standby":
proposal.result.status = "standby" proposal.result.status = "standby"
proposal.result.save() proposal.result.save()
return redirect(request.path) return redirect(request.path)
else: else:
initial = {} initial = {}
@ -261,17 +261,17 @@ def review_detail(request, pk):
else: else:
review_form = ReviewForm(initial=initial) review_form = ReviewForm(initial=initial)
message_form = SpeakerCommentForm() message_form = SpeakerCommentForm()
proposal.comment_count = proposal.result.comment_count proposal.comment_count = proposal.result.comment_count
proposal.total_votes = proposal.result.vote_count proposal.total_votes = proposal.result.vote_count
proposal.plus_one = proposal.result.plus_one proposal.plus_one = proposal.result.plus_one
proposal.plus_zero = proposal.result.plus_zero proposal.plus_zero = proposal.result.plus_zero
proposal.minus_zero = proposal.result.minus_zero proposal.minus_zero = proposal.result.minus_zero
proposal.minus_one = proposal.result.minus_one proposal.minus_one = proposal.result.minus_one
reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at") reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at")
messages = proposal.messages.order_by("submitted_at") messages = proposal.messages.order_by("submitted_at")
return render(request, "reviews/review_detail.html", { return render(request, "reviews/review_detail.html", {
"proposal": proposal, "proposal": proposal,
"latest_vote": latest_vote, "latest_vote": latest_vote,
@ -287,53 +287,64 @@ def review_detail(request, pk):
def review_delete(request, pk): def review_delete(request, pk):
review = get_object_or_404(Review, pk=pk) review = get_object_or_404(Review, pk=pk)
section_slug = review.section.slug section_slug = review.section.slug
if not request.user.has_perm("reviews.can_manage_%s" % section_slug): if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request) return access_not_permitted(request)
review = get_object_or_404(Review, pk=pk) review = get_object_or_404(Review, pk=pk)
review.delete() review.delete()
return redirect("review_detail", pk=review.proposal.pk) return redirect("review_detail", pk=review.proposal.pk)
@login_required @login_required
def review_status(request, section_slug=None, key=None): def review_status(request, section_slug=None, key=None):
if not request.user.has_perm("reviews.can_review_%s" % section_slug): if not request.user.has_perm("reviews.can_review_%s" % section_slug):
return access_not_permitted(request) return access_not_permitted(request)
VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD
ctx = { ctx = {
"section_slug": section_slug, "section_slug": section_slug,
"vote_threshold": VOTE_THRESHOLD, "vote_threshold": VOTE_THRESHOLD,
} }
queryset = ProposalBase.objects.select_related("speaker__user", "result").select_subclasses() queryset = ProposalBase.objects.select_related("speaker__user", "result").select_subclasses()
if section_slug: if section_slug:
queryset = queryset.filter(kind__section__slug=section_slug) queryset = queryset.filter(kind__section__slug=section_slug)
proposals = { proposals = {
# proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by the 'score' # proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by
"positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one=0).order_by("-result__score"), # the 'score'
# proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse sorted by the 'score' "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0,
"negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0, result__plus_one=0).order_by("result__score"), result__minus_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) # proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse
"indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one=0, result__plus_one=0).order_by("result__vote_count"), # sorted by the 'score'
# proposals with at least VOTE_THRESHOLD reviews and both a +1 and -1, sorted by total votes (highest first) "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0,
"controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one__gt=0).order_by("-result__vote_count"), 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 # 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) admin = request.user.has_perm("reviews.can_manage_%s" % section_slug)
for status in proposals: for status in proposals:
if key and key != status: if key and key != status:
continue 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: if key:
ctx.update({ ctx.update({
"key": key, "key": key,
@ -341,7 +352,7 @@ def review_status(request, section_slug=None, key=None):
}) })
else: else:
ctx["proposals"] = proposals ctx["proposals"] = proposals
return render(request, "reviews/review_stats.html", ctx) return render(request, "reviews/review_stats.html", ctx)
@ -361,14 +372,13 @@ def review_assignments(request):
@login_required @login_required
@require_POST @require_POST
def review_assignment_opt_out(request, pk): def review_assignment_opt_out(request, pk):
review_assignment = get_object_or_404(ReviewAssignment, review_assignment = get_object_or_404(
pk=pk, ReviewAssignment, pk=pk, user=request.user)
user=request.user
)
if not review_assignment.opted_out: if not review_assignment.opted_out:
review_assignment.opted_out = True review_assignment.opted_out = True
review_assignment.save() 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") return redirect("review_assignments")
@ -387,7 +397,7 @@ def review_bulk_accept(request, section_slug):
return redirect("review_section", section_slug=section_slug) return redirect("review_section", section_slug=section_slug)
else: else:
form = BulkPresentationForm() form = BulkPresentationForm()
return render(request, "reviews/review_bulk_accept.html", { return render(request, "reviews/review_bulk_accept.html", {
"form": form, "form": form,
}) })
@ -397,10 +407,12 @@ def review_bulk_accept(request, section_slug):
def result_notification(request, section_slug, status): def result_notification(request, section_slug, status):
if not request.user.has_perm("reviews.can_manage_%s" % section_slug): if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request) 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() notification_templates = NotificationTemplate.objects.all()
ctx = { ctx = {
"section_slug": section_slug, "section_slug": section_slug,
"status": status, "status": status,
@ -414,10 +426,10 @@ def result_notification(request, section_slug, status):
def result_notification_prepare(request, section_slug, status): def result_notification_prepare(request, section_slug, status):
if request.method != "POST": if request.method != "POST":
return HttpResponseNotAllowed(["POST"]) return HttpResponseNotAllowed(["POST"])
if not request.user.has_perm("reviews.can_manage_%s" % section_slug): if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request) return access_not_permitted(request)
proposal_pks = [] proposal_pks = []
try: try:
for pk in request.POST.getlist("_selected_action"): 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.filter(pk__in=proposal_pks)
proposals = proposals.select_related("speaker__user", "result") proposals = proposals.select_related("speaker__user", "result")
proposals = proposals.select_subclasses() proposals = proposals.select_subclasses()
notification_template_pk = request.POST.get("notification_template", "") notification_template_pk = request.POST.get("notification_template", "")
if notification_template_pk: if notification_template_pk:
notification_template = NotificationTemplate.objects.get(pk=notification_template_pk) notification_template = NotificationTemplate.objects.get(pk=notification_template_pk)
else: else:
notification_template = None notification_template = None
ctx = { ctx = {
"section_slug": section_slug, "section_slug": section_slug,
"status": status, "status": status,
@ -452,18 +464,18 @@ def result_notification_prepare(request, section_slug, status):
def result_notification_send(request, section_slug, status): def result_notification_send(request, section_slug, status):
if request.method != "POST": if request.method != "POST":
return HttpResponseNotAllowed(["POST"]) return HttpResponseNotAllowed(["POST"])
if not request.user.has_perm("reviews.can_manage_%s" % section_slug): if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request) return access_not_permitted(request)
if not all([k in request.POST for k in ["proposal_pks", "from_address", "subject", "body"]]): if not all([k in request.POST for k in ["proposal_pks", "from_address", "subject", "body"]]):
return HttpResponseBadRequest() return HttpResponseBadRequest()
try: try:
proposal_pks = [int(pk) for pk in request.POST["proposal_pks"].split(",")] proposal_pks = [int(pk) for pk in request.POST["proposal_pks"].split(",")]
except ValueError: except ValueError:
return HttpResponseBadRequest() return HttpResponseBadRequest()
proposals = ProposalBase.objects.filter( proposals = ProposalBase.objects.filter(
kind__section__slug=section_slug, kind__section__slug=section_slug,
result__status=status, result__status=status,
@ -471,15 +483,15 @@ def result_notification_send(request, section_slug, status):
proposals = proposals.filter(pk__in=proposal_pks) proposals = proposals.filter(pk__in=proposal_pks)
proposals = proposals.select_related("speaker__user", "result") proposals = proposals.select_related("speaker__user", "result")
proposals = proposals.select_subclasses() proposals = proposals.select_subclasses()
notification_template_pk = request.POST.get("notification_template", "") notification_template_pk = request.POST.get("notification_template", "")
if notification_template_pk: if notification_template_pk:
notification_template = NotificationTemplate.objects.get(pk=notification_template_pk) notification_template = NotificationTemplate.objects.get(pk=notification_template_pk)
else: else:
notification_template = None notification_template = None
emails = [] emails = []
for proposal in proposals: for proposal in proposals:
rn = ResultNotification() rn = ResultNotification()
rn.proposal = proposal rn.proposal = proposal
@ -494,7 +506,7 @@ def result_notification_send(request, section_slug, status):
) )
rn.save() rn.save()
emails.append(rn.email_args) emails.append(rn.email_args)
send_mass_mail(emails) send_mass_mail(emails)
return redirect("result_notification", section_slug=section_slug, status=status) return redirect("result_notification", section_slug=section_slug, status=status)

View file

@ -7,7 +7,7 @@ from symposion.schedule.models import Presentation
class SlotEditForm(forms.Form): class SlotEditForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.slot = kwargs.pop("slot") self.slot = kwargs.pop("slot")
super(SlotEditForm, self).__init__(*args, **kwargs) super(SlotEditForm, self).__init__(*args, **kwargs)
@ -16,7 +16,7 @@ class SlotEditForm(forms.Form):
self.fields["presentation"] = self.build_presentation_field() self.fields["presentation"] = self.build_presentation_field()
else: else:
self.fields["content_override"] = self.build_content_override_field() self.fields["content_override"] = self.build_content_override_field()
def build_presentation_field(self): def build_presentation_field(self):
kwargs = {} kwargs = {}
queryset = Presentation.objects.all() queryset = Presentation.objects.all()
@ -31,7 +31,7 @@ class SlotEditForm(forms.Form):
kwargs["required"] = True kwargs["required"] = True
kwargs["queryset"] = queryset kwargs["queryset"] = queryset
return forms.ModelChoiceField(**kwargs) return forms.ModelChoiceField(**kwargs)
def build_content_override_field(self): def build_content_override_field(self):
kwargs = { kwargs = {
"label": "Content", "label": "Content",

View file

@ -2,44 +2,43 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db import models from django.db import models
from markitup.fields import MarkupField from markitup.fields import MarkupField
from model_utils.managers import InheritanceManager
from symposion.proposals.models import ProposalBase from symposion.proposals.models import ProposalBase
from symposion.conference.models import Section from symposion.conference.models import Section
class Schedule(models.Model): class Schedule(models.Model):
section = models.OneToOneField(Section) section = models.OneToOneField(Section)
published = models.BooleanField(default=True) published = models.BooleanField(default=True)
hidden = models.BooleanField("Hide schedule from overall conference view", default=False) hidden = models.BooleanField("Hide schedule from overall conference view", default=False)
def __unicode__(self): def __unicode__(self):
return "%s Schedule" % self.section return "%s Schedule" % self.section
class Meta: class Meta:
ordering = ["section"] ordering = ["section"]
class Day(models.Model): class Day(models.Model):
schedule = models.ForeignKey(Schedule) schedule = models.ForeignKey(Schedule)
date = models.DateField() date = models.DateField()
def __unicode__(self): def __unicode__(self):
return "%s" % self.date return "%s" % self.date
class Meta: class Meta:
unique_together = [("schedule", "date")] unique_together = [("schedule", "date")]
ordering = ["date"] ordering = ["date"]
class Room(models.Model): class Room(models.Model):
schedule = models.ForeignKey(Schedule) schedule = models.ForeignKey(Schedule)
name = models.CharField(max_length=65) name = models.CharField(max_length=65)
order = models.PositiveIntegerField() order = models.PositiveIntegerField()
def __unicode__(self): def __unicode__(self):
return self.name 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 A slot kind represents what kind a slot is. For example, a slot can be a
break, lunch, or X-minute talk. break, lunch, or X-minute talk.
""" """
schedule = models.ForeignKey(Schedule) schedule = models.ForeignKey(Schedule)
label = models.CharField(max_length=50) label = models.CharField(max_length=50)
def __unicode__(self): def __unicode__(self):
return self.label return self.label
class Slot(models.Model): class Slot(models.Model):
day = models.ForeignKey(Day) day = models.ForeignKey(Day)
kind = models.ForeignKey(SlotKind) kind = models.ForeignKey(SlotKind)
start = models.TimeField() start = models.TimeField()
end = models.TimeField() end = models.TimeField()
content_override = MarkupField(blank=True) content_override = MarkupField(blank=True)
def assign(self, content): def assign(self, content):
""" """
Assign the given content to this slot and if a previous slot content Assign the given content to this slot and if a previous slot content
@ -73,7 +72,7 @@ class Slot(models.Model):
self.unassign() self.unassign()
content.slot = self content.slot = self
content.save() content.save()
def unassign(self): def unassign(self):
""" """
Unassign the associated content with this slot. Unassign the associated content with this slot.
@ -81,7 +80,7 @@ class Slot(models.Model):
if self.content and self.content.slot_id: if self.content and self.content.slot_id:
self.content.slot = None self.content.slot = None
self.content.save() self.content.save()
@property @property
def content(self): def content(self):
""" """
@ -92,14 +91,14 @@ class Slot(models.Model):
return self.content_ptr return self.content_ptr
except ObjectDoesNotExist: except ObjectDoesNotExist:
return None return None
@property @property
def rooms(self): def rooms(self):
return Room.objects.filter(pk__in=self.slotroom_set.values("room")) return Room.objects.filter(pk__in=self.slotroom_set.values("room"))
def __unicode__(self): def __unicode__(self):
return "%s %s (%s - %s)" % (self.day, self.kind, self.start, self.end) return "%s %s (%s - %s)" % (self.day, self.kind, self.start, self.end)
class Meta: class Meta:
ordering = ["day", "start", "end"] ordering = ["day", "start", "end"]
@ -108,48 +107,49 @@ class SlotRoom(models.Model):
""" """
Links a slot with a room. Links a slot with a room.
""" """
slot = models.ForeignKey(Slot) slot = models.ForeignKey(Slot)
room = models.ForeignKey(Room) room = models.ForeignKey(Room)
def __unicode__(self): def __unicode__(self):
return "%s %s" % (self.room, self.slot) return "%s %s" % (self.room, self.slot)
class Meta: class Meta:
unique_together = [("slot", "room")] unique_together = [("slot", "room")]
ordering = ["slot", "room__order"] ordering = ["slot", "room__order"]
class Presentation(models.Model): class Presentation(models.Model):
slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr") slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr")
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
description = MarkupField() description = MarkupField()
abstract = MarkupField() abstract = MarkupField()
speaker = models.ForeignKey("speakers.Speaker", related_name="presentations") 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) cancelled = models.BooleanField(default=False)
proposal_base = models.OneToOneField(ProposalBase, related_name="presentation") proposal_base = models.OneToOneField(ProposalBase, related_name="presentation")
section = models.ForeignKey(Section, related_name="presentations") section = models.ForeignKey(Section, related_name="presentations")
@property @property
def number(self): def number(self):
return self.proposal.number return self.proposal.number
@property @property
def proposal(self): def proposal(self):
if self.proposal_base_id is None: if self.proposal_base_id is None:
return None return None
return ProposalBase.objects.get_subclass(pk=self.proposal_base_id) return ProposalBase.objects.get_subclass(pk=self.proposal_base_id)
def speakers(self): def speakers(self):
yield self.speaker yield self.speaker
for speaker in self.additional_speakers.all(): for speaker in self.additional_speakers.all():
if speaker.user: if speaker.user:
yield speaker yield speaker
def __unicode__(self): def __unicode__(self):
return "#%s %s (%s)" % (self.number, self.title, self.speaker) return "#%s %s (%s)" % (self.number, self.title, self.speaker)
class Meta: class Meta:
ordering = ["slot"] ordering = ["slot"]

View file

@ -1,5 +1,4 @@
import itertools import itertools
import operator
from django.db.models import Count, Min from django.db.models import Count, Min
@ -7,22 +6,23 @@ from symposion.schedule.models import Room, Slot, SlotRoom
class TimeTable(object): class TimeTable(object):
def __init__(self, day): def __init__(self, day):
self.day = day self.day = day
def slots_qs(self): def slots_qs(self):
qs = Slot.objects.all() qs = Slot.objects.all()
qs = qs.filter(day=self.day) qs = qs.filter(day=self.day)
return qs return qs
def rooms(self): def rooms(self):
qs = Room.objects.all() qs = Room.objects.all()
qs = qs.filter(schedule=self.day.schedule) 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") qs = qs.order_by("order")
return qs return qs
def __iter__(self): def __iter__(self):
times = sorted(set(itertools.chain(*self.slots_qs().values_list("start", "end")))) times = sorted(set(itertools.chain(*self.slots_qs().values_list("start", "end"))))
slots = Slot.objects.filter(pk__in=self.slots_qs().values("pk")) slots = Slot.objects.filter(pk__in=self.slots_qs().values("pk"))
@ -38,7 +38,7 @@ class TimeTable(object):
row["slots"].append(slot) row["slots"].append(slot)
if row["slots"] or next_time is None: if row["slots"] or next_time is None:
yield row yield row
@staticmethod @staticmethod
def rowspan(times, start, end): def rowspan(times, start, end):
return times.index(end) - times.index(start) return times.index(end) - times.index(start)

View file

@ -1,3 +1,4 @@
# flake8: noqa
from django.conf.urls.defaults import url, patterns from django.conf.urls.defaults import url, patterns

View file

@ -1,4 +1,3 @@
from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.template import loader, Context from django.template import loader, Context
@ -12,7 +11,7 @@ from symposion.schedule.timetable import TimeTable
def fetch_schedule(slug): def fetch_schedule(slug):
qs = Schedule.objects.all() qs = Schedule.objects.all()
if slug is None: if slug is None:
if qs.count() > 1: if qs.count() > 1:
raise Http404() raise Http404()
@ -21,14 +20,14 @@ def fetch_schedule(slug):
raise Http404() raise Http404()
else: else:
schedule = get_object_or_404(qs, section__slug=slug) schedule = get_object_or_404(qs, section__slug=slug)
return schedule return schedule
def schedule_conference(request): def schedule_conference(request):
schedules = Schedule.objects.filter(published=True, hidden=False) schedules = Schedule.objects.filter(published=True, hidden=False)
sections = [] sections = []
for schedule in schedules: for schedule in schedules:
days_qs = Day.objects.filter(schedule=schedule) days_qs = Day.objects.filter(schedule=schedule)
@ -37,7 +36,7 @@ def schedule_conference(request):
"schedule": schedule, "schedule": schedule,
"days": days, "days": days,
}) })
ctx = { ctx = {
"sections": sections, "sections": sections,
} }
@ -45,14 +44,14 @@ def schedule_conference(request):
def schedule_detail(request, slug=None): def schedule_detail(request, slug=None):
schedule = fetch_schedule(slug) schedule = fetch_schedule(slug)
if not schedule.published and not request.user.is_staff: if not schedule.published and not request.user.is_staff:
raise Http404() raise Http404()
days_qs = Day.objects.filter(schedule=schedule) days_qs = Day.objects.filter(schedule=schedule)
days = [TimeTable(day) for day in days_qs] days = [TimeTable(day) for day in days_qs]
ctx = { ctx = {
"schedule": schedule, "schedule": schedule,
"days": days, "days": days,
@ -62,10 +61,10 @@ def schedule_detail(request, slug=None):
def schedule_list(request, slug=None): def schedule_list(request, slug=None):
schedule = fetch_schedule(slug) schedule = fetch_schedule(slug)
presentations = Presentation.objects.filter(section=schedule.section) presentations = Presentation.objects.filter(section=schedule.section)
presentations = presentations.exclude(cancelled=True) presentations = presentations.exclude(cancelled=True)
ctx = { ctx = {
"schedule": schedule, "schedule": schedule,
"presentations": presentations, "presentations": presentations,
@ -75,32 +74,32 @@ def schedule_list(request, slug=None):
def schedule_list_csv(request, slug=None): def schedule_list_csv(request, slug=None):
schedule = fetch_schedule(slug) schedule = fetch_schedule(slug)
presentations = Presentation.objects.filter(section=schedule.section) presentations = Presentation.objects.filter(section=schedule.section)
presentations = presentations.exclude(cancelled=True).order_by("id") presentations = presentations.exclude(cancelled=True).order_by("id")
response = HttpResponse(mimetype="text/csv") response = HttpResponse(mimetype="text/csv")
if slug: if slug:
file_slug = slug file_slug = slug
else: else:
file_slug = "presentations" file_slug = "presentations"
response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug
response.write(loader.get_template("schedule/schedule_list.csv").render(Context({ response.write(loader.get_template("schedule/schedule_list.csv").render(Context({
"presentations": presentations, "presentations": presentations,
}))) })))
return response return response
@login_required @login_required
def schedule_edit(request, slug=None): def schedule_edit(request, slug=None):
if not request.user.is_staff: if not request.user.is_staff:
raise Http404() raise Http404()
schedule = fetch_schedule(slug) schedule = fetch_schedule(slug)
days_qs = Day.objects.filter(schedule=schedule) days_qs = Day.objects.filter(schedule=schedule)
days = [TimeTable(day) for day in days_qs] days = [TimeTable(day) for day in days_qs]
ctx = { ctx = {
@ -112,12 +111,12 @@ def schedule_edit(request, slug=None):
@login_required @login_required
def schedule_slot_edit(request, slug, slot_pk): def schedule_slot_edit(request, slug, slot_pk):
if not request.user.is_staff: if not request.user.is_staff:
raise Http404() raise Http404()
slot = get_object_or_404(Slot, day__schedule__section__slug=slug, pk=slot_pk) slot = get_object_or_404(Slot, day__schedule__section__slug=slug, pk=slot_pk)
if request.method == "POST": if request.method == "POST":
form = SlotEditForm(request.POST, slot=slot) form = SlotEditForm(request.POST, slot=slot)
if form.is_valid(): if form.is_valid():
@ -145,13 +144,13 @@ def schedule_slot_edit(request, slug, slot_pk):
def schedule_presentation_detail(request, pk): def schedule_presentation_detail(request, pk):
presentation = get_object_or_404(Presentation, pk=pk) presentation = get_object_or_404(Presentation, pk=pk)
if presentation.slot: if presentation.slot:
schedule = presentation.slot.day.schedule schedule = presentation.slot.day.schedule
else: else:
schedule = None schedule = None
ctx = { ctx = {
"presentation": presentation, "presentation": presentation,
"schedule": schedule, "schedule": schedule,

View file

@ -4,6 +4,5 @@ from symposion.speakers.models import Speaker
admin.site.register(Speaker, admin.site.register(Speaker,
list_display = ["name", "email", "created"], list_display=["name", "email", "created"],
search_fields = ["name"], search_fields=["name"])
)

View file

@ -10,7 +10,7 @@ def speakers():
guido = User.objects.create_user("guido", "guido@python.org", "pythonisawesome") guido = User.objects.create_user("guido", "guido@python.org", "pythonisawesome")
matz = User.objects.create_user("matz", "matz@ruby.org", "pythonsucks") matz = User.objects.create_user("matz", "matz@ruby.org", "pythonsucks")
larry = User.objects.create_user("larryw", "larry@perl.org", "linenoisehere") larry = User.objects.create_user("larryw", "larry@perl.org", "linenoisehere")
Speaker.objects.create( Speaker.objects.create(
user=guido, user=guido,
name="Guido van Rossum", name="Guido van Rossum",
@ -19,8 +19,8 @@ def speakers():
Speaker.objects.create( Speaker.objects.create(
user=matz, user=matz,
name="Yukihiro Matsumoto", name="Yukihiro Matsumoto",
biography="I wrote Ruby, and named it after the rare gem Ruby, a pun " biography=("I wrote Ruby, and named it after the rare gem Ruby, a pun "
"on Perl/pearl.", "on Perl/pearl."),
) )
Speaker.objects.create( Speaker.objects.create(
user=larry, user=larry,

View file

@ -6,7 +6,7 @@ from symposion.speakers.models import Speaker
class SpeakerForm(forms.ModelForm): class SpeakerForm(forms.ModelForm):
class Meta: class Meta:
model = Speaker model = Speaker
fields = [ fields = [

View file

@ -1,17 +1,17 @@
import csv import csv
import os import os
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand
from symposion.speakers.models import Speaker from symposion.speakers.models import Speaker
class Command(BaseCommand): class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
csv_file = csv.writer(open(os.path.join(os.getcwd(), "speakers.csv"), "wb")) csv_file = csv.writer(open(os.path.join(os.getcwd(), "speakers.csv"), "wb"))
csv_file.writerow(["Name", "Bio"]) csv_file.writerow(["Name", "Bio"])
for speaker in Speaker.objects.all(): for speaker in Speaker.objects.all():
csv_file.writerow([ csv_file.writerow([
speaker.name.encode("utf-8"), speaker.name.encode("utf-8"),

View file

@ -9,44 +9,47 @@ from markitup.fields import MarkupField
class Speaker(models.Model): class Speaker(models.Model):
SESSION_COUNT_CHOICES = [ SESSION_COUNT_CHOICES = [
(1, "One"), (1, "One"),
(2, "Two") (2, "Two")
] ]
user = models.OneToOneField(User, null=True, related_name="speaker_profile") 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.") name = models.CharField(max_length=100, help_text=("As you would like it to appear in the "
biography = MarkupField(blank=True, help_text="A little bit about you. Edit using <a href='http://warpedvisions.org/projects/markdown-cheat-sheet/' target='_blank'>Markdown</a>.") "conference program."))
biography = MarkupField(blank=True, help_text=("A little bit about you. Edit using "
"<a href='http://warpedvisions.org/projects/"
"markdown-cheat-sheet/target='_blank'>"
"Markdown</a>."))
photo = models.ImageField(upload_to="speaker_photos", blank=True) photo = models.ImageField(upload_to="speaker_photos", blank=True)
annotation = models.TextField() # staff only annotation = models.TextField() # staff only
invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True) invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True)
invite_token = models.CharField(max_length=40, db_index=True) invite_token = models.CharField(max_length=40, db_index=True)
created = models.DateTimeField( created = models.DateTimeField(
default = datetime.datetime.now, default=datetime.datetime.now,
editable = False editable=False
) )
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
def __unicode__(self): def __unicode__(self):
if self.user: if self.user:
return self.name return self.name
else: else:
return "?" return "?"
def get_absolute_url(self): def get_absolute_url(self):
return reverse("speaker_edit") return reverse("speaker_edit")
@property @property
def email(self): def email(self):
if self.user is not None: if self.user is not None:
return self.user.email return self.user.email
else: else:
return self.invite_email return self.invite_email
@property @property
def all_presentations(self): def all_presentations(self):
presentations = [] presentations = []

View file

@ -1,3 +1,4 @@
# flake8: noqa
from django.conf.urls.defaults import * from django.conf.urls.defaults import *

View file

@ -17,7 +17,7 @@ def speaker_create(request):
return redirect(request.user.speaker_profile) return redirect(request.user.speaker_profile)
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
if request.method == "POST": if request.method == "POST":
try: try:
speaker = Speaker.objects.get(invite_email=request.user.email) speaker = Speaker.objects.get(invite_email=request.user.email)
@ -26,7 +26,7 @@ def speaker_create(request):
speaker = None speaker = None
found = False found = False
form = SpeakerForm(request.POST, request.FILES, instance=speaker) form = SpeakerForm(request.POST, request.FILES, instance=speaker)
if form.is_valid(): if form.is_valid():
speaker = form.save(commit=False) speaker = form.save(commit=False)
speaker.user = request.user speaker.user = request.user
@ -37,7 +37,7 @@ def speaker_create(request):
return redirect("dashboard") return redirect("dashboard")
else: else:
form = SpeakerForm(initial={"name": request.user.get_full_name()}) form = SpeakerForm(initial={"name": request.user.get_full_name()})
return render(request, "speakers/speaker_create.html", { return render(request, "speakers/speaker_create.html", {
"form": form, "form": form,
}) })
@ -48,15 +48,15 @@ def speaker_create_staff(request, pk):
user = get_object_or_404(User, pk=pk) user = get_object_or_404(User, pk=pk)
if not request.user.is_staff: if not request.user.is_staff:
raise Http404 raise Http404
try: try:
return redirect(user.speaker_profile) return redirect(user.speaker_profile)
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
if request.method == "POST": if request.method == "POST":
form = SpeakerForm(request.POST, request.FILES) form = SpeakerForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
speaker = form.save(commit=False) speaker = form.save(commit=False)
speaker.user = user speaker.user = user
@ -65,7 +65,7 @@ def speaker_create_staff(request, pk):
return redirect("user_list") return redirect("user_list")
else: else:
form = SpeakerForm(initial={"name": user.get_full_name()}) form = SpeakerForm(initial={"name": user.get_full_name()})
return render(request, "speakers/speaker_create.html", { return render(request, "speakers/speaker_create.html", {
"form": form, "form": form,
}) })
@ -88,8 +88,8 @@ def speaker_create_token(request, token):
).update( ).update(
speaker=existing_speaker speaker=existing_speaker
) )
messages.info(request, "You have been associated with all pending " messages.info(request, ("You have been associated with all pending "
"talk proposals") "talk proposals"))
return redirect("dashboard") return redirect("dashboard")
else: else:
if not request.user.is_authenticated(): if not request.user.is_authenticated():
@ -109,7 +109,7 @@ def speaker_edit(request, pk=None):
speaker = get_object_or_404(Speaker, pk=pk) speaker = get_object_or_404(Speaker, pk=pk)
else: else:
raise Http404() raise Http404()
if request.method == "POST": if request.method == "POST":
form = SpeakerForm(request.POST, request.FILES, instance=speaker) form = SpeakerForm(request.POST, request.FILES, instance=speaker)
if form.is_valid(): if form.is_valid():
@ -118,7 +118,7 @@ def speaker_edit(request, pk=None):
return redirect("dashboard") return redirect("dashboard")
else: else:
form = SpeakerForm(instance=speaker) form = SpeakerForm(instance=speaker)
return render(request, "speakers/speaker_edit.html", { return render(request, "speakers/speaker_edit.html", {
"form": form, "form": form,
}) })
@ -129,7 +129,7 @@ def speaker_profile(request, pk):
presentations = speaker.all_presentations presentations = speaker.all_presentations
if not presentations and not request.user.is_staff: if not presentations and not request.user.is_staff:
raise Http404() raise Http404()
return render(request, "speakers/speaker_profile.html", { return render(request, "speakers/speaker_profile.html", {
"speaker": speaker, "speaker": speaker,
"presentations": presentations, "presentations": presentations,

View file

@ -1,6 +1,7 @@
from django.contrib import admin 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): class BenefitLevelInline(admin.TabularInline):
@ -24,7 +25,7 @@ class SponsorBenefitInline(admin.StackedInline):
class SponsorAdmin(admin.ModelAdmin): class SponsorAdmin(admin.ModelAdmin):
save_on_top = True save_on_top = True
fieldsets = [ fieldsets = [
(None, { (None, {
@ -43,7 +44,7 @@ class SponsorAdmin(admin.ModelAdmin):
] ]
inlines = [SponsorBenefitInline] inlines = [SponsorBenefitInline]
list_display = ["name", "external_url", "level", "active"] list_display = ["name", "external_url", "level", "active"]
def get_form(self, *args, **kwargs): def get_form(self, *args, **kwargs):
# @@@ kinda ugly but using choices= on NullBooleanField is broken # @@@ kinda ugly but using choices= on NullBooleanField is broken
form = super(SponsorAdmin, self).get_form(*args, **kwargs) form = super(SponsorAdmin, self).get_form(*args, **kwargs)
@ -56,13 +57,13 @@ class SponsorAdmin(admin.ModelAdmin):
class BenefitAdmin(admin.ModelAdmin): class BenefitAdmin(admin.ModelAdmin):
list_display = ["name", "type", "description"] list_display = ["name", "type", "description"]
inlines = [BenefitLevelInline] inlines = [BenefitLevelInline]
class SponsorLevelAdmin(admin.ModelAdmin): class SponsorLevelAdmin(admin.ModelAdmin):
inlines = [BenefitLevelInline] inlines = [BenefitLevelInline]

View file

@ -16,7 +16,7 @@ class SponsorApplicationForm(forms.ModelForm):
} }
}) })
super(SponsorApplicationForm, self).__init__(*args, **kwargs) super(SponsorApplicationForm, self).__init__(*args, **kwargs)
class Meta: class Meta:
model = Sponsor model = Sponsor
fields = [ fields = [
@ -26,7 +26,7 @@ class SponsorApplicationForm(forms.ModelForm):
"contact_email", "contact_email",
"level" "level"
] ]
def save(self, commit=True): def save(self, commit=True):
obj = super(SponsorApplicationForm, self).save(commit=False) obj = super(SponsorApplicationForm, self).save(commit=False)
obj.applicant = self.user obj.applicant = self.user
@ -47,26 +47,26 @@ class SponsorDetailsForm(forms.ModelForm):
class SponsorBenefitsInlineFormSet(BaseInlineFormSet): class SponsorBenefitsInlineFormSet(BaseInlineFormSet):
def _construct_form(self, i, **kwargs): def _construct_form(self, i, **kwargs):
form = super(SponsorBenefitsInlineFormSet, self)._construct_form(i, **kwargs) form = super(SponsorBenefitsInlineFormSet, self)._construct_form(i, **kwargs)
# only include the relevant data fields for this benefit type # only include the relevant data fields for this benefit type
fields = form.instance.data_fields() fields = form.instance.data_fields()
form.fields = dict((k, v) for (k, v) in form.fields.items() if k in fields + ["id"]) form.fields = dict((k, v) for (k, v) in form.fields.items() if k in fields + ["id"])
for field in fields: for field in fields:
# don't need a label, the form template will label it with the benefit name # don't need a label, the form template will label it with the benefit name
form.fields[field].label = "" form.fields[field].label = ""
# provide word limit as help_text # provide word limit as help_text
if form.instance.benefit.type == "text" and form.instance.max_words: if form.instance.benefit.type == "text" and form.instance.max_words:
form.fields[field].help_text = u"maximum %s words" % 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 # use admin file widget that shows currently uploaded file
if field == "upload": if field == "upload":
form.fields[field].widget = AdminFileWidget() form.fields[field].widget = AdminFileWidget()
return form return form

View file

@ -1,15 +1,13 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.contrib.auth.models import Group from symposion.sponsorship.models import Sponsor, SponsorBenefit, SponsorLevel
from symposion.sponsorship.models import Sponsor, SponsorBenefit
class Command(BaseCommand): class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
for sponsor in Sponsor.objects.all(): for sponsor in Sponsor.objects.all():
level = None level = None
try: try:
level = sponsor.level level = sponsor.level
except SponsorLevel.DoesNotExist: except SponsorLevel.DoesNotExist:
@ -19,17 +17,17 @@ class Command(BaseCommand):
# Create all needed benefits if they don't exist already # Create all needed benefits if they don't exist already
sponsor_benefit, created = SponsorBenefit.objects.get_or_create( sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
sponsor=sponsor, benefit=benefit_level.benefit) sponsor=sponsor, benefit=benefit_level.benefit)
if created: if created:
print "created", sponsor_benefit, "for", sponsor print "created", sponsor_benefit, "for", sponsor
# and set to default limits for this level. # and set to default limits for this level.
sponsor_benefit.max_words = benefit_level.max_words sponsor_benefit.max_words = benefit_level.max_words
sponsor_benefit.other_limits = benefit_level.other_limits sponsor_benefit.other_limits = benefit_level.other_limits
# and set to active # and set to active
sponsor_benefit.active = True sponsor_benefit.active = True
# @@@ We don't call sponsor_benefit.clean here. This means # @@@ We don't call sponsor_benefit.clean here. This means
# that if the sponsorship level for a sponsor is adjusted # that if the sponsorship level for a sponsor is adjusted
# downwards, an existing too-long text entry can remain, # downwards, an existing too-long text entry can remain,

View file

@ -14,29 +14,30 @@ from symposion.sponsorship.managers import SponsorManager
class SponsorLevel(models.Model): class SponsorLevel(models.Model):
conference = models.ForeignKey(Conference, verbose_name=_("conference")) conference = models.ForeignKey(Conference, verbose_name=_("conference"))
name = models.CharField(_("name"), max_length=100) name = models.CharField(_("name"), max_length=100)
order = models.IntegerField(_("order"), default=0) order = models.IntegerField(_("order"), default=0)
cost = models.PositiveIntegerField(_("cost")) cost = models.PositiveIntegerField(_("cost"))
description = models.TextField(_("description"), blank=True, help_text=_("This is private.")) description = models.TextField(_("description"), blank=True, help_text=_("This is private."))
class Meta: class Meta:
ordering = ["conference", "order"] ordering = ["conference", "order"]
verbose_name = _("sponsor level") verbose_name = _("sponsor level")
verbose_name_plural = _("sponsor levels") verbose_name_plural = _("sponsor levels")
def __unicode__(self): def __unicode__(self):
return self.name return self.name
def sponsors(self): def sponsors(self):
return self.sponsor_set.filter(active=True).order_by("added") return self.sponsor_set.filter(active=True).order_by("added")
class Sponsor(models.Model): class Sponsor(models.Model):
applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"), null=True) applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"),
null=True)
name = models.CharField(_("Sponsor Name"), max_length=100) name = models.CharField(_("Sponsor Name"), max_length=100)
external_url = models.URLField(_("external URL")) external_url = models.URLField(_("external URL"))
annotation = models.TextField(_("annotation"), blank=True) annotation = models.TextField(_("annotation"), blank=True)
@ -45,34 +46,36 @@ class Sponsor(models.Model):
level = models.ForeignKey(SponsorLevel, verbose_name=_("level")) level = models.ForeignKey(SponsorLevel, verbose_name=_("level"))
added = models.DateTimeField(_("added"), default=datetime.datetime.now) added = models.DateTimeField(_("added"), default=datetime.datetime.now)
active = models.BooleanField(_("active"), default=False) active = models.BooleanField(_("active"), default=False)
# Denormalization (this assumes only one logo) # 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() objects = SponsorManager()
def __unicode__(self): def __unicode__(self):
return self.name return self.name
class Meta: class Meta:
verbose_name = _("sponsor") verbose_name = _("sponsor")
verbose_name_plural = _("sponsors") verbose_name_plural = _("sponsors")
def get_absolute_url(self): def get_absolute_url(self):
if self.active: if self.active:
return reverse("sponsor_detail", kwargs={"pk": self.pk}) return reverse("sponsor_detail", kwargs={"pk": self.pk})
return reverse("sponsor_list") return reverse("sponsor_list")
@property @property
def website_logo(self): def website_logo(self):
if self.sponsor_logo is None: 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.count():
if benefits[0].upload: if benefits[0].upload:
self.sponsor_logo = benefits[0] self.sponsor_logo = benefits[0]
self.save() self.save()
return self.sponsor_logo.upload return self.sponsor_logo.upload
@property @property
def listing_text(self): def listing_text(self):
if not hasattr(self, "_listing_text"): if not hasattr(self, "_listing_text"):
@ -82,46 +85,47 @@ class Sponsor(models.Model):
if benefits.count(): if benefits.count():
self._listing_text = benefits[0].text self._listing_text = benefits[0].text
return self._listing_text return self._listing_text
def reset_benefits(self): def reset_benefits(self):
""" """
Reset all benefits for this sponsor to the defaults for their Reset all benefits for this sponsor to the defaults for their
sponsorship level. sponsorship level.
""" """
level = None level = None
try: try:
level = self.level level = self.level
except SponsorLevel.DoesNotExist: except SponsorLevel.DoesNotExist:
pass pass
allowed_benefits = [] allowed_benefits = []
if level: if level:
for benefit_level in level.benefit_levels.all(): for benefit_level in level.benefit_levels.all():
# Create all needed benefits if they don't exist already # Create all needed benefits if they don't exist already
sponsor_benefit, created = SponsorBenefit.objects.get_or_create( sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
sponsor=self, benefit=benefit_level.benefit) sponsor=self, benefit=benefit_level.benefit)
# and set to default limits for this level. # and set to default limits for this level.
sponsor_benefit.max_words = benefit_level.max_words sponsor_benefit.max_words = benefit_level.max_words
sponsor_benefit.other_limits = benefit_level.other_limits sponsor_benefit.other_limits = benefit_level.other_limits
# and set to active # and set to active
sponsor_benefit.active = True sponsor_benefit.active = True
# @@@ We don't call sponsor_benefit.clean here. This means # @@@ We don't call sponsor_benefit.clean here. This means
# that if the sponsorship level for a sponsor is adjusted # that if the sponsorship level for a sponsor is adjusted
# downwards, an existing too-long text entry can remain, # downwards, an existing too-long text entry can remain,
# and won't raise a validation error until it's next # and won't raise a validation error until it's next
# edited. # edited.
sponsor_benefit.save() sponsor_benefit.save()
allowed_benefits.append(sponsor_benefit.pk) allowed_benefits.append(sponsor_benefit.pk)
# Any remaining sponsor benefits that don't normally belong to # Any remaining sponsor benefits that don't normally belong to
# this level are set to inactive # 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): def send_coordinator_emails(self):
pass # @@@ should this just be done centrally? pass # @@@ should this just be done centrally?
@ -147,59 +151,60 @@ BENEFIT_TYPE_CHOICES = [
class Benefit(models.Model): class Benefit(models.Model):
name = models.CharField(_("name"), max_length=100) name = models.CharField(_("name"), max_length=100)
description = models.TextField(_("description"), blank=True) 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): def __unicode__(self):
return self.name return self.name
class BenefitLevel(models.Model): class BenefitLevel(models.Model):
benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit")) benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit"))
level = models.ForeignKey(SponsorLevel, related_name="benefit_levels", verbose_name=_("level")) level = models.ForeignKey(SponsorLevel, related_name="benefit_levels", verbose_name=_("level"))
# default limits for this benefit at given level # default limits for this benefit at given level
max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True)
other_limits = models.CharField(_("other limits"), max_length=200, blank=True) other_limits = models.CharField(_("other limits"), max_length=200, blank=True)
class Meta: class Meta:
ordering = ["level"] ordering = ["level"]
def __unicode__(self): def __unicode__(self):
return u"%s - %s" % (self.level, self.benefit) return u"%s - %s" % (self.level, self.benefit)
class SponsorBenefit(models.Model): class SponsorBenefit(models.Model):
sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("sponsor")) sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("sponsor"))
benefit = models.ForeignKey(Benefit, related_name="sponsor_benefits", verbose_name=_("benefit")) benefit = models.ForeignKey(Benefit, related_name="sponsor_benefits", verbose_name=_("benefit"))
active = models.BooleanField(default=True) active = models.BooleanField(default=True)
# Limits: will initially be set to defaults from corresponding BenefitLevel # Limits: will initially be set to defaults from corresponding BenefitLevel
max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True)
other_limits = models.CharField(_("other limits"), max_length=200, blank=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 # Data: zero or one of these fields will be used, depending on the
# type of the Benefit (text, file, or simple) # type of the Benefit (text, file, or simple)
text = models.TextField(_("text"), blank=True) text = models.TextField(_("text"), blank=True)
upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files") upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files")
class Meta: class Meta:
ordering = ["-active"] ordering = ["-active"]
def __unicode__(self): def __unicode__(self):
return u"%s - %s" % (self.sponsor, self.benefit) return u"%s - %s" % (self.sponsor, self.benefit)
def clean(self): def clean(self):
num_words = len(self.text.split()) num_words = len(self.text.split())
if self.max_words and num_words > self.max_words: if self.max_words and num_words > self.max_words:
raise ValidationError( raise ValidationError(
"Sponsorship level only allows for %s words, you provided %d." % ( "Sponsorship level only allows for %s words, you provided %d." % (
self.max_words, num_words)) self.max_words, num_words))
def data_fields(self): def data_fields(self):
""" """
Return list of data field names which should be editable for Return list of data field names which should be editable for

View file

@ -8,7 +8,7 @@ register = template.Library()
class SponsorsNode(template.Node): class SponsorsNode(template.Node):
@classmethod @classmethod
def handle_token(cls, parser, token): def handle_token(cls, parser, token):
bits = token.split_contents() bits = token.split_contents()
@ -18,27 +18,30 @@ class SponsorsNode(template.Node):
return cls(bits[3], bits[1]) return cls(bits[3], bits[1])
else: else:
raise template.TemplateSyntaxError("%r takes 'as var' or 'level as var'" % bits[0]) raise template.TemplateSyntaxError("%r takes 'as var' or 'level as var'" % bits[0])
def __init__(self, context_var, level=None): def __init__(self, context_var, level=None):
if level: if level:
self.level = template.Variable(level) self.level = template.Variable(level)
else: else:
self.level = None self.level = None
self.context_var = context_var self.context_var = context_var
def render(self, context): def render(self, context):
conference = current_conference() conference = current_conference()
if self.level: if self.level:
level = self.level.resolve(context) 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: 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 context[self.context_var] = queryset
return u"" return u""
class SponsorLevelNode(template.Node): class SponsorLevelNode(template.Node):
@classmethod @classmethod
def handle_token(cls, parser, token): def handle_token(cls, parser, token):
bits = token.split_contents() bits = token.split_contents()
@ -46,10 +49,10 @@ class SponsorLevelNode(template.Node):
return cls(bits[2]) return cls(bits[2])
else: else:
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
def __init__(self, context_var): def __init__(self, context_var):
self.context_var = context_var self.context_var = context_var
def render(self, context): def render(self, context):
conference = current_conference() conference = current_conference()
context[self.context_var] = SponsorLevel.objects.filter(conference=conference) context[self.context_var] = SponsorLevel.objects.filter(conference=conference)
@ -72,4 +75,3 @@ def sponsor_levels(parser, token):
{% sponsor_levels as levels %} {% sponsor_levels as levels %}
""" """
return SponsorLevelNode.handle_token(parser, token) return SponsorLevelNode.handle_token(parser, token)

View file

@ -1,3 +1,4 @@
# flake8: noqa
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url
from django.views.generic.simple import direct_to_template from django.views.generic.simple import direct_to_template

View file

@ -5,7 +5,8 @@ from django.template import RequestContext
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required 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 from symposion.sponsorship.models import Sponsor, SponsorBenefit
@ -18,7 +19,7 @@ def sponsor_apply(request):
return redirect("sponsor_detail", pk=sponsor.pk) return redirect("sponsor_detail", pk=sponsor.pk)
else: else:
form = SponsorApplicationForm(user=request.user) form = SponsorApplicationForm(user=request.user)
return render_to_response("sponsorship/apply.html", { return render_to_response("sponsorship/apply.html", {
"form": form, "form": form,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
@ -28,7 +29,7 @@ def sponsor_apply(request):
def sponsor_add(request): def sponsor_add(request):
if not request.user.is_staff: if not request.user.is_staff:
raise Http404() raise Http404()
if request.method == "POST": if request.method == "POST":
form = SponsorApplicationForm(request.POST, user=request.user) form = SponsorApplicationForm(request.POST, user=request.user)
if form.is_valid(): if form.is_valid():
@ -38,7 +39,7 @@ def sponsor_add(request):
return redirect("sponsor_detail", pk=sponsor.pk) return redirect("sponsor_detail", pk=sponsor.pk)
else: else:
form = SponsorApplicationForm(user=request.user) form = SponsorApplicationForm(user=request.user)
return render_to_response("sponsorship/add.html", { return render_to_response("sponsorship/add.html", {
"form": form, "form": form,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
@ -47,31 +48,31 @@ def sponsor_add(request):
@login_required @login_required
def sponsor_detail(request, pk): def sponsor_detail(request, pk):
sponsor = get_object_or_404(Sponsor, pk=pk) sponsor = get_object_or_404(Sponsor, pk=pk)
if sponsor.applicant != request.user: if sponsor.applicant != request.user:
return redirect("sponsor_list") return redirect("sponsor_list")
formset_kwargs = { formset_kwargs = {
"instance": sponsor, "instance": sponsor,
"queryset": SponsorBenefit.objects.filter(active=True) "queryset": SponsorBenefit.objects.filter(active=True)
} }
if request.method == "POST": if request.method == "POST":
form = SponsorDetailsForm(request.POST, instance=sponsor) form = SponsorDetailsForm(request.POST, instance=sponsor)
formset = SponsorBenefitsFormSet(request.POST, request.FILES, **formset_kwargs) formset = SponsorBenefitsFormSet(request.POST, request.FILES, **formset_kwargs)
if form.is_valid() and formset.is_valid(): if form.is_valid() and formset.is_valid():
form.save() form.save()
formset.save() formset.save()
messages.success(request, "Sponsorship details have been updated") messages.success(request, "Sponsorship details have been updated")
return redirect("dashboard") return redirect("dashboard")
else: else:
form = SponsorDetailsForm(instance=sponsor) form = SponsorDetailsForm(instance=sponsor)
formset = SponsorBenefitsFormSet(**formset_kwargs) formset = SponsorBenefitsFormSet(**formset_kwargs)
return render_to_response("sponsorship/detail.html", { return render_to_response("sponsorship/detail.html", {
"sponsor": sponsor, "sponsor": sponsor,
"form": form, "form": form,

View file

@ -5,8 +5,7 @@ import reversion
from symposion.teams.models import Team, Membership from symposion.teams.models import Team, Membership
admin.site.register(Team, admin.site.register(Team,
prepopulated_fields={"slug": ("name",)}, prepopulated_fields={"slug": ("name",)})
)
class MembershipAdmin(reversion.VersionAdmin): class MembershipAdmin(reversion.VersionAdmin):

View file

@ -4,10 +4,10 @@ from .models import Team
class TeamPermissionsBackend(object): class TeamPermissionsBackend(object):
def authenticate(self, username=None, password=None): def authenticate(self, username=None, password=None):
return None return None
def get_team_permissions(self, user_obj, obj=None): def get_team_permissions(self, user_obj, obj=None):
""" """
Returns a set of permission strings that this user has through his/her Returns a set of permission strings that this user has through his/her

View file

@ -9,40 +9,43 @@ from symposion.teams.models import Membership
class TeamInvitationForm(forms.Form): 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): def __init__(self, *args, **kwargs):
self.team = kwargs.pop("team") self.team = kwargs.pop("team")
super(TeamInvitationForm, self).__init__(*args, **kwargs) super(TeamInvitationForm, self).__init__(*args, **kwargs)
def clean(self): def clean(self):
cleaned_data = super(TeamInvitationForm, self).clean() cleaned_data = super(TeamInvitationForm, self).clean()
email = cleaned_data.get("email") email = cleaned_data.get("email")
if email is None: if email is None:
raise forms.ValidationError("valid email address required") raise forms.ValidationError("valid email address required")
try: try:
user = User.objects.get(email=email) user = User.objects.get(email=email)
except User.DoesNotExist: except User.DoesNotExist:
# eventually we can invite them but for now assume they are # eventually we can invite them but for now assume they are
# already on the site # already on the site
raise forms.ValidationError(mark_safe("no account with email address <b>%s</b> found on this conference site" % escape(email))) raise forms.ValidationError(
mark_safe("no account with email address <b>%s</b> found on this conference "
"site" % escape(email)))
state = self.team.get_state_for_user(user) state = self.team.get_state_for_user(user)
if state in ["member", "manager"]: if state in ["member", "manager"]:
raise forms.ValidationError("user already in team") raise forms.ValidationError("user already in team")
if state in ["invited"]: if state in ["invited"]:
raise forms.ValidationError("user already invited to team") raise forms.ValidationError("user already invited to team")
self.user = user self.user = user
self.state = state self.state = state
return cleaned_data return cleaned_data
def invite(self): def invite(self):
if self.state is None: if self.state is None:
Membership.objects.create(team=self.team, user=self.user, state="invited") Membership.objects.create(team=self.team, user=self.user, state="invited")

View file

@ -20,19 +20,20 @@ class Team(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
description = models.TextField(blank=True) description = models.TextField(blank=True)
access = models.CharField(max_length=20, choices=TEAM_ACCESS_CHOICES) access = models.CharField(max_length=20, choices=TEAM_ACCESS_CHOICES)
# member permissions # member permissions
permissions = models.ManyToManyField(Permission, blank=True, related_name="member_teams") permissions = models.ManyToManyField(Permission, blank=True, related_name="member_teams")
# manager permissions # 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) created = models.DateTimeField(default=datetime.datetime.now, editable=False)
@models.permalink @models.permalink
def get_absolute_url(self): def get_absolute_url(self):
return ("team_detail", [self.slug]) return ("team_detail", [self.slug])
def __unicode__(self): def __unicode__(self):
return self.name return self.name
@ -41,16 +42,16 @@ class Team(models.Model):
return self.memberships.get(user=user).state return self.memberships.get(user=user).state
except Membership.DoesNotExist: except Membership.DoesNotExist:
return None return None
def applicants(self): def applicants(self):
return self.memberships.filter(state="applied") return self.memberships.filter(state="applied")
def invitees(self): def invitees(self):
return self.memberships.filter(state="invited") return self.memberships.filter(state="invited")
def members(self): def members(self):
return self.memberships.filter(state="member") return self.memberships.filter(state="member")
def managers(self): def managers(self):
return self.memberships.filter(state="manager") return self.memberships.filter(state="manager")

View file

@ -6,7 +6,7 @@ register = template.Library()
class AvailableTeamsNode(template.Node): class AvailableTeamsNode(template.Node):
@classmethod @classmethod
def handle_token(cls, parser, token): def handle_token(cls, parser, token):
bits = token.split_contents() bits = token.split_contents()
@ -14,10 +14,10 @@ class AvailableTeamsNode(template.Node):
return cls(bits[2]) return cls(bits[2])
else: else:
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
def __init__(self, context_var): def __init__(self, context_var):
self.context_var = context_var self.context_var = context_var
def render(self, context): def render(self, context):
request = context["request"] request = context["request"]
teams = [] teams = []

View file

@ -1,3 +1,4 @@
# flake8: noqa
from django.conf.urls.defaults import * from django.conf.urls.defaults import *
@ -7,7 +8,7 @@ urlpatterns = patterns("symposion.teams.views",
url(r"^(?P<slug>[\w\-]+)/join/$", "team_join", name="team_join"), url(r"^(?P<slug>[\w\-]+)/join/$", "team_join", name="team_join"),
url(r"^(?P<slug>[\w\-]+)/leave/$", "team_leave", name="team_leave"), url(r"^(?P<slug>[\w\-]+)/leave/$", "team_leave", name="team_leave"),
url(r"^(?P<slug>[\w\-]+)/apply/$", "team_apply", name="team_apply"), url(r"^(?P<slug>[\w\-]+)/apply/$", "team_apply", name="team_apply"),
# membership specific # membership specific
url(r"^promote/(?P<pk>\d+)/$", "team_promote", name="team_promote"), url(r"^promote/(?P<pk>\d+)/$", "team_promote", name="team_promote"),
url(r"^demote/(?P<pk>\d+)/$", "team_demote", name="team_demote"), url(r"^demote/(?P<pk>\d+)/$", "team_demote", name="team_demote"),

View file

@ -10,7 +10,7 @@ from symposion.teams.forms import TeamInvitationForm
from symposion.teams.models import Team, Membership from symposion.teams.models import Team, Membership
## perm checks # perm checks
# #
# @@@ these can be moved # @@@ these can be moved
@ -50,7 +50,7 @@ def can_invite(team, user):
return False return False
## views # views
@login_required @login_required
@ -59,7 +59,7 @@ def team_detail(request, slug):
state = team.get_state_for_user(request.user) state = team.get_state_for_user(request.user)
if team.access == "invitation" and state is None and not request.user.is_staff: if team.access == "invitation" and state is None and not request.user.is_staff:
raise Http404() raise Http404()
if can_invite(team, request.user): if can_invite(team, request.user):
if request.method == "POST": if request.method == "POST":
form = TeamInvitationForm(request.POST, team=team) form = TeamInvitationForm(request.POST, team=team)
@ -72,7 +72,7 @@ def team_detail(request, slug):
form = TeamInvitationForm(team=team) form = TeamInvitationForm(team=team)
else: else:
form = None form = None
return render(request, "teams/team_detail.html", { return render(request, "teams/team_detail.html", {
"team": team, "team": team,
"state": state, "state": state,
@ -89,7 +89,7 @@ def team_join(request, slug):
state = team.get_state_for_user(request.user) state = team.get_state_for_user(request.user)
if team.access == "invitation" and state is None and not request.user.is_staff: if team.access == "invitation" and state is None and not request.user.is_staff:
raise Http404() raise Http404()
if can_join(team, request.user) and request.method == "POST": if can_join(team, request.user) and request.method == "POST":
membership, created = Membership.objects.get_or_create(team=team, user=request.user) membership, created = Membership.objects.get_or_create(team=team, user=request.user)
membership.state = "member" membership.state = "member"
@ -106,7 +106,7 @@ def team_leave(request, slug):
state = team.get_state_for_user(request.user) state = team.get_state_for_user(request.user)
if team.access == "invitation" and state is None and not request.user.is_staff: if team.access == "invitation" and state is None and not request.user.is_staff:
raise Http404() raise Http404()
if can_leave(team, request.user) and request.method == "POST": if can_leave(team, request.user) and request.method == "POST":
membership = Membership.objects.get(team=team, user=request.user) membership = Membership.objects.get(team=team, user=request.user)
membership.delete() membership.delete()
@ -122,7 +122,7 @@ def team_apply(request, slug):
state = team.get_state_for_user(request.user) state = team.get_state_for_user(request.user)
if team.access == "invitation" and state is None and not request.user.is_staff: if team.access == "invitation" and state is None and not request.user.is_staff:
raise Http404() raise Http404()
if can_apply(team, request.user) and request.method == "POST": if can_apply(team, request.user) and request.method == "POST":
membership, created = Membership.objects.get_or_create(team=team, user=request.user) membership, created = Membership.objects.get_or_create(team=team, user=request.user)
membership.state = "applied" membership.state = "applied"

View file

@ -7,9 +7,9 @@ from django.contrib.sites.models import Site
def send_email(to, kind, **kwargs): def send_email(to, kind, **kwargs):
current_site = Site.objects.get_current() current_site = Site.objects.get_current()
ctx = { ctx = {
"current_site": current_site, "current_site": current_site,
"STATIC_URL": settings.STATIC_URL, "STATIC_URL": settings.STATIC_URL,
@ -19,12 +19,12 @@ def send_email(to, kind, **kwargs):
current_site.name, current_site.name,
render_to_string("emails/%s/subject.txt" % kind, ctx).strip() render_to_string("emails/%s/subject.txt" % kind, ctx).strip()
) )
message_html = render_to_string("emails/%s/message.html" % kind, ctx) message_html = render_to_string("emails/%s/message.html" % kind, ctx)
message_plaintext = strip_tags(message_html) message_plaintext = strip_tags(message_html)
from_email = settings.DEFAULT_FROM_EMAIL from_email = settings.DEFAULT_FROM_EMAIL
email = EmailMultiAlternatives(subject, message_plaintext, from_email, to) email = EmailMultiAlternatives(subject, message_plaintext, from_email, to)
email.attach_alternative(message_html, "text/html") email.attach_alternative(message_html, "text/html")
email.send() email.send()

View file

@ -12,12 +12,12 @@ import symposion.forms
class SignupView(account.views.SignupView): class SignupView(account.views.SignupView):
form_class = symposion.forms.SignupForm form_class = symposion.forms.SignupForm
form_kwargs = { form_kwargs = {
"prefix": "signup", "prefix": "signup",
} }
def create_user(self, form, commit=True): def create_user(self, form, commit=True):
user_kwargs = { user_kwargs = {
"first_name": form.cleaned_data["first_name"], "first_name": form.cleaned_data["first_name"],