Commit 99e8596f by Julia Hansbrough

Response to CR

parent 893acc57
...@@ -581,9 +581,15 @@ class CourseEnrollment(models.Model): ...@@ -581,9 +581,15 @@ class CourseEnrollment(models.Model):
) )
@classmethod @classmethod
def enrollments_in(cls, course_id): def enrollments_in(cls, course_id, mode=None):
"""Return a queryset of CourseEnrollment for every active enrollment in the course.""" """
return cls.objects.filter(course_id=course_id, is_active=True,) Return a queryset of CourseEnrollment for every active enrollment in the course course_id.
Returns only CourseEnrollments with the given mode, if a mode is supplied by the caller.
"""
if mode is None:
return cls.objects.filter(course_id=course_id, is_active=True,)
else:
return cls.objects.filter(course_id=course_id, is_active=True, mode=mode)
def activate(self): def activate(self):
"""Makes this `CourseEnrollment` record active. Saves immediately.""" """Makes this `CourseEnrollment` record active. Saves immediately."""
......
...@@ -205,13 +205,13 @@ class OrderItem(models.Model): ...@@ -205,13 +205,13 @@ class OrderItem(models.Model):
# this is denormalized, but convenient for SQL queries for reports, etc. user should always be = order.user # this is denormalized, but convenient for SQL queries for reports, etc. user should always be = order.user
user = models.ForeignKey(User, db_index=True) user = models.ForeignKey(User, db_index=True)
# this is denormalized, but convenient for SQL queries for reports, etc. status should always be = order.status # this is denormalized, but convenient for SQL queries for reports, etc. status should always be = order.status
status = models.CharField(max_length=32, default='cart', choices=ORDER_STATUSES) status = models.CharField(max_length=32, default='cart', choices=ORDER_STATUSES, db_index=True)
qty = models.IntegerField(default=1) qty = models.IntegerField(default=1)
unit_cost = models.DecimalField(default=0.0, decimal_places=2, max_digits=30) unit_cost = models.DecimalField(default=0.0, decimal_places=2, max_digits=30)
line_desc = models.CharField(default="Misc. Item", max_length=1024) line_desc = models.CharField(default="Misc. Item", max_length=1024)
currency = models.CharField(default="usd", max_length=8) # lower case ISO currency codes currency = models.CharField(default="usd", max_length=8) # lower case ISO currency codes
fulfilled_time = models.DateTimeField(null=True) fulfilled_time = models.DateTimeField(null=True, db_index=True)
refund_requested_time = models.DateTimeField(null=True) refund_requested_time = models.DateTimeField(null=True, db_index=True)
service_fee = models.DecimalField(default=0.0, decimal_places=2, max_digits=30) service_fee = models.DecimalField(default=0.0, decimal_places=2, max_digits=30)
# general purpose field, not user-visible. Used for reporting # general purpose field, not user-visible. Used for reporting
report_comments = models.TextField(default="") report_comments = models.TextField(default="")
...@@ -568,3 +568,8 @@ class CertificateItem(OrderItem): ...@@ -568,3 +568,8 @@ class CertificateItem(OrderItem):
"Please include your order number in your e-mail. " "Please include your order number in your e-mail. "
"Please do NOT include your credit card information.").format( "Please do NOT include your credit card information.").format(
billing_email=settings.PAYMENT_SUPPORT_EMAIL) billing_email=settings.PAYMENT_SUPPORT_EMAIL)
@classmethod
def verified_certificates_in(cls, course_id, status):
"""Return a queryset of CertificateItem for every verified enrollment in course_id with the given status."""
return CertificateItem.objects.filter(course_id=course_id, mode='verified', status=status)
...@@ -356,32 +356,47 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase): ...@@ -356,32 +356,47 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase):
self.cart.purchase() self.cart.purchase()
self.now = datetime.datetime.now(pytz.UTC) self.now = datetime.datetime.now(pytz.UTC)
# We can't modify the values returned by report_row_generator directly, since it's a generator, but
# we need the times on CORRECT_CSV and the generated report to match. So, we extract the times from
# the report_row_generator and place them in CORRECT_CSV.
self.time_str = {}
report = initialize_report("itemized_purchase_report")
purchases = report.report_row_generator(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
num_of_item = 0
for item in purchases:
num_of_item += 1
self.time_str[num_of_item] = item[0]
self.CORRECT_CSV = dedent("""
Purchase Time,Order ID,Status,Quantity,Unit Cost,Total Cost,Currency,Description,Comments
{time_str1},1,purchased,1,40,40,usd,Registration for Course: Robot Super Course,Ba\xc3\xbc\xe5\x8c\x85
{time_str2},1,purchased,1,40,40,usd,"Certificate of Achievement, verified cert for course Robot Super Course",
""".format(time_str1=str(self.time_str[1]), time_str2=str(self.time_str[2])))
def test_purchased_items_btw_dates(self): def test_purchased_items_btw_dates(self):
report = initialize_report("itemized_purchase_report") report = initialize_report("itemized_purchase_report")
purchases = report.get_report_data(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS) purchases = report.report_row_generator(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
self.assertEqual(len(purchases), 2)
self.assertIn(self.reg.orderitem_ptr, purchases)
self.assertIn(self.cert_item.orderitem_ptr, purchases)
no_purchases = report.get_report_data(self.now + self.FIVE_MINS, self.now + self.FIVE_MINS + self.FIVE_MINS)
self.assertFalse(no_purchases)
test_time = datetime.datetime.now(pytz.UTC) # since there's not many purchases, just run through the generator to make sure we've got the right number
num_purchases = 0
for item in purchases:
num_purchases += 1
self.assertEqual(num_purchases, 2)
#self.assertIn(self.reg.orderitem_ptr, purchases)
#self.assertIn(self.cert_item.orderitem_ptr, purchases)
CORRECT_CSV = dedent(""" no_purchases = report.report_row_generator(self.now + self.FIVE_MINS, self.now + self.FIVE_MINS + self.FIVE_MINS)
Purchase Time,Order ID,Status,Quantity,Unit Cost,Total Cost,Currency,Description,Comments
{time_str},1,purchased,1,40,40,usd,Registration for Course: Robot Super Course,Ba\xc3\xbc\xe5\x8c\x85 num_purchases = 0
{time_str},1,purchased,1,40,40,usd,"Certificate of Achievement, verified cert for course Robot Super Course", for item in no_purchases:
""".format(time_str=str(test_time))) num_purchases +=1
self.assertEqual(num_purchases, 0)
def test_purchased_csv(self): def test_purchased_csv(self):
""" """
Tests that a generated purchase report CSV is as we expect Tests that a generated purchase report CSV is as we expect
""" """
report = initialize_report("itemized_purchase_report") report = initialize_report("itemized_purchase_report")
for item in report.get_report_data(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS):
item.fulfilled_time = self.test_time
item.save()
csv_file = StringIO.StringIO() csv_file = StringIO.StringIO()
report.make_report(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS) report.make_report(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
csv = csv_file.getvalue() csv = csv_file.getvalue()
......
...@@ -29,44 +29,36 @@ class ReportTypeTests(ModuleStoreTestCase): ...@@ -29,44 +29,36 @@ class ReportTypeTests(ModuleStoreTestCase):
def setUp(self): def setUp(self):
# Need to make a *lot* of users for this one # Need to make a *lot* of users for this one
self.user1 = UserFactory.create() self.user1 = UserFactory.create()
self.user1.first_name = "John" self.user1.profile.name = "John Doe"
self.user1.last_name = "Doe" self.user1.profile.save()
self.user1.save()
self.user2 = UserFactory.create() self.user2 = UserFactory.create()
self.user2.first_name = "Jane" self.user2.profile.name = "Jane Deer"
self.user2.last_name = "Deer" self.user2.profile.save()
self.user2.save()
self.user3 = UserFactory.create() self.user3 = UserFactory.create()
self.user3.first_name = "Joe" self.user3.profile.name = "Joe Miller"
self.user3.last_name = "Miller" self.user3.profile.save()
self.user3.save()
self.user4 = UserFactory.create() self.user4 = UserFactory.create()
self.user4.first_name = "Simon" self.user4.profile.name = "Simon Blackquill"
self.user4.last_name = "Blackquill" self.user4.profile.save()
self.user4.save()
self.user5 = UserFactory.create() self.user5 = UserFactory.create()
self.user5.first_name = "Super" self.user5.profile.name = "Super Mario"
self.user5.last_name = "Mario" self.user5.profile.save()
self.user5.save()
self.user6 = UserFactory.create() self.user6 = UserFactory.create()
self.user6.first_name = "Princess" self.user6.profile.name = "Princess Peach"
self.user6.last_name = "Peach" self.user6.profile.save()
self.user6.save()
self.user7 = UserFactory.create() self.user7 = UserFactory.create()
self.user7.first_name = "King" self.user7.profile.name = "King Bowser"
self.user7.last_name = "Bowser" self.user7.profile.save()
self.user7.save()
self.user8 = UserFactory.create() self.user8 = UserFactory.create()
self.user8.first_name = "Susan" self.user8.profile.name = "Susan Smith"
self.user8.last_name = "Smith" self.user8.profile.save()
self.user8.save()
# Two are verified, three are audit, one honor # Two are verified, three are audit, one honor
...@@ -116,16 +108,29 @@ class ReportTypeTests(ModuleStoreTestCase): ...@@ -116,16 +108,29 @@ class ReportTypeTests(ModuleStoreTestCase):
self.cart.purchase(self.user8, self.course_id) self.cart.purchase(self.user8, self.course_id)
CourseEnrollment.unenroll(self.user8, self.course_id) CourseEnrollment.unenroll(self.user8, self.course_id)
self.test_time = datetime.datetime.now(pytz.UTC) # We can't modify the values returned by report_row_generator directly, since it's a generator, but
# we need the times on CORRECT_CSV and the generated report to match. So, we extract the times from
# the report_row_generator and place them in CORRECT_CSV.
self.time_str = {}
report = initialize_report("refund_report")
refunds = report.report_row_generator(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
time_index = 0
for item in refunds:
self.time_str[time_index] = item[2]
time_index += 1
self.time_str[time_index] = item[3]
time_index += 1
self.CORRECT_REFUND_REPORT_CSV = dedent(""" self.CORRECT_REFUND_REPORT_CSV = dedent("""
Order Number,Customer Name,Date of Original Transaction,Date of Refund,Amount of Refund,Service Fees (if any) Order Number,Customer Name,Date of Original Transaction,Date of Refund,Amount of Refund,Service Fees (if any)
3,King Bowser,{time_str},{time_str},40,0 3,King Bowser,{time_str0},{time_str1},40,0
4,Susan Smith,{time_str},{time_str},40,0 4,Susan Smith,{time_str2},{time_str3},40,0
""".format(time_str=str(self.test_time))) """.format(time_str0=str(self.time_str[0]), time_str1=str(self.time_str[1]), time_str2=str(self.time_str[2]), time_str3=str(self.time_str[3])))
self.test_time = datetime.datetime.now(pytz.UTC)
self.CORRECT_CERT_STATUS_CSV = dedent(""" self.CORRECT_CERT_STATUS_CSV = dedent("""
University,Course,Total Enrolled,Audit Enrollment,Honor Code Enrollment,Verified Enrollment,Gross Revenue,Gross Revenue over the Minimum,Number of Refunds,Dollars Refunded University,Course,Total Enrolled,Audit Enrollment,Honor Code Enrollment,Verified Enrollment,Gross Revenue,Gross Revenue over the Minimum,Number of Refunds,Dollars Refunded
MITx,999 Robot Super Course,6,3,1,2,80.00,0.00,0,0 MITx,999 Robot Super Course,6,3,1,2,80.00,0.00,2,80.00
""".format(time_str=str(self.test_time))) """.format(time_str=str(self.test_time)))
self.CORRECT_UNI_REVENUE_SHARE_CSV = dedent(""" self.CORRECT_UNI_REVENUE_SHARE_CSV = dedent("""
...@@ -133,10 +138,16 @@ class ReportTypeTests(ModuleStoreTestCase): ...@@ -133,10 +138,16 @@ class ReportTypeTests(ModuleStoreTestCase):
MITx,999 Robot Super Course,0,80.00,0.00,2,80.00 MITx,999 Robot Super Course,0,80.00,0.00,2,80.00
""".format(time_str=str(self.test_time))) """.format(time_str=str(self.test_time)))
def test_refund_report_get_report_data(self): def test_refund_report_report_row_generator(self):
report = initialize_report("refund_report") report = initialize_report("refund_report")
refunded_certs = report.get_report_data(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS) refunded_certs = report.report_row_generator(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
self.assertEqual(len(refunded_certs), 2)
# check that we have the right number
num_certs = 0
for cert in refunded_certs:
num_certs += 1
self.assertEqual(num_certs, 2)
self.assertTrue(CertificateItem.objects.get(user=self.user7, course_id=self.course_id)) self.assertTrue(CertificateItem.objects.get(user=self.user7, course_id=self.course_id))
self.assertTrue(CertificateItem.objects.get(user=self.user8, course_id=self.course_id)) self.assertTrue(CertificateItem.objects.get(user=self.user8, course_id=self.course_id))
...@@ -145,11 +156,6 @@ class ReportTypeTests(ModuleStoreTestCase): ...@@ -145,11 +156,6 @@ class ReportTypeTests(ModuleStoreTestCase):
Tests that a generated purchase report CSV is as we expect Tests that a generated purchase report CSV is as we expect
""" """
report = initialize_report("refund_report") report = initialize_report("refund_report")
for item in report.get_report_data(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS):
item.fulfilled_time = self.test_time
item.refund_requested_time = self.test_time # hm do we want to make these different
item.save()
csv_file = StringIO.StringIO() csv_file = StringIO.StringIO()
report.make_report(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS) report.make_report(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
csv = csv_file.getvalue() csv = csv_file.getvalue()
......
...@@ -213,7 +213,7 @@ def csv_report(request): ...@@ -213,7 +213,7 @@ def csv_report(request):
return _render_report_form(start_str, end_str, start_letter, end_letter, report_type, date_fmt_error=True) return _render_report_form(start_str, end_str, start_letter, end_letter, report_type, date_fmt_error=True)
report = initialize_report(report_type) report = initialize_report(report_type)
items = report.get_report_data(start_date, end_date, start_letter, end_letter) items = report.report_row_generator(start_date, end_date, start_letter, end_letter)
# TODO add this back later as a query-est function or something # TODO add this back later as a query-est function or something
try: try:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment