From e57991e473fb00816e2cc06afd5ee8e4dd046015 Mon Sep 17 00:00:00 2001 From: Magnus Hagander Date: Tue, 6 Nov 2018 16:28:28 +0100 Subject: [PATCH] Add "waitlist" for sponsors This is not really a waitlist, as it simply allows sponsors to sign up even when the level is full, but tells them they are on the waitlist and they will not be approved. The actual approval/rejection is handled manually by the administratror. --- docs/confreg/sponsors.md | 30 +++++++++++++++++-- postgresqleu/confsponsor/backendforms.py | 2 +- .../migrations/0011_maxsponsors.py | 20 +++++++++++++ postgresqleu/confsponsor/models.py | 23 ++++++++++++++ postgresqleu/confsponsor/views.py | 3 ++ template/confsponsor/admin_sponsor.html | 7 +++++ template/confsponsor/signup.html | 13 +++++++- template/confsponsor/signupform.html | 12 ++++++++ 8 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 postgresqleu/confsponsor/migrations/0011_maxsponsors.py diff --git a/docs/confreg/sponsors.md b/docs/confreg/sponsors.md index d310694..a1eb37d 100644 --- a/docs/confreg/sponsors.md +++ b/docs/confreg/sponsors.md @@ -70,6 +70,24 @@ once the signup is complete becomes: make sure to double check things like invoice details before doing so, as reverting from this state is complicated. +## Sponsor waitlist + +The sponsor waitlist is managed manually. When a maximum number of +sponsors are set at a level, signup works normally until there are +that many sponsors signed up. + +Once that number of sponsors are signed up *and confirmed*, the +sign-up button is automatically removed, and the level cannot be used +anymore. + +In the period where there are fewer *confirmed* sponsors than there +are maximum number of allowed sponsors, a wait list is allowed. The +waitlist is *manually* managed, so what happens is that these sponsors +are added as normal, but should not be "clicked through" by the +adminstrator. Once the limit is exceeded a red warning text is shown +on the page where invoices are generated, to avoid doing this by +mistake. + ## Managing sponsors Once sponsors are confirmed, await them to claim sponsorship @@ -124,9 +142,15 @@ Levelcost : Price for this level (excluding VAT if VAT is used) Available for signup -: Whether this level is currently enabled for signup (should normally - be on unless the level has a maximum number of uses and is sold - out). +: Whether this level is currently enabled for signup. + +Maximum number of sponsors +: Maximum number of sponsors that can sign up at this level. If more + than this number of *confirmed* sponsors exist, the sign up button + will be removed. If there are fewer *confirmed* sponsors, but the + total number including *unconfirmed* sponsors exceed exceed the number, + sponsors are offered a waitlist. If set to zero then an unlimited + number of sponsors are allowed at this level. Instant buy available : If this level requires a signed contract. If this box is checked, diff --git a/postgresqleu/confsponsor/backendforms.py b/postgresqleu/confsponsor/backendforms.py index 0f1a1f8..1f84756 100644 --- a/postgresqleu/confsponsor/backendforms.py +++ b/postgresqleu/confsponsor/backendforms.py @@ -134,7 +134,7 @@ class BackendSponsorshipLevelForm(BackendForm): class Meta: model = SponsorshipLevel - fields = ['levelname', 'urlname', 'levelcost', 'available', 'instantbuy', + fields = ['levelname', 'urlname', 'levelcost', 'available', 'maxnumber', 'instantbuy', 'paymentmethods', 'contract', 'canbuyvoucher', 'canbuydiscountcode'] def fix_fields(self): diff --git a/postgresqleu/confsponsor/migrations/0011_maxsponsors.py b/postgresqleu/confsponsor/migrations/0011_maxsponsors.py new file mode 100644 index 0000000..35f6c27 --- /dev/null +++ b/postgresqleu/confsponsor/migrations/0011_maxsponsors.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.10 on 2018-11-06 15:47 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('confsponsor', '0010_benefit_tweet_template'), + ] + + operations = [ + migrations.AddField( + model_name='sponsorshiplevel', + name='maxnumber', + field=models.IntegerField(default=0, verbose_name=b'Maximum number of sponsors'), + ), + ] diff --git a/postgresqleu/confsponsor/models.py b/postgresqleu/confsponsor/models.py index c27ba86..da9199b 100644 --- a/postgresqleu/confsponsor/models.py +++ b/postgresqleu/confsponsor/models.py @@ -1,6 +1,7 @@ from django.db import models from django.db.models.signals import pre_delete from django.core.exceptions import ValidationError +from django.utils.functional import cached_property from django.contrib.auth.models import User from django.contrib.postgres.fields import JSONField @@ -46,6 +47,7 @@ class SponsorshipLevel(models.Model): urlname = models.CharField(max_length=100, null=False, blank=False, validators=[validate_lowercase,]) levelcost = models.IntegerField(null=False, blank=False) available = models.BooleanField(null=False, blank=False, default=True, verbose_name="Available for signup") + maxnumber = models.IntegerField(null=False, blank=False, default=0, verbose_name="Maximum number of sponsors") instantbuy = models.BooleanField(null=False, blank=False, default=False, verbose_name="Instant buy available") paymentmethods = models.ManyToManyField(InvoicePaymentMethod, blank=False, verbose_name="Payment methods for generated invoices") contract = models.ForeignKey(SponsorshipContract, blank=True, null=True, on_delete=models.CASCADE) @@ -59,6 +61,27 @@ class SponsorshipLevel(models.Model): ordering = ('levelcost', 'levelname',) unique_together = (('conference', 'urlname'), ) + @cached_property + def num_confirmed(self): + return self.sponsor_set.filter(confirmed=True).count() + + @cached_property + def num_unconfirmed(self): + return self.sponsor_set.filter(confirmed=False).count() + + @cached_property + def num_total(self): + return self.num_confirmed + self.num_unconfirmed + + @cached_property + def can_signup(self): + if self.available: + if self.maxnumber > 0: + return self.num_confirmed < self.maxnumber + else: + return True + return False + class SponsorshipBenefit(models.Model): level = models.ForeignKey(SponsorshipLevel, null=False, blank=False, on_delete=models.CASCADE) benefitname = models.CharField(max_length=100, null=False, blank=False) diff --git a/postgresqleu/confsponsor/views.py b/postgresqleu/confsponsor/views.py index 7eaac22..9f87232 100644 --- a/postgresqleu/confsponsor/views.py +++ b/postgresqleu/confsponsor/views.py @@ -255,6 +255,9 @@ def sponsor_signup(request, confurlname, levelurlname): raise Http404() level = get_object_or_404(SponsorshipLevel, conference=conference, urlname=levelurlname, available=True) + if not level.can_signup: + messages.error(request, "This level is not currently available for signup") + return HttpResponseRedirect("../") user_name = request.user.first_name + ' ' + request.user.last_name diff --git a/template/confsponsor/admin_sponsor.html b/template/confsponsor/admin_sponsor.html index eeb277a..9382831 100644 --- a/template/confsponsor/admin_sponsor.html +++ b/template/confsponsor/admin_sponsor.html @@ -131,6 +131,13 @@ has been received, go ahead and generate the invoice. be emailed to the sponsor, as well as show up on their dashboard. Once the invoice is paid, the sponsorship will automatically become confirmed.

