Skip to content

Commit

Permalink
Move url storage from Feedback to Notifcation
Browse files Browse the repository at this point in the history
To help troubleshooting, we create an abstract model for sharing
analysis methods among models and move the storage of urls from the
Feedback model to Notifications.
  • Loading branch information
higs4281 committed May 2, 2017
1 parent 0af0fa5 commit 29ce940
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 78 deletions.
19 changes: 19 additions & 0 deletions paying_for_college/migrations/0013_add_notification_url.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
Original file line number Diff line number Diff line change
@@ -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),
]
156 changes: 87 additions & 69 deletions paying_for_college/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()],
Expand Down
8 changes: 8 additions & 0 deletions paying_for_college/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down
3 changes: 2 additions & 1 deletion paying_for_college/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
16 changes: 9 additions & 7 deletions paying_for_college/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -361,22 +361,24 @@ 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."
return HttpResponseBadRequest(errmsg)
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':
Expand Down
3 changes: 2 additions & 1 deletion src/disclosures/js/dispatchers/post-verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/';
Expand Down

0 comments on commit 29ce940

Please sign in to comment.