8000 Update benefits validation rules (#2120) · python/pythondotorg@ae08791 · GitHub
[go: up one dir, main page]

Skip to content

Commit ae08791

Browse files
berinhardewdurbin
andauthored
Update benefits validation rules (#2120)
* Add flag to control if package allows a la carte benefits * Applications for standalone benefits cannot have packages * Don't validate a la carte application with a disabled a la carte package * Don't display Potential a la carte option if package disabled it * Disable standalone inputs if package was selected * Fix breaking tests which have standalone benefits with packages and a la carte ones * Disable a la carte benefits if package disables it * Force a la carte and standalone cards positions to the start of the div * Disable a la carte benefits when form is empty * Add title to inputs to provide more information about the disabled inputs * ensure state for sold-out a la carte benefits is rendered in UI * un-select any a la carte when selecting a package that doesn't allow them * add notes to a la carte and standalone instructions to clarify why options are enabled/disabled * Initial load of benefits state on the page load. * Display clear form button at the bottom of the form * Add JS code to clear form and reset application form to its initial state Co-authored-by: Ee Durbin <ewdurbin@gmail.com>
1 parent 9f967f2 commit ae08791

File tree

8 files changed

+188
-23
lines changed

8 files changed

+188
-23
lines changed

sponsors/admin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ def update_related_sponsorships(self, *args, **kwargs):
173173
@admin.register(SponsorshipPackage)
174174
class SponsorshipPackageAdmin(OrderedModelAdmin):
175175
ordering = ("-year", "order",)
176-
list_display = ["name", "year", "advertise", "move_up_down_links"]
177-
list_filter = ["advertise", "year"]
176+
list_display = ["name", "year", "advertise", "allow_a_la_carte", "move_up_down_links"]
177+
list_filter = ["advertise", "year", "allow_a_la_carte"]
178178
search_fields = ["name"]
179179

180180
def get_readonly_fields(self, request, obj=None):

sponsors/forms.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ def _clean_benefits(self, cleaned_data):
141141
"""
142142
package = cleaned_data.get("package")
143143
benefits = self.get_benefits(cleaned_data, include_a_la_carte=True)
144+
a_la_carte = cleaned_data.get("a_la_carte_benefits")
144145
standalone = cleaned_data.get("standalone_benefits")
145146

146147
if not benefits and not standalone:
@@ -151,6 +152,14 @@ def _clean_benefits(self, cleaned_data):
151152
raise forms.ValidationError(
152153
_("You must pick a package to include the selected benefits.")
153154
)
155+
elif standalone and package:
156+
raise forms.ValidationError(
157+
_("Application with package cannot have standalone benefits.")
158+
)
159+
elif package and a_la_carte and not package.allow_a_la_carte:
160+
raise forms.ValidationError(
161+
_("Package does not accept a la carte benefits.")
162+
)
154163

155164
benefits_ids = [b.id for b in benefits]
156165
for benefit in benefits:
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.2.24 on 2022-08-13 10:51
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('sponsors', '0090_auto_20220812_1314'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='sponsorshippackage',
15+
name='allow_a_la_carte',
16+
field=models.BooleanField(default=True, help_text='If disabled, a la carte benefits will be disabled in application form'),
17+
),
18+
]

sponsors/models/sponsorship.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ class SponsorshipPackage(OrderedModel):
5050
"to reference this package.")
5151
year = models.PositiveIntegerField(null=True, validators=YEAR_VALIDATORS, db_index=True)
5252

53+
allow_a_la_carte = models.BooleanField(
54+
default=True, help_text="If disabled, a la carte benefits will be disabled in application form"
55+
)
56+
5357
def __str__(self):
5458
return f'{self.name} ({self.year})'
5559

sponsors/tests/test_forms.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,56 @@ def test_validate_form_without_package_but_with_standalone_benefits(self):
145145
self.assertEqual([], form.get_benefits())
146146
self.assertEqual([benefit], form.get_benefits(include_standalone=True))
147147

148-
def test_should_not_validate_form_without_package_with_a_la_carte_and_standalone_benefits(self):
148+
def test_do_not_validate_form_with_package_and_standalone_benefits(self):
149+
benefit = self.standalone[0]
150+
data = {
151+
"standalone_benefits": [benefit.id],
152+
"package": self.package.id,
153+
"benefits_psf": [self.program_1_benefits[0].id],
154+
}
155+
form = SponsorshipsBenefitsForm(data=data)
156+
self.assertFalse(form.is_valid())
157+
self.assertIn(
158+
"Application with package cannot have standalone benefits.",
159+
form.errors["__all__"]
160+
)
161+
162+
def test_should_not_validate_form_without_package_with_a_la_carte_benefits(self):
149163
data = {
150-
"standalone_benefits": [self.standalone[0]],
151-
"a_la_carte_benefits": [self.a_la_carte[0]],
164+
"a_la_carte_benefits": [self.a_la_carte[0].id],
152165
}
153166

154167
form = SponsorshipsBenefitsForm(data=data)
155168

156169
self.assertFalse(form.is_valid())
170+
self.assertIn(
171+
"You must pick a package to include the selected benefits.",
172+
form.errors["__all__"]
173+
)
174+
175+
data.update({
176+
"package": self.package.id,
177+
})
178+
form = SponsorshipsBenefitsForm(data=data)
179+
self.assertTrue(form.is_valid())
180+
181+
def test_do_not_validate_package_package_with_disabled_a_la_carte_benefits(self):
182+
self.package.allow_a_la_carte = False
183+
self.package.save()
184+
data = {
185+
"package": self.package.id,
186+
"benefits_psf": [self.program_1_benefits[0].id],
187+
"a_la_carte_benefits": [self.a_la_carte[0].id],
188+
}
189+
form = SponsorshipsBenefitsForm(data=data)
190+
self.assertFalse(form.is_valid())
191+
self.assertIn(
192+
"Package does not accept a la carte benefits.",
193+
form.errors["__all__"]
194+
)
195+
data.pop("a_la_carte_benefits")
196+
form = SponsorshipsBenefitsForm(data=data)
197+
self.assertTrue(form.is_valid(), form.errors)
157198

158199
def test_benefits_conflicts_helper_property(self):
159200
benefit_1, benefit_2 = baker.make("sponsors.SponsorshipBenefit", _quantity=2)

sponsors/tests/test_views.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def setUp(self):
5757
"benefits_psf": [b.id for b in self.program_1_benefits],
5858
"benefits_working_group": [b.id for b in self.program_2_benefits],
5959
"a_la_carte_benefits": [b.id for b in self.a_la_carte_benefits],
60-
"standalone_benefits": [b.id for b in self.standalone_benefits],
60+
"standalone_benefits": [],
6161
"package": self.package.id,
6262
}
6363

@@ -150,6 +150,7 @@ def test_valid_only_with_standalone(self):
150150
self.data["benefits_working_group"] = []
151151
self.data["a_la_carte_benefits"] = []
152152
self.data["package"] = ""
153+
self.data["standalone_benefits"] = [b.id for b in self.standalone_benefits]
153154

154155
response = self.client.post(self.url, data=self.data)
155156

@@ -218,7 +219,6 @@ def setUp(self):
218219
"package": self.package.id,
219220
"benefits_psf": [b.id for b in self.program_1_benefits],
220221
"a_la_carte_benefits": [self.a_la_carte.id],
221-
"standalone_benefits": [self.standalone.id],
222222
}
223223
)
224224
self.data = {
@@ -349,8 +349,8 @@ def test_create_new_sponsorship(self):
349349
)
350350
sponsorship = Sponsorship.objects.get(sponsor__name="CompanyX")
351351
self.assertTrue(sponsorship.benefits.exists())
352-
# 3 benefits + 1 a-la-carte + 1 standalone
353-
self.assertEqual(5, sponsorship.benefits.count())
352+
# 3 benefits + 1 a-la-carte + 0 standalone
353+
self.assertEqual(4, sponsorship.benefits.count())
354354
self.assertTrue(sponsorship.level_name)
355355
self.assertTrue(sponsorship.submited_by, self.user)
356356
self.assertEqual(

static/js/sponsors/applicationForm.js

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,76 @@ $(document).ready(function(){
99
getBenefitInput: function(benefitId) { return SELECTORS.benefitsInputs().filter('[value=' + benefitId + ']'); },
1010
getSelectedBenefits: function() { return SELECTORS.benefitsInputs().filter(":checked"); },
1111
tickImages: function() { return $(`.benefit-within-package img`) },
12-
sectionToggleBtns: function() { return $(".toggle_btn")}
12+
sectionToggleBtns: function() { return $(".toggle_btn")},
13+
aLaCarteInputs: function() { return $("input[name=a_la_carte_benefits]"); },
14+
standaloneInputs: function() { return $("input[name=standalone_benefits]"); },
15+
aLaCarteMessage: function() { return $("#a-la-cart-benefits-disallowed"); },
16+
standaloneMessage: function() { return $("#standalone-benefits-disallowed"); },
17+
clearFormButton: function() { return $("#clear_form_btn"); },
18+
applicationForm: function() { return $("#application_form"); },
1319
}
1420

15-
const initialPackage = $("input[name=package]:checked").val();
16-
if (initialPackage && initialPackage.length > 0) mobileUpdate(initialPackage);
21+
const pkgInputs = $("input[name=package]:checked");
22+
if (pkgInputs.length > 0 && pkgInputs.val()) {
23+
24+
// Disable A La Carte inputs based on initial package value
25+
if (pkgInputs.attr("allow_a_la_carte") !== "true"){
26+
let msg = "Cannot add a la carte benefit with the selected package.";
27+
SELECTORS.aLaCarteInputs().attr("title", msg);
28+
SELECTORS.aLaCarteMessage().removeClass("hidden");
29+
SELECTORS.aLaCarteInputs().prop("checked", false);
30+
SELECTORS.aLaCarteInputs().prop("disabled", true);
31+
32+
}
33+
34+
// Disable Standalone benefits inputs
35+
let msg ="Cannot apply for standalone benefit with the selected package.";
36+
SELECTORS.standaloneInputs().prop("checked", false);
37+
SELECTORS.standaloneInputs().prop("disabled", true);
38+
SELECTORS.standaloneMessage().removeClass("hidden");
39+
SELECTORS.standaloneInputs().attr("title", msg);
40+
41+
// Update mobile selection
42+
mobileUpdate(pkgInputs.val());
43+
} else {
44+
// disable a la carte if no package selected at the first step
45+
SELECTORS.aLaCarteInputs().prop("disabled", true);
46+
}
1747

1848
SELECTORS.sectionToggleBtns().click(function(){
1949
const section = $(this).data('section');
2050
const className = ".section-" + section + "-content";
2151
$(className).toggle();
2252
});
2353

54+
SELECTORS.clearFormButton().click(function(){
55+
SELECTORS.aLaCarteInputs().prop('checked', false).prop('selected', false);
56+
SELECTORS.benefitsInputs().prop('checked', false).prop('selected', false);
57+
SELECTORS.packageInput().prop('checked', false).prop('selected', false);
58+
SELECTORS.standaloneInputs()
59+
.prop('checked', false).prop('selected', false).prop("disabled", false);
60+
61+
SELECTORS.tickImages().each((i, img) => {
62+
const initImg = img.getAttribute('data-initial-state');
63+
const src = img.getAttribute('src');
64+
65+
if (src !== initImg) {
66+
img.setAttribute('data-next-state', src);
67+
}
68+
69+
img.setAttribute('src', initImg);
70+
});
71+
$(".selected").removeClass("selected");
72+
$('.custom-fee').hide();
73+
});
74+
2475
SELECTORS.packageInput().click(function(){
2576
let package = this.value;
26-
if (package.length == 0) return;
77+
if (package.length == 0) {
78+
SELECTORS.standaloneInputs().prop("disabled", false);
79+
SELECTORS.standaloneMessage().addClass("hidden");
80+
return;
81+
}
2782

2883
// clear previous customizations
2984
SELECTORS.tickImages().each((i, img) => {
@@ -48,6 +103,25 @@ $(document).ready(function(){
48103
$(`.package-${package}-benefit`).addClass("selected");
49104
$(`.package-${package}-benefit input`).prop("disabled", false);
50105

106+
let msg ="Cannot apply for standalone benefit with the selected package.";
107+
SELECTORS.standaloneInputs().prop("checked", false);
108+
SELECTORS.standaloneInputs().prop("disabled", true);
109+
SELECTORS.standaloneMessage().removeClass("hidden");
110+
SELECTORS.standaloneInputs().attr("title", msg);
111+
112+
// Disable a la carte benefits if package disables it
113+
if ($(this).attr("allow_a_la_carte") !== "true") {
114+
msg ="Cannot add a la carte benefit with the selected package.";
115+
SELECTORS.aLaCarteInputs().attr("title", msg);
116+
SELECTORS.aLaCarteMessage().removeClass("hidden");
117+
SELECTORS.aLaCarteInputs().prop("checked", false);
118+
SELECTORS.aLaCarteInputs().prop("disabled", true);
119+
} else {
120+
SELECTORS.aLaCarteInputs().attr("title", "");
121+
SELECTORS.aLaCarteMessage().addClass("hidden");
122+
SELECTORS.aLaCarteInputs().not('.soldout').prop("disabled", false);
123+
}
124+
51125
// populate hidden inputs according to package's benefits
52126
SELECTORS.getPackageBenefits(package).each(function(){
53127
let benefit = $(this).html();

templates/sponsors/sponsorship_benefits_form.html

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,25 @@ <h4 class="col">Select a Sponsorship Package</h4>
5353
{% endif %}
5454
</span>
5555
{% for package in form.fields.package.queryset %}
56-
<div id="pkg_container_{{ package.id }}" class="col col-items package-input {% if package.id == form.initial.package %}selected{% endif %}" data-package-id="{{ package.id}}" onclick="updatePackageInput({{forloop.counter0}})">
56+
<div
57+
id="pkg_container_{{ package.id }}"
58+
class="col col-items package-input
59+
{% if package.id == form.initial.package %}selected{% endif %}"
60+
data-package-id="{{ package.id}}"
61+
onclick="updatePackageInput({{forloop.counter0}})"
62+
{% if not package.allow_a_la_carte %}title="This package does not accept customization with a la carte benefits."{% endif %}
63+
>
5764
<h4>{{ package.name|upper }}</h4>
5865
<span class="package-price">${{ package.sponsorship_amount|intcomma }}<span class="custom-fee-{{ package.id }} custom-fee">*</span></span>
59-
<input type="radio" name="package" value="{{ package.id }}" id="id_package_{{ forloop.counter0 }}" {% if package.id == form.initial.package %}checked="checked"{% endif %} data-pos={{ forloop.counter0 }} />
66+
<input
67+
type="radio"
68+
name="package"
69+
value="{{ package.id }}"
70+
id="id_package_{{ forloop.counter0 }}"
71+
{% if package.id == form.initial.package %}checked="checked"{% endif %}
72+
data-pos={{ forloop.counter0 }}
73+
{% if package.allow_a_la_carte %}allow_a_la_carte="true"{% endif %}
74+
/>
6075
<span class="custom-fee-{{ package.id }} custom-fee">* Subject to change</span>
6176
</div>
6277
{% endfor %}
@@ -101,7 +116,7 @@ <h4 class="benefit-title">{{ benefit.name }}</h4>
101116
{% endif %}
102117
data-initial-state="{% static 'img/sponsors/tick.svg' %}"
103118
/>
104-
{% elif not benefit.package_only %}
119+
{% elif not benefit.package_only and package.allow_a_la_carte%}
105120
<img
106121
id="benefit-{{ benefit.id }}-package-{{ package.id }}"
107122
onclick="benefitUpdate({{ benefit.id }}, {{ package.id }})"
@@ -144,15 +159,16 @@ <h4 class="benefit-title">{{ benefit.name }}</h4>
144159
<img class="col" src='{% static "img/sponsors/title-2.svg" %}'/>
145160
<div class="col with-description">
146161
<h4>Select a la carte benefits</h4>
147-
<span>Available at any level of sponsorship</span>
162+
<div>Available to add to sponsorship packages.</div>
163+
<div id="a-la-cart-benefits-disallowed" class="hidden"><b>Selected sponsorship package does not allow a la carte benefit additions.</b></div>
148164
</div>
149165
</div>
150166

151167
<div id="a-la-carte-benefits" class="custom-benefits">
152-
<div class="row">
168+
<div class="row" style="justify-content: start;">
153169
{% for benefit in form.fields.a_la_carte_benefits.queryset %}
154170
<div class="col col-items">
155-
<input type="checkbox" name="a_la_carte_benefits" value="{{ benefit.id }}" {% if benefit.id in form.initial.a_la_carte_benefits|default:"" %}checked{% endif %}/>
171+
<input type="checkbox" name="a_la_carte_benefits" {% if benefit.unavailability_message and benefit.id not in field.initial or not benefit.has_capacity %}disabled{% endif %} class="{% if benefit.unavailability_message or not benefit.has_capacity %}soldout{% endif %}" value="{{ benefit.id }}" {% if benefit.id in form.initial.a_la_carte_benefits|default:"" %}checked{% endif %}/>
156172
<div class="a-la-carte-description">
157173
<span class="benefit-program">{{ benefit.program }}</span> -
158174
<span class="benefit-title">{{ benefit.name }}</span>
@@ -161,7 +177,7 @@ <h4>Select a la carte benefits</h4>
161177
</div>
162178
{% if forloop.counter|divisibleby:4 %}
163179
</div>
164-
<div class="row">
180+
<div class="row" style="justify-content: start;">
165181
{% endif %}
166182
{% endfor %}
167183
</div>
@@ -178,11 +194,12 @@ <h4>Select a la carte benefits</h4>
178194
<div class="col with-description">
179195
<h4>Select standalone benefits</h4>
180196
<span>Available to be selected without package</span>
197+
<div id="standalone-benefits-disallowed" class="hidden"><b>Standalone benefits are not available when selecting a package.</b></div>
181198
</div>
182199
</div>
183200

184201
<div id="standalone-benefits" class="custom-benefits">
185-
<div class="row">
202+
<div class="row" style="justify-content: start;">
186203
{% for benefit in form.fields.standalone_benefits.queryset %}
187204
<div class="col col-items">
188205
<input type="checkbox" name="standalone_benefits" value="{{ benefit.id }}" {% if benefit.id in form.initial.standalone_benefits|default:"" %}checked{% endif %}/>
@@ -218,9 +235,11 @@ <h4 class="col">Submit your contact information</h4>
218235
<li><p>Submit your contact information</p></li>
219236
</ol>
220237
<div>
221-
<input class="btn btn-sponsorship-submit" type="submit" value="Submit" {% if custom_year %}disabled{% endif %}>
238+
<label>
239+
<input class="btn btn-sponsorship-submit" type="submit" value="Submit" {% if custom_year %}disabled{% endif %}/>
240+
| <a id="clear_form_btn" href="#package-selection">Clear form</a>
241+
</label>
222242
</div>
223-
<span></span>
224243
</div>
225244

226245
<div class="row section-title">

0 commit comments

Comments
 (0)
0