+{%if sponsor.level.maxnumber > 0%} + sponsor.level.maxnumber%} style="color:red;"{%endif%}> + This level allows a maximum of {{sponsor.level.maxnumber}} sponsors. There are currently + {{sponsor.level.num_confirmed}} confirmed and {{sponsor.level.num_unconfirmed}} unconfirmed + sponsors signed up at this level, make sure you don't approve too many! +

+{%endif%}
{%csrf_token%}
diff --git a/template/confsponsor/signup.html b/template/confsponsor/signup.html index 17d10f5..95c1fe2 100644 --- a/template/confsponsor/signup.html +++ b/template/confsponsor/signup.html @@ -60,6 +60,17 @@ The following benefits are available at this level (click for details):

The price for this sponsorship level is {{currency_symbol}}{{level.levelcost}}.

+{%if level.maxnumber%} +

+ This level allows a maximum of {{level.maxnumber}} sponsors. There are currently + {{level.num_confirmed}} confirmed and {{level.num_unconfirmed}} unconfirmed + sponsors signed up at this level. +{%if level.maxnumber <= level.num_total and level.num_unconfirmed > 0 %} +At this point, you can sign up on the wait list for this level, and will be granted +a slot if not all other sponsors complete their signup. +{%endif%} +

+{%endif%} {%if level.contract%}

A full specification of all the levels can be found in the @@ -67,7 +78,7 @@ A full specification of all the levels can be found in the If there is a difference, the version listed in the contract takes precedence.

{%endif%} -{%if level.available%} +{%if level.can_signup %}

Sign Up

{%else%}

This level is not currently available for signup.

diff --git a/template/confsponsor/signupform.html b/template/confsponsor/signupform.html index 7d1d3fd..9c1d66a 100644 --- a/template/confsponsor/signupform.html +++ b/template/confsponsor/signupform.html @@ -77,6 +77,18 @@ instructions.

{%endif%} +{%if level.maxnumber%} +

+ This level allows a maximum of {{level.maxnumber}} sponsors. There are currently + {{level.num_confirmed}} confirmed and {{level.num_unconfirmed}} unconfirmed + sponsors signed up at this level. +{%if level.maxnumber <= level.num_total and level.num_unconfirmed > 0 %} +At this point, you can sign up on the wait list for this level, and will be granted +a slot if not all other sponsors complete their signup. +{%endif%} +

+{%endif%} + {%if previewaddr%} -- 2.39.5