From 361789f0b6a26a5d399f7721ecfb6133c04b01e8 Mon Sep 17 00:00:00 2001
From: Magnus Hagander
Date: Fri, 28 Feb 2025 16:59:03 +0100
Subject: [PATCH] Replace "instantbuy" setting on sponsorship levels with
contract levels
This allows explicit specification of "No contract, Click-through
contract or Full contract", instead of the definitely-hard-to-grok
combination of whether a contract existed and if the checkbox was set or
not.
---
postgresqleu/confsponsor/backendforms.py | 26 ++++++++------
.../migrations/0034_rename_instantbuy.py | 29 +++++++++++++++
postgresqleu/confsponsor/models.py | 17 ++++++++-
postgresqleu/confsponsor/util.py | 3 +-
postgresqleu/confsponsor/views.py | 22 ++++++------
template/confsponsor/admin_dashboard.html | 2 +-
template/confsponsor/admin_sponsor.html | 8 ++---
.../confsponsor/admin_sponsor_details.html | 12 +++----
template/confsponsor/signupform.html | 35 +++++++++++--------
template/confsponsor/sponsor.html | 16 +++++----
10 files changed, 115 insertions(+), 55 deletions(-)
create mode 100644 postgresqleu/confsponsor/migrations/0034_rename_instantbuy.py
diff --git a/postgresqleu/confsponsor/backendforms.py b/postgresqleu/confsponsor/backendforms.py
index 10f945f0..ffa9b959 100644
--- a/postgresqleu/confsponsor/backendforms.py
+++ b/postgresqleu/confsponsor/backendforms.py
@@ -304,7 +304,7 @@ class BackendSponsorshipLevelBenefitCopyForm(django.forms.Form):
class BackendSponsorshipLevelForm(BackendForm):
helplink = 'sponsors#level'
- list_fields = ['levelname', 'levelcost', 'available', 'public', ]
+ list_fields = ['levelname', 'levelcost', 'available', 'public', 'contractlevel']
linked_objects = OrderedDict({
'benefit': BackendSponsorshipLevelBenefitManager(),
})
@@ -315,7 +315,7 @@ class BackendSponsorshipLevelForm(BackendForm):
class Meta:
model = SponsorshipLevel
- fields = ['levelname', 'urlname', 'levelcost', 'available', 'public', 'maxnumber', 'instantbuy',
+ fields = ['levelname', 'urlname', 'levelcost', 'available', 'public', 'maxnumber', 'contractlevel',
'paymentdays', 'paymentdueby', 'paymentmethods', 'invoiceextradescription', 'contract', 'canbuyvoucher', 'canbuydiscountcode']
widgets = {
'paymentmethods': django.forms.CheckboxSelectMultiple,
@@ -330,7 +330,7 @@ class BackendSponsorshipLevelForm(BackendForm):
{
'id': 'contract',
'legend': 'Contract information',
- 'fields': ['instantbuy', 'contract', 'paymentdays', 'paymentdueby'],
+ 'fields': ['contractlevel', 'contract', 'paymentdays', 'paymentdueby'],
},
{
'id': 'payment',
@@ -353,13 +353,19 @@ class BackendSponsorshipLevelForm(BackendForm):
def clean(self):
cleaned_data = super(BackendSponsorshipLevelForm, self).clean()
- if not (cleaned_data.get('instantbuy', False) or cleaned_data['contract']):
- self.add_error('instantbuy', 'Sponsorship level must either be instant signup or have a contract')
- self.add_error('contract', 'Sponsorship level must either be instant signup or have a contract')
-
- if int(cleaned_data['levelcost'] == 0) and cleaned_data.get('instantbuy', False):
- self.add_error('levelcost', 'Sponsorships with zero cost can not be instant signup')
- self.add_error('instantbuy', 'Sponsorships with zero cost can not be instant signup')
+ if cleaned_data['contractlevel'] == 0:
+ if cleaned_data['contract']:
+ self.add_error('contract', 'Contracts cannot be specified when contract level is No contract')
+ if cleaned_data['levelcost'] == 0:
+ self.add_error('levelcost', 'Cost cannot be zero when contract level is No contract')
+ elif cleaned_data['contractlevel'] == 1:
+ if not cleaned_data['contract']:
+ self.add_error('contract', 'Contract is required when contract level is Click-through')
+ if cleaned_data['levelcost'] == 0:
+ self.add_error('levelcost', 'Cost cannot be zero when contract level is Click-through')
+ else:
+ if not cleaned_data['contract']:
+ self.add_error('contract', 'Contract is required when contract level is Full')
return cleaned_data
diff --git a/postgresqleu/confsponsor/migrations/0034_rename_instantbuy.py b/postgresqleu/confsponsor/migrations/0034_rename_instantbuy.py
new file mode 100644
index 00000000..0debf896
--- /dev/null
+++ b/postgresqleu/confsponsor/migrations/0034_rename_instantbuy.py
@@ -0,0 +1,29 @@
+# Generated by Django 4.2.11 on 2025-02-26 20:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('confsponsor', '0033_payment_terms'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='sponsorshiplevel',
+ name='contractlevel',
+ field=models.IntegerField(choices=[(0, 'No contract'), (1, 'Click-through contract'), (2, 'Full contract')], default=0, verbose_name='Contract level'),
+ ),
+ migrations.RunSQL("""
+UPDATE confsponsor_sponsorshiplevel SET contractlevel=CASE
+WHEN instantbuy AND contract_id IS NOT NULL THEN 1
+WHEN instantbuy AND contract_id IS NULL THEN 0
+ELSE 2 END""",
+ """
+UPDATE confsponsor_sponsorshiplevel SET instantbuy=(contractlevel < 2)"""),
+ migrations.RemoveField(
+ model_name='sponsorshiplevel',
+ name='instantbuy',
+ ),
+ ]
diff --git a/postgresqleu/confsponsor/models.py b/postgresqleu/confsponsor/models.py
index 5536c9b2..d6d9b404 100644
--- a/postgresqleu/confsponsor/models.py
+++ b/postgresqleu/confsponsor/models.py
@@ -23,6 +23,13 @@ vat_status_choices = (
(2, 'Company is from outside EU'),
)
+CONTRACT_LEVEL_CHOICES = (
+ (0, 'No contract'),
+ (1, 'Click-through contract'),
+ (2, 'Full contract'),
+)
+CONTRACT_LEVEL_MAP = dict(CONTRACT_LEVEL_CHOICES)
+
class SponsorshipContract(models.Model):
conference = models.ForeignKey(Conference, null=False, blank=False, on_delete=models.CASCADE)
@@ -47,7 +54,8 @@ class SponsorshipLevel(models.Model):
public = models.BooleanField(null=False, blank=False, default=True, verbose_name="Publicly visible",
help_text="If unchecked the sponsorship level will be treated as internal, for example for testing")
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")
+ contractlevel = models.IntegerField(null=False, blank=False, default=0, verbose_name="Contract level",
+ choices=CONTRACT_LEVEL_CHOICES)
paymentmethods = models.ManyToManyField(InvoicePaymentMethod, blank=False, verbose_name="Payment methods for generated invoices")
invoiceextradescription = models.TextField(
blank=True, null=False, verbose_name="Invoice extra description",
@@ -90,6 +98,13 @@ class SponsorshipLevel(models.Model):
return True
return False
+ @cached_property
+ def contractlevel_name(self):
+ return CONTRACT_LEVEL_MAP[self.contractlevel]
+
+ def _display_contractlevel(self, cache):
+ return self.contractlevel_name
+
class SponsorshipBenefit(models.Model):
level = models.ForeignKey(SponsorshipLevel, null=False, blank=False, on_delete=models.CASCADE)
diff --git a/postgresqleu/confsponsor/util.py b/postgresqleu/confsponsor/util.py
index c88d87e1..f379e393 100644
--- a/postgresqleu/confsponsor/util.py
+++ b/postgresqleu/confsponsor/util.py
@@ -53,7 +53,8 @@ ORDER BY max(b.sortkey), a.overview_name""", {
'cost': format_currency(lvl.levelcost),
'available': lvl.available,
'maxnumber': lvl.maxnumber,
- 'instantbuy': lvl.instantbuy,
+ 'instantbuy': lvl.contractlevel == 1 or (lvl.contractlevel == 0 and cost > 0), # legacy
+ 'contractlevel': lvl.contractlevel_name,
'benefits': [dict(_get_benefit_data(b)) for b in lvl.sponsorshipbenefit_set.all()
],
}
diff --git a/postgresqleu/confsponsor/views.py b/postgresqleu/confsponsor/views.py
index 81069648..78a0be03 100644
--- a/postgresqleu/confsponsor/views.py
+++ b/postgresqleu/confsponsor/views.py
@@ -222,7 +222,7 @@ def sponsor_contractview(request, sponsorid):
# Should not happen
raise Http404("No contract at this level")
- if sponsor.level.instantbuy:
+ if sponsor.level.contractlevel == 1:
# Click-through contract
resp = HttpResponse(content_type='application/pdf')
@@ -375,6 +375,9 @@ def _generate_and_send_sponsor_contract(sponsor):
conference = sponsor.conference
level = sponsor.level
+ if level.contractlevel == 0:
+ return
+
pdf = fill_pdf_fields(
level.contract.contractpdf,
get_pdf_fields_for_conference(conference, sponsor),
@@ -386,7 +389,7 @@ def _generate_and_send_sponsor_contract(sponsor):
send_sponsor_manager_email(
sponsor,
'Your contract for {}'.format(conference.conferencename),
- 'confsponsor/mail/{}.txt'.format('sponsor_contract_instant' if level.instantbuy else 'sponsor_contract_manual'),
+ 'confsponsor/mail/{}.txt'.format('sponsor_contract_instant' if level.contractlevel == 1 else 'sponsor_contract_manual'),
{
'conference': conference,
'sponsor': sponsor,
@@ -452,7 +455,7 @@ def sponsor_signup(request, confurlname, levelurlname):
# Stage 2 = contract choice. When submitted, sign up.
# If there is no contract needed on this level, or there is no choice
# of contract because only one available, we bypass stage 1.
- if stage == '1' and (level.instantbuy or not conference.contractprovider or not conference.manualcontracts):
+ if stage == '1' and (level.contractlevel != 2 or not conference.contractprovider or not conference.manualcontracts):
stage = '2'
def _render_contract_choices():
@@ -485,7 +488,6 @@ def sponsor_signup(request, confurlname, levelurlname):
'level': level,
'form': form,
'noform': 1,
- 'needscontract': not (level.instantbuy or not conference.contractprovider),
'sponsorname': form.cleaned_data['name'],
'vatnumber': form.cleaned_data['vatnumber'] if settings.EU_VAT else None,
'previewaddr': get_sponsor_invoice_address(form.cleaned_data['name'],
@@ -502,7 +504,7 @@ def sponsor_signup(request, confurlname, levelurlname):
# If the Continue editing button is selected we should go back
# to just rendering the normal form. Otherwise, go ahead and create the record.
if request.POST.get('submit', '') != 'Continue editing':
- if request.POST.get('contractchoice', '') not in ('0', '1') and not level.instantbuy:
+ if request.POST.get('contractchoice', '') not in ('0', '1') and level.contractlevel == 2:
return _render_contract_choices()
social = {
@@ -520,7 +522,7 @@ def sponsor_signup(request, confurlname, levelurlname):
level=level,
social=social,
invoiceaddr=form.cleaned_data['address'],
- signmethod=1 if request.POST.get('contractchoice', '') == '1' or not conference.contractprovider or level.instantbuy else 0,
+ signmethod=1 if request.POST.get('contractchoice', '') == '1' or not conference.contractprovider or level.contractlevel < 2 else 0,
autoapprovesigned=conference.autocontracts,
)
if settings.EU_VAT:
@@ -534,10 +536,10 @@ def sponsor_signup(request, confurlname, levelurlname):
error = None
- if level.instantbuy:
- if sponsor.level.contract:
- # Instantbuy levels that has a contract should get an implicit contract
- # attached to an email.
+ if level.contractlevel < 2:
+ # No contract or click-through contract
+ if level.contractlevel == 1:
+ # Click-through contract
_generate_and_send_sponsor_contract(sponsor)
mailstr += "Level does not require a signed contract. Verify the details and approve\nthe sponsorship using:\n\n{0}/events/sponsor/admin/{1}/{2}/".format(
diff --git a/template/confsponsor/admin_dashboard.html b/template/confsponsor/admin_dashboard.html
index 3cfbdb84..9c243094 100644
--- a/template/confsponsor/admin_dashboard.html
+++ b/template/confsponsor/admin_dashboard.html
@@ -86,7 +86,7 @@ sponsor manually, you may want to confirm them manually as well...
{%else%}
Invoiced
{%endif%}
- {%elif s.level.instantbuy%}
+ {%elif s.level.contractlevel < 2 %}
Pending organizer verification
{%else%}
{%if s.signmethod == 0 and s.contract %}
diff --git a/template/confsponsor/admin_sponsor.html b/template/confsponsor/admin_sponsor.html
index 21e48f0a..caaa7b59 100644
--- a/template/confsponsor/admin_sponsor.html
+++ b/template/confsponsor/admin_sponsor.html
@@ -140,8 +140,8 @@
This sponsorship is awaiting an invoice to be paid.
-{%if not sponsor.level.instantbuy%}
-{%comment%}Instant buy sponsorships should never be manually confirmed{%endcomment%}
+{%if sponsor.level.contractlevel == 2 %}
+{%comment%}Only full contract sponsorships should be manually confirmed{%endcomment%}
Iff there is a signed and countersigned contract available
for this sponsor, it can be confirmed before the invoice is paid.
@@ -161,7 +161,7 @@ for this sponsor, it can be confirmed before the invoice is paid.
{%else%}
{%comment%}Sponsor has no invoice{%endcomment%}
{%if sponsor.level.levelcost %}
-{%if sponsor.level.instantbuy%}
+{%if sponsor.level.contractlevel < 2%}
This sponsorship has not yet been issued an invoice. This is an
"instant buy" level sponsorship, so as soon as the sponsorship
@@ -191,7 +191,7 @@ This sponsorship has not yet been issued an invoice. Once the contract
has been received, go ahead and generate the invoice.
This sponsorship has zero cost, which means payment is handled manually.
diff --git a/template/confsponsor/admin_sponsor_details.html b/template/confsponsor/admin_sponsor_details.html
index 371c3148..dac8b191 100644
--- a/template/confsponsor/admin_sponsor_details.html
+++ b/template/confsponsor/admin_sponsor_details.html
@@ -39,13 +39,11 @@
{%if sponsor.invoice%}
diff --git a/template/confsponsor/signupform.html b/template/confsponsor/signupform.html
index 534e5584..689e4d1b 100644
--- a/template/confsponsor/signupform.html
+++ b/template/confsponsor/signupform.html
@@ -7,14 +7,6 @@ Thank you for your interest in sponsoring {{conference}}! Please fill out the
form below to initiate your sponsorship!
-{%if level.contract%}
-
-Before you complete the signup form, please make sure you have read the
-contract, and
-agree with the contents in it.
-
-{%endif%}
-
{%if form.errors%}
NOTE! Your submitted form contained errors and has not been saved!
@@ -46,16 +38,31 @@ the invoice cannot be changed.
{%if previewaddr%}
-{%if needscontract%}
+{%if level.contractlevel > 0%}
Contract details
+{% if level.contractlevel == 1 %}
+
+ This sponsorship level uses a click-through contract. This
+ means that by signing up, you agree to the
+ contract, and
+ accept that no changes can be made to it.
+
+{% else %}
+
+ This sponsorship level requires a signed contract. Please review the
+ contract before
+ completing the sign-up, and only continue if you agree with it.
+ Once signed up, you will receive a contract or signing.
+
+{% endif %}
The contract for the sponsorship will be issued to the company name
{{sponsorname}}{%if vatnumber%} with VAT
number {{vatnumber}}{%endif%}.
-
-
- It will not be possible to change this after this
- step, so if anything about it is incorrect please click Continue
+
+ It will not be possible to change this, or any other
+ details in the contract, after this
+ step. If anything about it is incorrect please click Continue
editing and correct it before proceeding.
{%endif%}
@@ -92,7 +99,7 @@ the invoice cannot be changed.
{%endif%}{# contractchoices #}
-{%if not level.instantbuy and not noform %}
+{%if level.contractlevle == 2 and not noform %}
Please note that due to the level of this sponsorship contract, we
will require a signed contract, apart from the confirmation
diff --git a/template/confsponsor/sponsor.html b/template/confsponsor/sponsor.html
index da8f878e..fd81d3e3 100644
--- a/template/confsponsor/sponsor.html
+++ b/template/confsponsor/sponsor.html
@@ -34,20 +34,22 @@
-{% if sponsor.confirmed and sponsor.level.contract %}
+{% if sponsor.confirmed %}
Contract:
-{% if sponsor.level.instantbuy %}
+{% if sponsor.level.contractlevel == 0 %}
+This level requires no contract.
+{% elif sponsor.level.contractlevel == 1 %}
Click-through contract agreed to. View copy of contract
{% else %}
{%if sponsor.signmethod == 0%}
Digital contract completed {{sponsor.contract.completed}}.
{%if sponsor.contract.completed and sponsor.contract.has_completed_pdf %}View signed contract{%endif%}
{% else %}
-Manual contract.
-{% endif %}
-{% endif %}
+Manual contract signed.
+{% endif %}{%comment%}digital contract/manual contract{%endcomment%}
+{% endif %}{%comment%}contractlevel{%endcomment%}
{% endif %}
@@ -73,8 +75,8 @@ Manual contract.
{%endwith%}
{%else%}
{%comment%}No invoice generated{%endcomment%}
-{%if sponsor.level.instantbuy%}
-{%comment%}No invoice generated but instantbuy active, so awaiting admin approval{%endcomment%}
+{%if sponsor.level.contractlevel < 2 %}
+{%comment%}No invoice generated but clickthrough contract or no contract, so awaiting admin approval{%endcomment%}
Your sponsorship request has been submitted, and is currently awaiting confirmation
from the conference organizers. As soon as it has been, an invoice will be automatically
--
2.39.5