temporarily include pycon module containing advanced sponsorship app
This commit is contained in:
parent
7596922f4d
commit
6d9c1c2e5f
18 changed files with 939 additions and 0 deletions
0
pycon/__init__.py
Normal file
0
pycon/__init__.py
Normal file
8
pycon/admin.py
Normal file
8
pycon/admin.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from pycon.models import PyConProposalCategory, PyConTalkProposal, PyConTutorialProposal, PyConPosterProposal
|
||||||
|
|
||||||
|
admin.site.register(PyConProposalCategory)
|
||||||
|
admin.site.register(PyConTalkProposal)
|
||||||
|
admin.site.register(PyConTutorialProposal)
|
||||||
|
admin.site.register(PyConPosterProposal)
|
84
pycon/forms.py
Normal file
84
pycon/forms.py
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from markitup.widgets import MarkItUpWidget
|
||||||
|
|
||||||
|
from pycon.models import PyConProposalCategory, PyConTalkProposal, PyConTutorialProposal, PyConPosterProposal
|
||||||
|
|
||||||
|
|
||||||
|
class PyConProposalForm(forms.ModelForm):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(PyConProposalForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields["category"] = forms.ModelChoiceField(
|
||||||
|
queryset = PyConProposalCategory.objects.order_by("name")
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean_description(self):
|
||||||
|
value = self.cleaned_data["description"]
|
||||||
|
if len(value) > 400:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
u"The description must be less than 400 characters"
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class PyConTalkProposalForm(PyConProposalForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PyConTalkProposal
|
||||||
|
fields = [
|
||||||
|
"title",
|
||||||
|
"category",
|
||||||
|
"audience_level",
|
||||||
|
"extreme",
|
||||||
|
"duration",
|
||||||
|
"description",
|
||||||
|
"abstract",
|
||||||
|
"additional_notes",
|
||||||
|
"recording_release",
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
"abstract": MarkItUpWidget(),
|
||||||
|
"additional_notes": MarkItUpWidget(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PyConTutorialProposalForm(PyConProposalForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PyConTutorialProposal
|
||||||
|
fields = [
|
||||||
|
"title",
|
||||||
|
"category",
|
||||||
|
"audience_level",
|
||||||
|
"description",
|
||||||
|
"abstract",
|
||||||
|
"additional_notes",
|
||||||
|
"recording_release",
|
||||||
|
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
"abstract": MarkItUpWidget(),
|
||||||
|
"additional_notes": MarkItUpWidget(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PyConPosterProposalForm(PyConProposalForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = PyConPosterProposal
|
||||||
|
fields = [
|
||||||
|
"title",
|
||||||
|
"category",
|
||||||
|
"audience_level",
|
||||||
|
"description",
|
||||||
|
"abstract",
|
||||||
|
"additional_notes",
|
||||||
|
"recording_release",
|
||||||
|
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
"abstract": MarkItUpWidget(),
|
||||||
|
"additional_notes": MarkItUpWidget(),
|
||||||
|
}
|
||||||
|
|
68
pycon/models.py
Normal file
68
pycon/models.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from symposion.proposals.models import ProposalBase
|
||||||
|
|
||||||
|
|
||||||
|
class PyConProposalCategory(models.Model):
|
||||||
|
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
slug = models.SlugField()
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "PyCon proposal category"
|
||||||
|
verbose_name_plural = "PyCon proposal categories"
|
||||||
|
|
||||||
|
|
||||||
|
class PyConProposal(ProposalBase):
|
||||||
|
|
||||||
|
AUDIENCE_LEVEL_NOVICE = 1
|
||||||
|
AUDIENCE_LEVEL_EXPERIENCED = 2
|
||||||
|
AUDIENCE_LEVEL_INTERMEDIATE = 3
|
||||||
|
|
||||||
|
AUDIENCE_LEVELS = [
|
||||||
|
(AUDIENCE_LEVEL_NOVICE, "Novice"),
|
||||||
|
(AUDIENCE_LEVEL_INTERMEDIATE, "Intermediate"),
|
||||||
|
(AUDIENCE_LEVEL_EXPERIENCED, "Experienced"),
|
||||||
|
]
|
||||||
|
|
||||||
|
category = models.ForeignKey(PyConProposalCategory)
|
||||||
|
audience_level = models.IntegerField(choices=AUDIENCE_LEVELS)
|
||||||
|
|
||||||
|
recording_release = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
help_text="By submitting your talk proposal, you agree to give permission to the Python Software Foundation to record, edit, and release audio and/or video of your presentation. If you do not agree to this, please uncheck this box. See <a href='https://us.pycon.org/2013/speaking/recording/' target='_blank'>PyCon 2013 Recording Release</a> for details."
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class PyConTalkProposal(PyConProposal):
|
||||||
|
|
||||||
|
DURATION_CHOICES = [
|
||||||
|
(0, "No preference"),
|
||||||
|
(1, "I prefer a 30 minute slot"),
|
||||||
|
(2, "I prefer a 45 minute slot"),
|
||||||
|
]
|
||||||
|
|
||||||
|
extreme = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="'Extreme' talks are advanced talks with little or no introductory material. See <a href='http://us.pycon.org/2013/speaker/extreme/' target='_blank'>Extreme Talks</a> for details."
|
||||||
|
)
|
||||||
|
duration = models.IntegerField(choices=DURATION_CHOICES)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "PyCon talk proposal"
|
||||||
|
|
||||||
|
|
||||||
|
class PyConTutorialProposal(PyConProposal):
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "PyCon tutorial proposal"
|
||||||
|
|
||||||
|
|
||||||
|
class PyConPosterProposal(PyConProposal):
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "PyCon poster proposal"
|
5
pycon/sponsorship/__init__.py
Normal file
5
pycon/sponsorship/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
SPONSOR_COORDINATORS = "sponsor-coordinators"
|
||||||
|
|
||||||
|
AUTH_GROUPS = [
|
||||||
|
SPONSOR_COORDINATORS
|
||||||
|
]
|
69
pycon/sponsorship/admin.py
Normal file
69
pycon/sponsorship/admin.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from pycon.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, SponsorBenefit
|
||||||
|
|
||||||
|
|
||||||
|
class BenefitLevelInline(admin.TabularInline):
|
||||||
|
model = BenefitLevel
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorBenefitInline(admin.StackedInline):
|
||||||
|
model = SponsorBenefit
|
||||||
|
extra = 0
|
||||||
|
fieldsets = [
|
||||||
|
(None, {
|
||||||
|
"fields": [
|
||||||
|
("benefit", "active"),
|
||||||
|
("max_words", "other_limits"),
|
||||||
|
"text",
|
||||||
|
"upload",
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
save_on_top = True
|
||||||
|
fieldsets = [
|
||||||
|
(None, {
|
||||||
|
"fields": [
|
||||||
|
("name", "applicant"),
|
||||||
|
("level", "active"),
|
||||||
|
"external_url",
|
||||||
|
"annotation",
|
||||||
|
("contact_name", "contact_email")
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
("Metadata", {
|
||||||
|
"fields": ["added"],
|
||||||
|
"classes": ["collapse"]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
inlines = [SponsorBenefitInline]
|
||||||
|
|
||||||
|
def get_form(self, *args, **kwargs):
|
||||||
|
# @@@ kinda ugly but using choices= on NullBooleanField is broken
|
||||||
|
form = super(SponsorAdmin, self).get_form(*args, **kwargs)
|
||||||
|
form.base_fields["active"].widget.choices = [
|
||||||
|
(u"1", "unreviewed"),
|
||||||
|
(u"2", "approved"),
|
||||||
|
(u"3", "rejected")
|
||||||
|
]
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
|
class BenefitAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
inlines = [BenefitLevelInline]
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorLevelAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
inlines = [BenefitLevelInline]
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(SponsorLevel, SponsorLevelAdmin)
|
||||||
|
admin.site.register(Sponsor, SponsorAdmin)
|
||||||
|
admin.site.register(Benefit, BenefitAdmin)
|
72
pycon/sponsorship/forms.py
Normal file
72
pycon/sponsorship/forms.py
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
from django import forms
|
||||||
|
from django.forms.models import inlineformset_factory, BaseInlineFormSet
|
||||||
|
|
||||||
|
from django.contrib.admin.widgets import AdminFileWidget
|
||||||
|
|
||||||
|
from pycon.sponsorship.models import Sponsor, SponsorBenefit
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorApplicationForm(forms.ModelForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.user = kwargs.pop("user")
|
||||||
|
kwargs.update({
|
||||||
|
"initial": {
|
||||||
|
"contact_name": self.user.get_full_name,
|
||||||
|
"contact_email": self.user.email,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
super(SponsorApplicationForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Sponsor
|
||||||
|
fields = ["name", "contact_name", "contact_email", "level"]
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
obj = super(SponsorApplicationForm, self).save(commit=False)
|
||||||
|
obj.applicant = self.user
|
||||||
|
if commit:
|
||||||
|
obj.save()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorDetailsForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Sponsor
|
||||||
|
fields = [
|
||||||
|
"name",
|
||||||
|
"external_url",
|
||||||
|
"contact_name",
|
||||||
|
"contact_email"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorBenefitsInlineFormSet(BaseInlineFormSet):
|
||||||
|
|
||||||
|
def _construct_form(self, i, **kwargs):
|
||||||
|
form = super(SponsorBenefitsInlineFormSet, self)._construct_form(i, **kwargs)
|
||||||
|
|
||||||
|
# only include the relevant data fields for this benefit type
|
||||||
|
fields = form.instance.data_fields()
|
||||||
|
form.fields = dict((k, v) for (k, v) in form.fields.items() if k in fields + ["id"])
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
# don't need a label, the form template will label it with the benefit name
|
||||||
|
form.fields[field].label = ""
|
||||||
|
|
||||||
|
# provide word limit as help_text
|
||||||
|
if form.instance.benefit.type == "text" and form.instance.max_words:
|
||||||
|
form.fields[field].help_text = u"maximum %s words" % form.instance.max_words
|
||||||
|
|
||||||
|
# use admin file widget that shows currently uploaded file
|
||||||
|
if field == "upload":
|
||||||
|
form.fields[field].widget = AdminFileWidget()
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
|
SponsorBenefitsFormSet = inlineformset_factory(
|
||||||
|
Sponsor, SponsorBenefit,
|
||||||
|
formset=SponsorBenefitsInlineFormSet,
|
||||||
|
can_delete=False, extra=0,
|
||||||
|
fields=["text", "upload"]
|
||||||
|
)
|
0
pycon/sponsorship/management/__init__.py
Normal file
0
pycon/sponsorship/management/__init__.py
Normal file
0
pycon/sponsorship/management/commands/__init__.py
Normal file
0
pycon/sponsorship/management/commands/__init__.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
|
||||||
|
from pycon.sponsorship import AUTH_GROUPS
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
for group in AUTH_GROUPS:
|
||||||
|
Group.objects.get_or_create(name=group)
|
|
@ -0,0 +1,80 @@
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from contextlib import closing
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.template.defaultfilters import slugify
|
||||||
|
|
||||||
|
from pycon.sponsorship.models import Sponsor
|
||||||
|
|
||||||
|
|
||||||
|
def zipdir(basedir, archivename):
|
||||||
|
assert os.path.isdir(basedir)
|
||||||
|
with closing(zipfile.ZipFile(archivename, "w", zipfile.ZIP_DEFLATED)) as z:
|
||||||
|
for root, dirs, files in os.walk(basedir):
|
||||||
|
#NOTE: ignore empty directories
|
||||||
|
for fn in files:
|
||||||
|
absfn = os.path.join(root, fn)
|
||||||
|
zfn = absfn[len(basedir)+len(os.sep):] #XXX: relative path
|
||||||
|
z.write(absfn, zfn)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.join(os.getcwd(), "build"))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
csv_file = csv.writer(
|
||||||
|
open(os.path.join(os.getcwd(), "build", "sponsors.csv"), "wb")
|
||||||
|
)
|
||||||
|
csv_file.writerow(["Name", "URL", "Level", "Description"])
|
||||||
|
|
||||||
|
for sponsor in Sponsor.objects.all():
|
||||||
|
path = os.path.join(os.getcwd(), "build", slugify(sponsor.name))
|
||||||
|
try:
|
||||||
|
os.makedirs(path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"name": sponsor.name,
|
||||||
|
"url": sponsor.external_url,
|
||||||
|
"level": sponsor.level.name,
|
||||||
|
"description": "",
|
||||||
|
}
|
||||||
|
for sponsor_benefit in sponsor.sponsor_benefits.all():
|
||||||
|
if sponsor_benefit.benefit_id == 2:
|
||||||
|
data["description"] = sponsor_benefit.text
|
||||||
|
if sponsor_benefit.benefit_id == 1:
|
||||||
|
if sponsor_benefit.upload:
|
||||||
|
data["ad"] = sponsor_benefit.upload.path
|
||||||
|
if sponsor_benefit.benefit_id == 7:
|
||||||
|
if sponsor_benefit.upload:
|
||||||
|
data["logo"] = sponsor_benefit.upload.path
|
||||||
|
|
||||||
|
if "ad" in data:
|
||||||
|
ad_path = data.pop("ad")
|
||||||
|
shutil.copy(ad_path, path)
|
||||||
|
if "logo" in data:
|
||||||
|
logo_path = data.pop("logo")
|
||||||
|
shutil.copy(logo_path, path)
|
||||||
|
|
||||||
|
csv_file.writerow([
|
||||||
|
data["name"].encode("utf-8"),
|
||||||
|
data["url"].encode("utf-8"),
|
||||||
|
data["level"].encode("utf-8"),
|
||||||
|
data["description"].encode("utf-8")
|
||||||
|
])
|
||||||
|
|
||||||
|
zipdir(
|
||||||
|
os.path.join(
|
||||||
|
os.getcwd(), "build"),
|
||||||
|
os.path.join(os.getcwd(), "sponsors.zip"
|
||||||
|
)
|
||||||
|
)
|
|
@ -0,0 +1,38 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
|
||||||
|
from pycon.sponsorship.models import Sponsor, SponsorBenefit
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
for sponsor in Sponsor.objects.all():
|
||||||
|
level = None
|
||||||
|
try:
|
||||||
|
level = sponsor.level
|
||||||
|
except SponsorLevel.DoesNotExist:
|
||||||
|
pass
|
||||||
|
if level:
|
||||||
|
for benefit_level in level.benefit_levels.all():
|
||||||
|
# Create all needed benefits if they don't exist already
|
||||||
|
sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
|
||||||
|
sponsor=sponsor, benefit=benefit_level.benefit)
|
||||||
|
|
||||||
|
if created:
|
||||||
|
print "created", sponsor_benefit, "for", sponsor
|
||||||
|
|
||||||
|
# and set to default limits for this level.
|
||||||
|
sponsor_benefit.max_words = benefit_level.max_words
|
||||||
|
sponsor_benefit.other_limits = benefit_level.other_limits
|
||||||
|
|
||||||
|
# and set to active
|
||||||
|
sponsor_benefit.active = True
|
||||||
|
|
||||||
|
# @@@ We don't call sponsor_benefit.clean here. This means
|
||||||
|
# that if the sponsorship level for a sponsor is adjusted
|
||||||
|
# downwards, an existing too-long text entry can remain,
|
||||||
|
# and won't raise a validation error until it's next
|
||||||
|
# edited.
|
||||||
|
sponsor_benefit.save()
|
38
pycon/sponsorship/managers.py
Normal file
38
pycon/sponsorship/managers.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorManager(models.Manager):
|
||||||
|
|
||||||
|
def active(self):
|
||||||
|
return self.get_query_set().filter(active=True).order_by("level")
|
||||||
|
|
||||||
|
def with_weblogo(self):
|
||||||
|
queryset = self.raw("""
|
||||||
|
SELECT DISTINCT
|
||||||
|
"sponsorship_sponsor"."id",
|
||||||
|
"sponsorship_sponsor"."applicant_id",
|
||||||
|
"sponsorship_sponsor"."name",
|
||||||
|
"sponsorship_sponsor"."external_url",
|
||||||
|
"sponsorship_sponsor"."annotation",
|
||||||
|
"sponsorship_sponsor"."contact_name",
|
||||||
|
"sponsorship_sponsor"."contact_email",
|
||||||
|
"sponsorship_sponsor"."level_id",
|
||||||
|
"sponsorship_sponsor"."added",
|
||||||
|
"sponsorship_sponsor"."active",
|
||||||
|
"sponsorship_sponsorlevel"."order"
|
||||||
|
FROM
|
||||||
|
"sponsorship_sponsor"
|
||||||
|
INNER JOIN
|
||||||
|
"sponsorship_sponsorbenefit" ON ("sponsorship_sponsor"."id" = "sponsorship_sponsorbenefit"."sponsor_id")
|
||||||
|
INNER JOIN
|
||||||
|
"sponsorship_benefit" ON ("sponsorship_sponsorbenefit"."benefit_id" = "sponsorship_benefit"."id")
|
||||||
|
LEFT OUTER JOIN
|
||||||
|
"sponsorship_sponsorlevel" ON ("sponsorship_sponsor"."level_id" = "sponsorship_sponsorlevel"."id")
|
||||||
|
WHERE (
|
||||||
|
"sponsorship_sponsor"."active" = 't' AND
|
||||||
|
"sponsorship_benefit"."type" = 'weblogo' AND
|
||||||
|
"sponsorship_sponsorbenefit"."upload" != ''
|
||||||
|
)
|
||||||
|
ORDER BY "sponsorship_sponsorlevel"."order" ASC, "sponsorship_sponsor"."added" ASC
|
||||||
|
""")
|
||||||
|
return queryset
|
266
pycon/sponsorship/models.py
Normal file
266
pycon/sponsorship/models.py
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models.signals import post_init, post_save
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from symposion.conference.models import Conference
|
||||||
|
|
||||||
|
from pycon.sponsorship import SPONSOR_COORDINATORS
|
||||||
|
from pycon.sponsorship.managers import SponsorManager
|
||||||
|
# from symposion.utils.mail import send_email
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorLevel(models.Model):
|
||||||
|
|
||||||
|
conference = models.ForeignKey(Conference, verbose_name=_("conference"))
|
||||||
|
name = models.CharField(_("name"), max_length=100)
|
||||||
|
order = models.IntegerField(_("order"), default=0)
|
||||||
|
cost = models.PositiveIntegerField(_("cost"))
|
||||||
|
description = models.TextField(_("description"), blank=True, help_text=_("This is private."))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["conference", "order"]
|
||||||
|
verbose_name = _("sponsor level")
|
||||||
|
verbose_name_plural = _("sponsor levels")
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s %s" % (self.conference, self.name)
|
||||||
|
|
||||||
|
def sponsors(self):
|
||||||
|
return self.sponsor_set.filter(active=True).order_by("added")
|
||||||
|
|
||||||
|
|
||||||
|
class Sponsor(models.Model):
|
||||||
|
|
||||||
|
applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"), null=True)
|
||||||
|
|
||||||
|
name = models.CharField(_("Sponsor Name"), max_length=100)
|
||||||
|
external_url = models.URLField(_("external URL"))
|
||||||
|
annotation = models.TextField(_("annotation"), blank=True)
|
||||||
|
contact_name = models.CharField(_("Contact Name"), max_length=100)
|
||||||
|
contact_email = models.EmailField(_(u"Contact Email"))
|
||||||
|
level = models.ForeignKey(SponsorLevel, verbose_name=_("level"))
|
||||||
|
added = models.DateTimeField(_("added"), default=datetime.datetime.now)
|
||||||
|
active = models.BooleanField(_("active"), default=False)
|
||||||
|
|
||||||
|
# Denormalization (this assumes only one logo)
|
||||||
|
sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True, editable=False)
|
||||||
|
|
||||||
|
objects = SponsorManager()
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("sponsor")
|
||||||
|
verbose_name_plural = _("sponsors")
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
if self.active:
|
||||||
|
return reverse("sponsor_detail", kwargs={"pk": self.pk})
|
||||||
|
return reverse("sponsor_list")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def website_logo_url(self):
|
||||||
|
if not hasattr(self, "_website_logo_url"):
|
||||||
|
self._website_logo_url = None
|
||||||
|
benefits = self.sponsor_benefits.filter(benefit__type="weblogo", upload__isnull=False)
|
||||||
|
if benefits.exists():
|
||||||
|
# @@@ smarter handling of multiple weblogo benefits?
|
||||||
|
# shouldn't happen
|
||||||
|
if benefits[0].upload:
|
||||||
|
self._website_logo_url = benefits[0].upload.url
|
||||||
|
return self._website_logo_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def listing_text(self):
|
||||||
|
if not hasattr(self, "_listing_text"):
|
||||||
|
self._listing_text = None
|
||||||
|
benefits = self.sponsor_benefits.filter(benefit__id=7)
|
||||||
|
if benefits.count():
|
||||||
|
self._listing_text = benefits[0].text
|
||||||
|
return self._listing_text
|
||||||
|
|
||||||
|
@property
|
||||||
|
def joblisting_text(self):
|
||||||
|
if not hasattr(self, "_joblisting_text"):
|
||||||
|
self._joblisting_text = None
|
||||||
|
benefits = self.sponsor_benefits.filter(benefit__id=21)
|
||||||
|
if benefits.count():
|
||||||
|
self._joblisting_text = benefits[0].text
|
||||||
|
return self._joblisting_text
|
||||||
|
|
||||||
|
@property
|
||||||
|
def website_logo(self):
|
||||||
|
if self.sponsor_logo is None:
|
||||||
|
benefits = self.sponsor_benefits.filter(benefit__type="weblogo", upload__isnull=False)[:1]
|
||||||
|
if benefits.count():
|
||||||
|
if benefits[0].upload:
|
||||||
|
self.sponsor_logo = benefits[0]
|
||||||
|
self.save()
|
||||||
|
return self.sponsor_logo.upload
|
||||||
|
|
||||||
|
def reset_benefits(self):
|
||||||
|
"""
|
||||||
|
Reset all benefits for this sponsor to the defaults for their
|
||||||
|
sponsorship level.
|
||||||
|
"""
|
||||||
|
level = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
level = self.level
|
||||||
|
except SponsorLevel.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
allowed_benefits = []
|
||||||
|
if level:
|
||||||
|
for benefit_level in level.benefit_levels.all():
|
||||||
|
# Create all needed benefits if they don't exist already
|
||||||
|
sponsor_benefit, created = SponsorBenefit.objects.get_or_create(
|
||||||
|
sponsor=self, benefit=benefit_level.benefit)
|
||||||
|
|
||||||
|
# and set to default limits for this level.
|
||||||
|
sponsor_benefit.max_words = benefit_level.max_words
|
||||||
|
sponsor_benefit.other_limits = benefit_level.other_limits
|
||||||
|
|
||||||
|
# and set to active
|
||||||
|
sponsor_benefit.active = True
|
||||||
|
|
||||||
|
# @@@ We don't call sponsor_benefit.clean here. This means
|
||||||
|
# that if the sponsorship level for a sponsor is adjusted
|
||||||
|
# downwards, an existing too-long text entry can remain,
|
||||||
|
# and won't raise a validation error until it's next
|
||||||
|
# edited.
|
||||||
|
sponsor_benefit.save()
|
||||||
|
|
||||||
|
allowed_benefits.append(sponsor_benefit.pk)
|
||||||
|
|
||||||
|
# Any remaining sponsor benefits that don't normally belong to
|
||||||
|
# this level are set to inactive
|
||||||
|
self.sponsor_benefits.exclude(pk__in=allowed_benefits).update(active=False, max_words=None, other_limits="")
|
||||||
|
|
||||||
|
# @@@ should this just be done centrally?
|
||||||
|
def send_coordinator_emails(self):
|
||||||
|
for user in User.objects.filter(groups__name=SPONSOR_COORDINATORS):
|
||||||
|
send_email(
|
||||||
|
[user.email], "sponsor_signup",
|
||||||
|
context = {"sponsor": self}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _store_initial_level(sender, instance, **kwargs):
|
||||||
|
if instance:
|
||||||
|
instance._initial_level_id = instance.level_id
|
||||||
|
post_init.connect(_store_initial_level, sender=Sponsor)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_level_change(sender, instance, created, **kwargs):
|
||||||
|
if instance and (created or instance.level_id != instance._initial_level_id):
|
||||||
|
instance.reset_benefits()
|
||||||
|
post_save.connect(_check_level_change, sender=Sponsor)
|
||||||
|
|
||||||
|
|
||||||
|
def _send_sponsor_notification_emails(sender, instance, created, **kwargs):
|
||||||
|
if instance and created:
|
||||||
|
instance.send_coordinator_emails()
|
||||||
|
post_save.connect(_send_sponsor_notification_emails, sender=Sponsor)
|
||||||
|
|
||||||
|
|
||||||
|
class Benefit(models.Model):
|
||||||
|
|
||||||
|
name = models.CharField(_("name"), max_length=100)
|
||||||
|
description = models.TextField(_("description"), blank=True)
|
||||||
|
type = models.CharField(
|
||||||
|
_("type"),
|
||||||
|
choices=[
|
||||||
|
("text", "Text"),
|
||||||
|
("file", "File"),
|
||||||
|
("weblogo", "Web Logo"),
|
||||||
|
("simple", "Simple")
|
||||||
|
],
|
||||||
|
max_length=10,
|
||||||
|
default="simple"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class BenefitLevel(models.Model):
|
||||||
|
|
||||||
|
benefit = models.ForeignKey(
|
||||||
|
Benefit,
|
||||||
|
related_name="benefit_levels",
|
||||||
|
verbose_name=_("benefit")
|
||||||
|
)
|
||||||
|
level = models.ForeignKey(
|
||||||
|
SponsorLevel,
|
||||||
|
related_name="benefit_levels",
|
||||||
|
verbose_name=_("level")
|
||||||
|
)
|
||||||
|
max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True)
|
||||||
|
other_limits = models.CharField(_("other limits"), max_length=200, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["level"]
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s - %s" % (self.level, self.benefit)
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorBenefit(models.Model):
|
||||||
|
|
||||||
|
sponsor = models.ForeignKey(
|
||||||
|
Sponsor,
|
||||||
|
related_name="sponsor_benefits",
|
||||||
|
verbose_name=_("sponsor")
|
||||||
|
)
|
||||||
|
benefit = models.ForeignKey(Benefit,
|
||||||
|
related_name="sponsor_benefits",
|
||||||
|
verbose_name=_("benefit")
|
||||||
|
)
|
||||||
|
active = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
# Limits: will initially be set to defaults from corresponding BenefitLevel
|
||||||
|
max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True)
|
||||||
|
other_limits = models.CharField(_("other limits"), max_length=200, blank=True)
|
||||||
|
|
||||||
|
# Data: zero or one of these fields will be used, depending on the
|
||||||
|
# type of the Benefit (text, file, or simple)
|
||||||
|
text = models.TextField(_("text"), blank=True)
|
||||||
|
upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-active']
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s - %s" % (self.sponsor, self.benefit)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if self.max_words and len(self.text.split()) > self.max_words:
|
||||||
|
raise ValidationError("Sponsorship level only allows for %s words." % self.max_words)
|
||||||
|
|
||||||
|
def data_fields(self):
|
||||||
|
"""
|
||||||
|
Return list of data field names which should be editable for
|
||||||
|
this ``SponsorBenefit``, depending on its ``Benefit`` type.
|
||||||
|
"""
|
||||||
|
if self.benefit.type == "file" or self.benefit.type == "weblogo":
|
||||||
|
return ["upload"]
|
||||||
|
elif self.benefit.type == "text":
|
||||||
|
return ["text"]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _denorm_weblogo(sender, instance, created, **kwargs):
|
||||||
|
if instance:
|
||||||
|
if instance.benefit.type == "weblogo" and instance.upload:
|
||||||
|
sponsor = instance.sponsor
|
||||||
|
sponsor.sponsor_logo = instance
|
||||||
|
sponsor.save()
|
||||||
|
post_save.connect(_denorm_weblogo, sender=SponsorBenefit)
|
0
pycon/sponsorship/templatetags/__init__.py
Normal file
0
pycon/sponsorship/templatetags/__init__.py
Normal file
74
pycon/sponsorship/templatetags/sponsorship_tags.py
Normal file
74
pycon/sponsorship/templatetags/sponsorship_tags.py
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
from symposion.conference.models import current_conference
|
||||||
|
from pycon.sponsorship.models import Sponsor, SponsorLevel
|
||||||
|
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorsNode(template.Node):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def handle_token(cls, parser, token):
|
||||||
|
bits = token.split_contents()
|
||||||
|
if len(bits) == 3 and bits[1] == "as":
|
||||||
|
return cls(bits[2])
|
||||||
|
elif len(bits) == 4 and bits[2] == "as":
|
||||||
|
return cls(bits[3], bits[1])
|
||||||
|
else:
|
||||||
|
raise template.TemplateSyntaxError("%r takes 'as var' or 'level as var'" % bits[0])
|
||||||
|
|
||||||
|
def __init__(self, context_var, level=None):
|
||||||
|
if level:
|
||||||
|
self.level = template.Variable(level)
|
||||||
|
else:
|
||||||
|
self.level = None
|
||||||
|
self.context_var = context_var
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
conference = current_conference()
|
||||||
|
if self.level:
|
||||||
|
level = self.level.resolve(context)
|
||||||
|
queryset = Sponsor.objects.filter(level__conference = conference, level__name__iexact = level, active = True).order_by("added")
|
||||||
|
else:
|
||||||
|
queryset = Sponsor.objects.filter(level__conference = conference, active = True).order_by("level__order", "added")
|
||||||
|
context[self.context_var] = queryset
|
||||||
|
return u""
|
||||||
|
|
||||||
|
|
||||||
|
class SponsorLevelNode(template.Node):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def handle_token(cls, parser, token):
|
||||||
|
bits = token.split_contents()
|
||||||
|
if len(bits) == 3 and bits[1] == "as":
|
||||||
|
return cls(bits[2])
|
||||||
|
else:
|
||||||
|
raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0])
|
||||||
|
|
||||||
|
def __init__(self, context_var):
|
||||||
|
self.context_var = context_var
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
conference = current_conference()
|
||||||
|
context[self.context_var] = SponsorLevel.objects.filter(conference=conference)
|
||||||
|
return u""
|
||||||
|
|
||||||
|
|
||||||
|
@register.tag
|
||||||
|
def sponsors(parser, token):
|
||||||
|
"""
|
||||||
|
{% sponsors as all_sponsors %}
|
||||||
|
or
|
||||||
|
{% sponsors "gold" as gold_sponsors %}
|
||||||
|
"""
|
||||||
|
return SponsorsNode.handle_token(parser, token)
|
||||||
|
|
||||||
|
|
||||||
|
@register.tag
|
||||||
|
def sponsor_levels(parser, token):
|
||||||
|
"""
|
||||||
|
{% sponsor_levels as levels %}
|
||||||
|
"""
|
||||||
|
return SponsorLevelNode.handle_token(parser, token)
|
10
pycon/sponsorship/urls.py
Normal file
10
pycon/sponsorship/urls.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
from django.conf.urls.defaults import patterns, url
|
||||||
|
from django.views.generic.simple import direct_to_template
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns("pycon.sponsorship.views",
|
||||||
|
url(r"^$", direct_to_template, {"template": "sponsorship/list.html"}, name="sponsor_list"),
|
||||||
|
# url(r"^jobs/$", direct_to_template, {"template": "sponsors/jobs.html"}, name="sponsor_jobs"),
|
||||||
|
url(r"^apply/$", "sponsor_apply", name="sponsor_apply"),
|
||||||
|
url(r"^(?P<pk>\d+)/$", "sponsor_detail", name="sponsor_detail"),
|
||||||
|
)
|
115
pycon/sponsorship/views.py
Normal file
115
pycon/sponsorship/views.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import render_to_response, redirect, get_object_or_404
|
||||||
|
from django.template import RequestContext
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
from pycon.sponsorship.forms import SponsorApplicationForm, SponsorDetailsForm, SponsorBenefitsFormSet
|
||||||
|
from pycon.sponsorship.models import Sponsor, SponsorBenefit
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def sponsor_apply(request):
|
||||||
|
if request.method == "POST":
|
||||||
|
form = SponsorApplicationForm(request.POST, user=request.user)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return redirect("dashboard")
|
||||||
|
else:
|
||||||
|
form = SponsorApplicationForm(user=request.user)
|
||||||
|
|
||||||
|
return render_to_response("sponsorship/apply.html", {
|
||||||
|
"form": form,
|
||||||
|
}, context_instance=RequestContext(request))
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def sponsor_detail(request, pk):
|
||||||
|
sponsor = get_object_or_404(Sponsor, pk=pk)
|
||||||
|
|
||||||
|
if not sponsor.active or sponsor.applicant != request.user:
|
||||||
|
return redirect("sponsor_list")
|
||||||
|
|
||||||
|
formset_kwargs = {
|
||||||
|
"instance": sponsor,
|
||||||
|
"queryset": SponsorBenefit.objects.filter(active=True)
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
|
||||||
|
form = SponsorDetailsForm(request.POST, instance=sponsor)
|
||||||
|
formset = SponsorBenefitsFormSet(request.POST, request.FILES, **formset_kwargs)
|
||||||
|
|
||||||
|
if form.is_valid() and formset.is_valid():
|
||||||
|
form.save()
|
||||||
|
formset.save()
|
||||||
|
|
||||||
|
messages.success(request, "Your sponsorship application has been submitted!")
|
||||||
|
|
||||||
|
return redirect(request.path)
|
||||||
|
else:
|
||||||
|
form = SponsorDetailsForm(instance=sponsor)
|
||||||
|
formset = SponsorBenefitsFormSet(**formset_kwargs)
|
||||||
|
|
||||||
|
return render_to_response("sponsorship/detail.html", {
|
||||||
|
"sponsor": sponsor,
|
||||||
|
"form": form,
|
||||||
|
"formset": formset,
|
||||||
|
}, context_instance=RequestContext(request))
|
||||||
|
|
||||||
|
|
||||||
|
@staff_member_required
|
||||||
|
def sponsor_export_data(request):
|
||||||
|
sponsors = []
|
||||||
|
data = ""
|
||||||
|
|
||||||
|
for sponsor in Sponsor.objects.order_by("added"):
|
||||||
|
d = {
|
||||||
|
"name": sponsor.name,
|
||||||
|
"url": sponsor.external_url,
|
||||||
|
"level": (sponsor.level.order, sponsor.level.name),
|
||||||
|
"description": "",
|
||||||
|
}
|
||||||
|
for sponsor_benefit in sponsor.sponsor_benefits.all():
|
||||||
|
if sponsor_benefit.benefit_id == 2:
|
||||||
|
d["description"] = sponsor_benefit.text
|
||||||
|
sponsors.append(d)
|
||||||
|
|
||||||
|
def izip_longest(*args):
|
||||||
|
fv = None
|
||||||
|
def sentinel(counter=([fv]*(len(args)-1)).pop):
|
||||||
|
yield counter()
|
||||||
|
iters = [itertools.chain(it, sentinel(), itertools.repeat(fv)) for it in args]
|
||||||
|
try:
|
||||||
|
for tup in itertools.izip(*iters):
|
||||||
|
yield tup
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
def pairwise(iterable):
|
||||||
|
a, b = itertools.tee(iterable)
|
||||||
|
b.next()
|
||||||
|
return izip_longest(a, b)
|
||||||
|
|
||||||
|
def level_key(s):
|
||||||
|
return s["level"]
|
||||||
|
|
||||||
|
for level, level_sponsors in itertools.groupby(sorted(sponsors, key=level_key), level_key):
|
||||||
|
data += "%s\n" % ("-" * (len(level[1])+4))
|
||||||
|
data += "| %s |\n" % level[1]
|
||||||
|
data += "%s\n\n" % ("-" * (len(level[1])+4))
|
||||||
|
for sponsor, next in pairwise(level_sponsors):
|
||||||
|
description = sponsor["description"].strip()
|
||||||
|
description = description if description else "-- NO DESCRIPTION FOR THIS SPONSOR --"
|
||||||
|
data += "%s\n\n%s" % (sponsor["name"], description)
|
||||||
|
if next is not None:
|
||||||
|
data += "\n\n%s\n\n" % ("-"*80)
|
||||||
|
else:
|
||||||
|
data += "\n\n"
|
||||||
|
|
||||||
|
return HttpResponse(data, content_type="text/plain;charset=utf-8")
|
Loading…
Reference in a new issue