From 3ffcc4da7c24c012ddce39bc9cc8439fefb93560 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Thu, 12 Jul 2012 15:17:42 -0400 Subject: [PATCH] first pass at merging pycon's sponsorship app with more basic symposion one --- symposion/sponsorship/admin.py | 68 ++++++++++++++++++++- symposion/sponsorship/forms.py | 72 ++++++++++++++++++++++ symposion/sponsorship/managers.py | 7 +++ symposion/sponsorship/models.py | 99 +++++++++++++++++++++++++++++-- symposion/sponsorship/urls.py | 9 +++ symposion/sponsorship/views.py | 58 ++++++++++++++++++ 6 files changed, 305 insertions(+), 8 deletions(-) create mode 100644 symposion/sponsorship/forms.py create mode 100644 symposion/sponsorship/managers.py create mode 100644 symposion/sponsorship/urls.py create mode 100644 symposion/sponsorship/views.py diff --git a/symposion/sponsorship/admin.py b/symposion/sponsorship/admin.py index a26b0a96..482ffb7b 100644 --- a/symposion/sponsorship/admin.py +++ b/symposion/sponsorship/admin.py @@ -1,7 +1,69 @@ from django.contrib import admin -from symposion.sponsorship.models import SponsorLevel, Sponsor +from symposion.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, SponsorBenefit -admin.site.register(SponsorLevel) -admin.site.register(Sponsor, list_display=("name", "level", "added", "active"), list_filter = ("level", )) +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) diff --git a/symposion/sponsorship/forms.py b/symposion/sponsorship/forms.py new file mode 100644 index 00000000..d8fd418a --- /dev/null +++ b/symposion/sponsorship/forms.py @@ -0,0 +1,72 @@ +from django import forms +from django.forms.models import inlineformset_factory, BaseInlineFormSet + +from django.contrib.admin.widgets import AdminFileWidget + +from symposion.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"] +) diff --git a/symposion/sponsorship/managers.py b/symposion/sponsorship/managers.py new file mode 100644 index 00000000..490b328c --- /dev/null +++ b/symposion/sponsorship/managers.py @@ -0,0 +1,7 @@ +from django.db import models + + +class SponsorManager(models.Manager): + + def active(self): + return self.get_query_set().filter(active=True).order_by("level") diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index a902c0be..9c9eeda2 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -1,17 +1,24 @@ import datetime +from django.core.exceptions import ValidationError +from django.core.urlresolvers import reverse from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.models import User + from symposion.conference.models import Conference +from symposion.sponsorship.managers import SponsorManager + class SponsorLevel(models.Model): conference = models.ForeignKey(Conference, verbose_name=_("conference")) name = models.CharField(_("name"), max_length=100) order = models.IntegerField(_("order"), default=0) - description = models.TextField(_("description"), blank=True) + cost = models.PositiveIntegerField(_("cost")) + description = models.TextField(_("description"), blank=True, help_text=_("This is private.")) class Meta: ordering = ["conference", "order"] @@ -27,19 +34,101 @@ class SponsorLevel(models.Model): class Sponsor(models.Model): - name = models.CharField(_("name"), max_length=100) + 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 e\u2011mail")) - logo = models.ImageField(_("logo"), upload_to="sponsor_logos/") + 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") + + +BENEFIT_TYPE_CHOICES = [ + ("text", "Text"), + ("file", "File"), + ("weblogo", "Web Logo"), + ("simple", "Simple") +] + + +class Benefit(models.Model): + + name = models.CharField(_("name"), max_length=100) + description = models.TextField(_("description"), blank=True) + type = models.CharField(_("type"), choices=BENEFIT_TYPE_CHOICES, max_length=10, default="simple") + + def __unicode__(self): + return self.name + + +class BenefitLevel(models.Model): + + benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit")) + level = models.ForeignKey(SponsorLevel, related_name="benefit_levels", verbose_name=_("level")) + + # default limits for this benefit at given level + max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) + other_limits = models.CharField(_("other limits"), max_length=200, blank=True) + + class Meta: + ordering = ["level"] + + def __unicode__(self): + return u"%s - %s" % (self.level, self.benefit) + + +class SponsorBenefit(models.Model): + + sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("sponsor")) + benefit = models.ForeignKey(Benefit, related_name="sponsor_benefits", verbose_name=_("benefit")) + active = models.BooleanField(default=True) + + # Limits: will initially be set to defaults from corresponding BenefitLevel + max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) + other_limits = models.CharField(_("other limits"), max_length=200, blank=True) + + # Data: zero or one of these fields will be used, depending on the + # type of the Benefit (text, file, or simple) + text = models.TextField(_("text"), blank=True) + upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files") + + class Meta: + ordering = ['-active'] + + def __unicode__(self): + return u"%s - %s" % (self.sponsor, self.benefit) + + def clean(self): + 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 [] diff --git a/symposion/sponsorship/urls.py b/symposion/sponsorship/urls.py new file mode 100644 index 00000000..52ccdaae --- /dev/null +++ b/symposion/sponsorship/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import patterns, url +from django.views.generic.simple import direct_to_template + + +urlpatterns = patterns("symposion.sponsorship.views", + url(r"^$", direct_to_template, {"template": "sponsorship/list.html"}, name="sponsor_list"), + url(r"^apply/$", "sponsor_apply", name="sponsor_apply"), + url(r"^(?P\d+)/$", "sponsor_detail", name="sponsor_detail"), +) diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py new file mode 100644 index 00000000..83acbf9e --- /dev/null +++ b/symposion/sponsorship/views.py @@ -0,0 +1,58 @@ +from django.shortcuts import render_to_response, redirect, get_object_or_404 +from django.template import RequestContext + +from django.contrib import messages +from django.contrib.auth.decorators import login_required + +from 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))