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%}
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.
+ 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%}
+