Changeset - 6d9c1c2e5f49
pycon/__init__.py
Show inline comments
 
new file 100644
pycon/admin.py
Show inline comments
 
new file 100644
 
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)
...
 
\ No newline at end of file
pycon/forms.py
Show inline comments
 
new file 100644
 
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(),
 
        }
 

	
pycon/models.py
Show inline comments
 
new file 100644
 
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"
pycon/sponsorship/__init__.py
Show inline comments
 
new file 100644
 
SPONSOR_COORDINATORS = "sponsor-coordinators"
 

	
 
AUTH_GROUPS = [
 
    SPONSOR_COORDINATORS
 
]
pycon/sponsorship/admin.py
Show inline comments
 
new file 100644
 
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)
...
 
\ No newline at end of file
pycon/sponsorship/forms.py
Show inline comments
 
new file 100644
 
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"]
 
)
pycon/sponsorship/management/__init__.py
Show inline comments
 
new file 100644
pycon/sponsorship/management/commands/__init__.py
Show inline comments
 
new file 100644
pycon/sponsorship/management/commands/create_sponsors_groups.py
Show inline comments
 
new file 100644
 
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)
pycon/sponsorship/management/commands/export_sponsors_data.py
Show inline comments
 
new file 100644
 
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"
 
            )
 
        )
pycon/sponsorship/management/commands/reset_sponsor_benefits.py
Show inline comments
 
new file 100644
 
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()
pycon/sponsorship/managers.py
Show inline comments
 
new file 100644
 
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
pycon/sponsorship/models.py
Show inline comments
 
new file 100644
 
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)
...
 
\ No newline at end of file
pycon/sponsorship/templatetags/__init__.py
Show inline comments
 
new file 100644
pycon/sponsorship/templatetags/sponsorship_tags.py
Show inline comments
 
new file 100644
 
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)
pycon/sponsorship/urls.py
Show inline comments
 
new file 100644
 
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"),
 
)
pycon/sponsorship/views.py
Show inline comments
 
new file 100644
 
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")
0 comments (0 inline, 0 general)