Skip to content

Commit cbea9c8

Browse files
authored
Deprecate splash info module (#3480)
* Move sibling discount and lunch choices to extracosts * Move sibling discount on dashboard * Allow sibling discount to be reverted, fix form * Remove splash info module * Remove unused import * Fix stripe module * Fix for accounting module view * Fix hard coded admission and discount in form * Add sibling discount to tests, fix form and program setup * Fix deletion of admission and payment (whoops) * Fix migration
1 parent ff1c901 commit cbea9c8

File tree

21 files changed

+223
-460
lines changed

21 files changed

+223
-460
lines changed

esp/esp/accounting/controllers.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class ProgramAccountingController(BaseAccountingController):
7575
def __init__(self, program, *args, **kwargs):
7676
self.program = program
7777
self.finaid_items = ['Financial aid grant', 'Sibling discount']
78+
self.admission_items = ["Program admission", "Student payment"]
7879

7980
def clear_all_data(self):
8081
# Clear all financial data for the program
@@ -126,12 +127,6 @@ def setup_lineitemtypes(self, base_cost, optional_items=None, select_items=None)
126127
)
127128
result.append(lit_finaid)
128129

129-
(lit_sibling, created) = LineItemType.objects.get_or_create(
130-
text='Sibling discount',
131-
program=program
132-
)
133-
result.append(lit_sibling)
134-
135130
for item in optional_items:
136131
(lit_optional, created) = LineItemType.objects.get_or_create(
137132
text=item[0],
@@ -172,20 +167,23 @@ def default_finaid_lineitemtype(self):
172167
def default_siblingdiscount_lineitemtype(self):
173168
return LineItemType.objects.filter(program=self.program, text='Sibling discount').order_by('-id')[0]
174169

170+
def default_admission_lineitemtype(self):
171+
return LineItemType.objects.filter(program=self.program, text='Program admission').order_by('-id')[0]
172+
175173
def get_lineitemtypes_Q(self, required_only=False, optional_only=False, payment_only=False, lineitemtype_id=None):
176174
if lineitemtype_id:
177175
return Q(id=lineitemtype_id)
178176
q_object = Q(program=self.program) & ~Q(text__in=self.finaid_items) # exclude finaid grants and sibling discounts
177+
# The Stripe module (or, if used, donation module) currently takes care of the donation
178+
# optional line item, so ignore it in the optional costs module.
179+
for module_name in ['CreditCardModule_Stripe', 'DonationModule']:
180+
other_module = self.program.getModule(module_name)
181+
if other_module and other_module.get_setting('offer_donation', default=True):
182+
q_object &= ~Q(text=other_module.get_setting('donation_text'))
179183
if required_only:
180184
q_object &= Q(required=True, for_payments=False)
181185
elif optional_only:
182186
q_object &= Q(required=False, for_payments=False)
183-
# The Stripe module (or, if used, donation module) currently takes care of the donation
184-
# optional line item, so ignore it in the optional costs module.
185-
for module_name in ['CreditCardModule_Stripe', 'DonationModule']:
186-
other_module = self.program.getModule(module_name)
187-
if other_module and other_module.get_setting('offer_donation', default=True):
188-
q_object &= ~Q(text=other_module.get_setting('donation_text'))
189187
elif payment_only:
190188
q_object &= Q(required=False, for_payments=True)
191189
return q_object
@@ -300,15 +298,15 @@ def ensure_required_transfers(self):
300298

301299
def apply_preferences(self, optional_items):
302300
""" Function to ensure there are transfers for this user corresponding
303-
to optional line item types, accoring to their preferences.
301+
to optional line item types, according to their preferences.
304302
optional_items is a list of 4-tuples: (item name, quantity, cost, option ID)
305303
The last 2 items, cost and option ID, are non-required and should
306304
be set to None if unused. """
307305

308306
result = []
309307
program_account = self.default_program_account()
310308
source_account = self.default_source_account()
311-
line_items = self.get_lineitemtypes(optional_only=True)
309+
line_items = self.get_lineitemtypes().exclude(text__in=self.admission_items)
312310

313311
# Clear existing transfers
314312
Transfer.objects.filter(user=self.user, line_item__in=line_items).delete()
@@ -334,7 +332,7 @@ def apply_preferences(self, optional_items):
334332
option = LineItemOptions.objects.get(id=option_id)
335333
if cost is None:
336334
transfer_amount = option.amount_dec_inherited
337-
for i in range(quantity):
335+
for i in range(quantity or 0):
338336
result.append(Transfer.objects.create(source=source_account, destination=program_account, user=self.user, line_item=lit, amount_dec=transfer_amount, option=option))
339337
break
340338
if not matched:
@@ -380,7 +378,7 @@ def get_transfers(self, line_items=None, **kwargs):
380378
def get_preferences(self, line_items=None):
381379
# Return a list of 4-tuples: (item name, quantity, cost, options)
382380
result = []
383-
transfers = self.get_transfers(line_items, optional_only=True)
381+
transfers = self.get_transfers(line_items)
384382
for transfer in transfers:
385383
li_name = transfer.line_item.text
386384
if (li_name, transfer.amount_dec, transfer.option_id) not in map(lambda x: (x[0], x[2], x[3]), result):
@@ -547,7 +545,7 @@ def amount_finaid(self, amount_siblingdiscount=None):
547545
return aid_amount
548546

549547
def amount_siblingdiscount(self):
550-
if (not self.program.sibling_discount) or self.program.splashinfo_objects.get(self.user.id):
548+
if self.program.sibling_discount and SplashInfo.getForUser(self.user, self.program).siblingdiscount:
551549
return self.program.sibling_discount
552550
else:
553551
return Decimal('0')

esp/esp/accounting/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def user_accounting(user, progs = []):
8383
for t in iac.get_transfers().select_related('line_item')
8484
]
8585
sort_order = {"Cost (required)": 0, "Cost (optional)": 1, "Sibling discount": 2, "Financial aid": 3, "Payment": 4}
86+
classified_transfers.sort(key=lambda t: 'Program admission' not in t['transfer'].line_item.text) # put Program admission at the top
8687
classified_transfers.sort(key=lambda t: sort_order[t['type']])
8788
result = {
8889
'program': prog,

esp/esp/program/admin.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,12 @@ class Admin_SplashInfo(admin.ModelAdmin):
134134
list_display = (
135135
'student',
136136
'program',
137+
'siblingdiscount',
138+
'siblingname',
139+
'submitted'
137140
)
138-
search_fields = default_user_search('student')
139-
list_filter = [ 'program', ]
141+
search_fields = default_user_search('student') + ['siblingname']
142+
list_filter = [ 'program', 'siblingdiscount', 'submitted']
140143
admin_site.register(SplashInfo, Admin_SplashInfo)
141144

142145
## Schedule stuff (wish it was schedule_.py)

esp/esp/program/models/__init__.py

Lines changed: 21 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,15 +1285,24 @@ def _sibling_discount_get(self):
12851285
return self._sibling_discount
12861286

12871287
def _sibling_discount_set(self, value):
1288+
from esp.accounting.models import LineItemType
12881289
if value is not None:
12891290
self._sibling_discount = Decimal(value)
12901291
Tag.setTag('sibling_discount', target=self, value=self._sibling_discount)
1292+
LineItemType.objects.get_or_create(text='Sibling discount', program=self, amount_dec = self._sibling_discount)
12911293
else:
12921294
self._sibling_discount = Decimal('0.00')
12931295
Tag.objects.filter(key='sibling_discount', object_id=self.id).delete()
1296+
LineItemType.objects.filter(text='Sibling discount', program=self).delete()
12941297

12951298
sibling_discount = property(_sibling_discount_get, _sibling_discount_set)
12961299

1300+
def base_cost(self):
1301+
from esp.accounting.controllers import ProgramAccountingController
1302+
pac = ProgramAccountingController(self)
1303+
return pac.default_admission_lineitemtype().amount_dec
1304+
base_cost = property(base_cost)
1305+
12971306
@property
12981307
def splashinfo_objects(self):
12991308
"""
@@ -1317,8 +1326,8 @@ class SplashInfo(models.Model):
13171326
student = AjaxForeignKey(ESPUser)
13181327
# Program field may be empty for backwards compatibility with Stanford data
13191328
program = AjaxForeignKey(Program, null=True)
1320-
lunchsat = models.CharField(max_length=32, blank=True, null=True)
1321-
lunchsun = models.CharField(max_length=32, blank=True, null=True)
1329+
lunchsat = models.CharField(max_length=32, blank=True, null=True) # No longer used, kept for backwards compatibility
1330+
lunchsun = models.CharField(max_length=32, blank=True, null=True) # No longer used, kept for backwards compatibility
13221331
siblingdiscount = models.NullBooleanField(default=False, blank=True)
13231332
siblingname = models.CharField(max_length=64, blank=True, null=True)
13241333
submitted = models.NullBooleanField(default=False, blank=True)
@@ -1351,62 +1360,22 @@ def getForUser(user, program=None):
13511360
n.save()
13521361
return n
13531362

1354-
def pretty_version(self, attr_name):
1355-
# Look up choices
1356-
tag_data = Tag.getProgramTag('splashinfo_choices', self.program)
1357-
1358-
# Check for matching item in list of choices
1359-
if tag_data:
1360-
tag_struct = json.loads(tag_data)
1361-
for item in tag_struct[attr_name]:
1362-
if item[0] == getattr(self, attr_name):
1363-
return item[1].decode('utf-8')
1364-
1365-
return u'N/A'
1366-
1367-
def pretty_satlunch(self):
1368-
return self.pretty_version('lunchsat')
1369-
1370-
def pretty_sunlunch(self):
1371-
return self.pretty_version('lunchsun')
1372-
13731363
def execute_sibling_discount(self):
1364+
from esp.accounting.controllers import IndividualAccountingController
1365+
from esp.accounting.models import Transfer
1366+
iac = IndividualAccountingController(self.program, self.student)
1367+
line_item_type = iac.default_siblingdiscount_lineitemtype()
1368+
source_account = iac.default_finaid_account()
1369+
dest_account = iac.default_source_account()
13741370
if self.siblingdiscount:
1375-
from esp.accounting.controllers import IndividualAccountingController
1376-
from esp.accounting.models import Transfer
1377-
iac = IndividualAccountingController(self.program, self.student)
1378-
source_account = iac.default_finaid_account()
1379-
dest_account = iac.default_source_account()
1380-
line_item_type = iac.default_siblingdiscount_lineitemtype()
1381-
transfer, created = Transfer.objects.get_or_create(source=source_account, destination=dest_account, user=self.student, line_item=line_item_type, amount_dec=Decimal('20.00'))
1371+
transfer, created = Transfer.objects.get_or_create(source=source_account, destination=dest_account, user=self.student, line_item=line_item_type, amount_dec=self.program.sibling_discount)
13821372
return transfer
1373+
else:
1374+
Transfer.objects.filter(source=source_account, destination=dest_account, user=self.student, line_item=line_item_type).delete()
13831375

13841376
def save(self):
1385-
from esp.accounting.controllers import IndividualAccountingController
1386-
1387-
# We have two things to put in: "Saturday Lunch" and "Sunday Lunch".
1388-
# If they are not there, they will be created. These names are hard coded.
1389-
from esp.accounting.models import LineItemType
1390-
LineItemType.objects.get_or_create(program=self.program, text='Saturday Lunch')
1391-
LineItemType.objects.get_or_create(program=self.program, text='Sunday Lunch')
1392-
1393-
# Figure out how much everything costs
1394-
cost_info = json.loads(Tag.getProgramTag('splashinfo_costs', self.program))
1395-
1396-
# Save accounting information
1397-
iac = IndividualAccountingController(self.program, self.student)
1398-
1399-
if not self.lunchsat or self.lunchsat == 'no':
1400-
iac.set_preference('Saturday Lunch', 0)
1401-
elif 'lunchsat' in cost_info:
1402-
iac.set_preference('Saturday Lunch', 1, cost_info['lunchsat'][self.lunchsat])
1403-
1404-
if not self.lunchsun or self.lunchsun == 'no':
1405-
iac.set_preference('Sunday Lunch', 0)
1406-
elif 'lunchsun' in cost_info:
1407-
iac.set_preference('Sunday Lunch', 1, cost_info['lunchsun'][self.lunchsun])
1408-
14091377
super(SplashInfo, self).save()
1378+
self.execute_sibling_discount()
14101379

14111380

14121381
class RegistrationProfile(models.Model):

esp/esp/program/modules/forms/lineitems.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from esp.accounting.models import LineItemType, LineItemOptions
55
from esp.program.models import Program
66

7-
exclude_line_items = ["Sibling discount", "Program admission", "Financial aid grant", "Student payment", "Donation to Learning Unlimited", "Saturday Lunch", "Sunday Lunch"]
7+
exclude_line_items = ["Sibling discount", "Program admission", "Financial aid grant", "Student payment", "Donation to Learning Unlimited"]
88

99
class LineItemForm(forms.ModelForm):
1010
text = forms.CharField(label = 'Name', widget = forms.TextInput(attrs={'placeholder': '(name)'}), validators=[RegexValidator(regex = '^(' + '|'.join(exclude_line_items) + ')$', message = 'That line item name is reserved for internal operations. Please choose another name.', inverse_match = True)])

esp/esp/program/modules/forms/splashinfo.py

Lines changed: 19 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -33,85 +33,39 @@
3333
"""
3434

3535
from django import forms
36-
from esp.middleware import ESPError
37-
from esp.tagdict.models import Tag
38-
import json
36+
from esp.program.models import Program
3937

40-
"""
41-
The SplashInfoForm is customizable for different lunch options.
42-
The choices should be provided in a Tag, splashinfo_choices, which is in
43-
JSON format. Example:
44-
key = 'splashinfo_choices'
45-
value = '{
46-
"lunchsat": [["no", "No thanks; I will bring my own lunch"],
47-
["chicken", "Yes, Chicken"],
48-
["steak", "Yes, Steak"],
49-
["spicy_thai", "Yes, Spicy Thai"],
50-
["veggie", "Yes, Vegetarian"]],
51-
"lunchsun": [["no", "No thanks; I will bring my own lunch"],
52-
["cheese", "Yes, Cheese Pizza"],
53-
["pepperoni", "Yes, Pepperoni Pizza"]],
54-
}'
55-
56-
The interface (including directions, whether to exclude or rename days,
57-
and costs) should be controlled by overriding the template:
58-
program/modules/splashinfomodule/splashinfo.html
59-
"""
60-
61-
class SplashInfoForm(forms.Form):
62-
# The default choices are somewhat unappetizing...
63-
default_choices = [['no', 'No'], ['yes', 'Yes']]
64-
discount_choices = [(False, 'I am the first in my household enrolling in Splash (+ $40)'),
65-
(True, 'I have a sibling already enrolled in Splash (+ $20).')]
66-
67-
lunchsat = forms.ChoiceField(choices=default_choices)
68-
lunchsun = forms.ChoiceField(choices=default_choices)
69-
siblingdiscount = forms.ChoiceField(choices=discount_choices, widget=forms.RadioSelect)
38+
class SiblingDiscountForm(forms.Form):
39+
siblingdiscount = forms.TypedChoiceField(choices=[], coerce=lambda x: x == 'True', widget=forms.RadioSelect)
7040
siblingname = forms.CharField(max_length=128, required=False)
7141

7242
def __init__(self, *args, **kwargs):
73-
# Extract a program if one was provided
7443
if 'program' in kwargs:
75-
program = kwargs['program']
76-
del kwargs['program']
44+
program = kwargs.pop('program')
7745
else:
78-
program = None
79-
80-
# Run default init function
81-
super(SplashInfoForm, self).__init__(*args, **kwargs)
82-
83-
# Set choices from Tag data (try to get program-specific choices if they exist)
84-
tag_data = Tag.getProgramTag('splashinfo_choices', program)
85-
if tag_data:
86-
tag_struct = json.loads(tag_data)
87-
self.fields['lunchsat'].choices = tag_struct['lunchsat']
88-
self.fields['lunchsun'].choices = tag_struct['lunchsun']
89-
90-
if not Tag.getBooleanTag('splashinfo_siblingdiscount', program=program):
91-
del self.fields['siblingdiscount']
92-
del self.fields['siblingname']
93-
94-
if not Tag.getBooleanTag('splashinfo_lunchsat', program=program):
95-
del self.fields['lunchsat']
96-
97-
if not Tag.getBooleanTag('splashinfo_lunchsun', program=program):
98-
del self.fields['lunchsun']
46+
raise KeyError('Need to supply program as named argument to SiblingDiscountForm')
47+
self.base_fields['siblingdiscount'].choices = [(False, 'I am the first in my household enrolling in Splash (+ $' + str(program.base_cost) + ').'),
48+
(True, 'I have a sibling already enrolled in Splash (+ $' + str(program.base_cost - program.sibling_discount) + ').')]
49+
self.base_fields['siblingdiscount'].initial = True
50+
super(SiblingDiscountForm, self).__init__(*args, **kwargs)
51+
52+
def clean(self):
53+
cleaned_data = super(SiblingDiscountForm, self).clean()
54+
siblingdiscount = cleaned_data.get("siblingdiscount")
55+
siblingname = cleaned_data.get("siblingname")
56+
if siblingdiscount and not siblingname:
57+
self.add_error('siblingname', "You didn't provide the name of your sibling.")
58+
elif not siblingdiscount:
59+
self.cleaned_data['siblingname'] = ""
9960

10061
def load(self, splashinfo):
101-
self.initial['lunchsat'] = splashinfo.lunchsat
102-
self.initial['lunchsun'] = splashinfo.lunchsun
10362
self.initial['siblingdiscount'] = splashinfo.siblingdiscount
10463
self.initial['siblingname'] = splashinfo.siblingname
10564

10665
def save(self, splashinfo):
107-
if 'lunchsat' in self.cleaned_data:
108-
splashinfo.lunchsat = self.cleaned_data['lunchsat']
109-
if 'lunchsun' in self.cleaned_data:
110-
splashinfo.lunchsun = self.cleaned_data['lunchsun']
11166
if 'siblingdiscount' in self.cleaned_data:
112-
splashinfo.siblingdiscount = (self.cleaned_data['siblingdiscount'] == "True")
67+
splashinfo.siblingdiscount = self.cleaned_data['siblingdiscount']
11368
if 'siblingname' in self.cleaned_data:
11469
splashinfo.siblingname = self.cleaned_data['siblingname']
11570
splashinfo.submitted = True
11671
splashinfo.save()
117-

esp/esp/program/modules/handlers/creditcardmodule_stripe.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ def get_setting(self, name, default=None):
8585
return self.apply_settings().get(name, default)
8686

8787
def line_item_type(self):
88-
pac = ProgramAccountingController(self.program)
89-
(donate_type, created) = pac.get_lineitemtypes().get_or_create(program=self.program, text=self.get_setting('donation_text'))
88+
(donate_type, created) = LineItemType.objects.get_or_create(program=self.program, text=self.get_setting('donation_text'))
9089
return donate_type
9190

9291
def isCompleted(self):
@@ -170,7 +169,7 @@ def payonline(self, request, tl, one, two, module, extra, prog):
170169
sibling_type = iac.default_siblingdiscount_lineitemtype()
171170
grant_type = iac.default_finaid_lineitemtype()
172171
offer_donation = self.settings['offer_donation']
173-
donate_type = iac.get_lineitemtypes().get(text=self.settings['donation_text']) if offer_donation else None
172+
donate_type = LineItemType.objects.get(program=self.program, text=self.settings['donation_text']) if offer_donation else None
174173
context['itemizedcosts'] = iac.get_transfers().exclude(line_item__in=filter(None, [payment_type, sibling_type, grant_type, donate_type])).order_by('-line_item__required')
175174
context['itemizedcosttotal'] = iac.amount_due()
176175
# This amount should be formatted as an integer in order to be

esp/esp/program/modules/handlers/donationmodule.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from esp.users.models import ESPUser, Record
4040
from esp.tagdict.models import Tag
4141
from esp.accounting.models import LineItemType
42-
from esp.accounting.controllers import ProgramAccountingController, IndividualAccountingController
42+
from esp.accounting.controllers import IndividualAccountingController
4343
from esp.middleware import ESPError
4444
from esp.middleware.threadlocalrequest import get_current_request
4545

@@ -119,8 +119,7 @@ def get_setting(self, name, default=None):
119119
return self.apply_settings().get(name, default)
120120

121121
def line_item_type(self):
122-
pac = ProgramAccountingController(self.program)
123-
(donate_type, created) = pac.get_lineitemtypes().get_or_create(program=self.program, text=self.get_setting('donation_text'))
122+
(donate_type, created) = LineItemType.objects.get_or_create(program=self.program, text=self.get_setting('donation_text'))
124123
return donate_type
125124

126125
def isCompleted(self):

0 commit comments

Comments
 (0)