diff --git a/paying_for_college/migrations/0013_add_notification_url.py b/paying_for_college/migrations/0013_add_notification_url.py new file mode 100644 index 0000000..e92b565 --- /dev/null +++ b/paying_for_college/migrations/0013_add_notification_url.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('paying_for_college', '0012_add_feedback_url'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='url', + field=models.TextField(null=True, blank=True), + ), + ] diff --git a/paying_for_college/migrations/0014_migrate_url_field_to_notifications.py b/paying_for_college/migrations/0014_migrate_url_field_to_notifications.py new file mode 100644 index 0000000..65b9670 --- /dev/null +++ b/paying_for_college/migrations/0014_migrate_url_field_to_notifications.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations +from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned + + +def parse_url(feedback): + """ + Replicates the Feedback model method 'parsed_url' because model methods + are not available in migrations (grumble). + """ + data = {} + if 'feedback' in feedback.url or '?' not in feedback.url: + return data + split_fields = feedback.url.split('?')[1].split('&') + for field in split_fields: + pair = field.split('=') + data[pair[0]] = pair[1] + return data + + +def forward(apps, schema_editor): + Notification = apps.get_model('paying_for_college', 'Notification') + Feedback = apps.get_model('paying_for_college', 'Feedback') + db_alias = schema_editor.connection.alias + + for feedback in Feedback.objects.using(db_alias).exclude(url=None): + try: + notification = Notification.objects.using( + db_alias).get(oid=parse_url(feedback).get('oid')) + notification.url = feedback.url + notification.save() + except ObjectDoesNotExist: + continue + except MultipleObjectsReturned: + continue + + +def backward(apps, schema_editor): + Notification = apps.get_model('paying_for_college', 'Notification') + db_alias = schema_editor.connection.alias + + for notification in Notification.objects.using(db_alias).all(): + notification.url = None + notification.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('paying_for_college', '0013_add_notification_url'), + ] + + operations = [ + migrations.RunPython(forward, backward), + ] diff --git a/paying_for_college/models.py b/paying_for_college/models.py index c553046..7e31716 100755 --- a/paying_for_college/models.py +++ b/paying_for_college/models.py @@ -341,7 +341,93 @@ def nicknames(self): return ", ".join([nick.nickname for nick in self.nickname_set.all()]) -class Notification(models.Model): +class DisclosureBase(models.Model): + """Abstract base class for disclosure-related interactions""" + url = models.TextField(blank=True, null=True) + + class Meta: + abstract = True + + @property + def parsed_url(self): + """parses a disclosure URL and returns a field:value dict""" + data = {} + if not self.url or 'feedback' in self.url or '?' not in self.url: + return data + split_fields = self.url.replace( + '#info-right', '').split('?')[1].split('&') + for field in split_fields: + pair = field.split('=') + data[pair[0]] = pair[1] + return data + + @property + def school(self): + """Returns a school object, derived from a feedback url""" + if not self.url: + return None + row = self.parsed_url + if row and row.get('iped'): + return School.objects.get(pk=row['iped']) + else: + return None + + @property + def unmet_cost(self): + """Calculates and returns a disclosure's unmet cost""" + url_data = self.parsed_url + if not url_data: + return None + + def total_fields(field_list): + total = 0 + for field in field_list: + if field in url_data.keys() and url_data.get(field, '') != '': + try: + total += int(url_data[field]) + except ValueError: + pass + return total + + cost_fields = ['tuit', 'hous', 'book', 'tran', 'othr'] + asset_fields = ['pelg', 'gib', 'mta', 'schg', 'othg', 'stag', 'wkst', + 'prvl', 'ppl', 'perl', 'gpl', 'insl', 'subl', 'unsl'] + total_costs = total_fields(cost_fields) + total_assets = total_fields(asset_fields) + return total_costs - total_assets + + @property + def cost_error(self): + """Return 1 or 0: Is total-cost less than tuition?""" + url_data = self.parsed_url + if url_data and url_data['totl'] != '' and url_data['tuit'] != '': + if int(url_data['totl']) < int(url_data['tuit']): + return 1 + else: + return 0 + else: + return 0 + + @property + def tuition_plan(self): + """Return amount of tuition plan or None""" + url_data = self.parsed_url + if url_data and url_data.get('insl'): + try: + return int(url_data.get('insl')) + except ValueError: + return None + + +class Feedback(DisclosureBase): + """ + User-submitted feedback + """ + created = models.DateTimeField(auto_now_add=True) + message = models.TextField() + + +class Notification(DisclosureBase): """record of a disclosure verification""" institution = models.ForeignKey(School) oid = models.CharField(max_length=40) @@ -731,74 +817,6 @@ class Worksheet(models.Model): updated = models.DateTimeField(auto_now=True) -class Feedback(models.Model): - """ - User-submitted feedback - """ - created = models.DateTimeField(auto_now_add=True) - message = models.TextField() - url = models.TextField(blank=True, null=True) - - @property - def parsed_url(self): - """parses a disclosure URL and returns a field:value dict""" - data = {} - if not self.url or 'feedback' in self.url or '?' not in self.url: - return data - split_fields = self.url.split('?')[1].split('&') - for field in split_fields: - pair = field.split('=') - data[pair[0]] = pair[1] - return data - - @property - def school(self): - """Returns a school object, derived from a feedback url""" - if not self.url: - return None - row = self.parsed_url - if row and row.get('iped'): - return School.objects.get(pk=row['iped']) - else: - return None - - @property - def unmet_cost(self): - """Calculates and returns a disclosure's unmet cost""" - url_data = self.parsed_url - if not url_data: - return None - - def total_fields(field_list): - total = 0 - for field in field_list: - if field in url_data.keys() and url_data.get(field, '') != '': - try: - total += int(url_data[field]) - except ValueError: - pass - return total - - cost_fields = ['tuit', 'hous', 'book', 'tran', 'othr'] - asset_fields = ['pelg', 'gib', 'mta', 'schg', 'othg', 'stag', 'wkst', - 'prvl', 'ppl', 'perl', 'gpl', 'insl', 'subl', 'unsl'] - total_costs = total_fields(cost_fields) - total_assets = total_fields(asset_fields) - return total_costs - total_assets - - @property - def cost_error(self): - """Return 1 or 0: Is total-cost less than tuition?""" - url_data = self.parsed_url - if url_data and url_data['totl'] != '' and url_data['tuit'] != '': - if int(url_data['totl']) < int(url_data['tuit']): - return 1 - else: - return 0 - else: - return 0 - - def print_vals(obj, val_list=False, val_dict=False, noprint=False): """inspect a Django db object""" keylist = sorted([key for key in obj._meta.get_all_field_names()], diff --git a/paying_for_college/tests/test_models.py b/paying_for_college/tests/test_models.py index 0a27f9a..fef5bb1 100644 --- a/paying_for_college/tests/test_models.py +++ b/paying_for_college/tests/test_models.py @@ -297,6 +297,14 @@ def test_feedback_cost_error(self): feedback.url = feedback.url.replace('totl=1000', 'totl=') self.assertEqual(feedback.cost_error, 0) + def test_feedback_tuition_plan(self): + feedback = self.create_feedback() + self.assertEqual(feedback.tuition_plan, 4339) + feedback.url = feedback.url.replace('insl=4339', 'insl=a') + self.assertEqual(feedback.tuition_plan, None) + feedback.url = feedback.url.replace('insl=a', 'insl=') + self.assertEqual(feedback.tuition_plan, None) + class NonSettlementNotificaion(TestCase): fixtures = ['test_fixture.json'] diff --git a/paying_for_college/tests/test_views.py b/paying_for_college/tests/test_views.py index d9f871f..ad503c0 100644 --- a/paying_for_college/tests/test_views.py +++ b/paying_for_college/tests/test_views.py @@ -371,7 +371,8 @@ class VerifyViewTest(django.test.TestCase): fixtures = ['test_fixture.json'] post_data = {'oid': 'f38283b5b7c939a058889f997949efa566c616c5', 'iped': '243197', - 'errors': 'none'} + 'errors': 'none', + 'URL': 'fake-url.com'} url = reverse('disclosures:verify') def test_verify_view(self): diff --git a/paying_for_college/views.py b/paying_for_college/views.py index 590bc19..7b64dd9 100755 --- a/paying_for_college/views.py +++ b/paying_for_college/views.py @@ -225,7 +225,7 @@ def post(self, request): base_template = BASE_TEMPLATE feedback = Feedback( message=form.cleaned_data['message'][:2000], - url=form.cleaned_data['referrer']) + url=form.cleaned_data['referrer'].replace('#info-right', '')) feedback.save() return render_to_response( "feedback_thanks.html", @@ -361,11 +361,11 @@ class VerifyView(View): def post(self, request): data = request.POST timestamp = timezone.now() - if 'oid' in data and data['oid'] and validate_oid(data['oid']): + if data.get('oid') and validate_oid(data['oid']): OID = data['oid'] else: return HttpResponseBadRequest('Error: No valid OID provided') - if 'iped' in data and data['iped'] and get_school(data['iped']): + if data.get('iped') and get_school(data['iped']): school = get_school(data['iped']) if not school.contact: errmsg = "Error: School has no contact." @@ -373,10 +373,12 @@ def post(self, request): if Notification.objects.filter(institution=school, oid=OID): errmsg = "Error: OfferID has already generated a notification." return HttpResponseBadRequest(errmsg) - notification = Notification(institution=school, - oid=OID, - timestamp=timestamp, - errors=data['errors'][:255]) + notification = Notification( + institution=school, + oid=OID, + url=data['URL'].replace('#info-right', ''), + timestamp=timestamp, + errors=data['errors'][:255]) notification.save() msg = notification.notify_school() callback = json.dumps({'result': diff --git a/src/disclosures/js/dispatchers/post-verify.js b/src/disclosures/js/dispatchers/post-verify.js index 28e47fa..f652a4a 100644 --- a/src/disclosures/js/dispatchers/post-verify.js +++ b/src/disclosures/js/dispatchers/post-verify.js @@ -24,7 +24,8 @@ var postVerify = { csrfmiddlewaretoken: this.csrfToken, oid: offerID, iped: collegeID, - errors: 'none' + errors: 'none', + URL: window.location.href }; url = '/' + $( 'main' ).attr( 'data-context' ) + '/understanding-your-financial-aid-offer/api/verify/';