Commit f2b03942 by Julia Hansbrough

CR response

parent 95affba6
......@@ -96,6 +96,14 @@ class CourseMode(models.Model):
return None
@classmethod
def min_course_price_for_verified_for_currency(cls, course_id, currency):
modes = cls.modes_for_course(course_id)
for mode in modes:
if (mode.currency == currency) and (mode.slug == 'verified'):
return mode.min_price
return 0
@classmethod
def min_course_price_for_currency(cls, course_id, currency):
"""
Returns the minimum price of the course in the appropriate currency over all the course's
......
......@@ -17,11 +17,13 @@ import json
import logging
from pytz import UTC
import uuid
from collections import defaultdict
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.db import models, IntegrityError
from django.db.models import Sum, Count
from django.db.models.signals import post_save
from django.dispatch import receiver, Signal
import django.dispatch
......@@ -585,11 +587,14 @@ class CourseEnrollment(models.Model):
Returns a dictionary that stores the total enrollment count for a course, as well as the
enrollment count for each individual mode.
"""
d = {}
d['total'] = cls.objects.filter(course_id=course_id, is_active=True).count()
d['honor'] = cls.objects.filter(course_id=course_id, is_active=True, mode='honor').count()
d['audit'] = cls.objects.filter(course_id=course_id, is_active=True, mode='audit').count()
d['verified'] = cls.objects.filter(course_id=course_id, is_active=True, mode='verified').count()
# Unfortunately, Django's "group by"-style queries look super-awkward
query = cls.objects.filter(course_id=course_id, is_active=True).values('mode').order_by().annotate(Count('mode'))
total = 0
d = defaultdict(int)
for item in query:
d[item['mode']] = item['mode__count']
total += item['mode__count']
d['total'] = total
return d
def activate(self):
......
......@@ -587,8 +587,17 @@ class CertificateItem(OrderItem):
etc
"""
query = use_read_replica_if_available(
CertificateItem.objects.filter(course_id=course_id, mode='verified', status='purchased').aggregate(Sum(field_to_aggregate)))[field_to_aggregate + '__sum']
CertificateItem.objects.filter(course_id=course_id, mode='verified', status=status).aggregate(Sum(field_to_aggregate)))[field_to_aggregate + '__sum']
if query is None:
return Decimal(0.00)
else:
return query
@classmethod
def verified_certificates_contributing_more_than_minimum(cls, course_id):
return use_read_replica_if_available(
CertificateItem.objects.filter(
course_id=course_id,
mode='verified',
status='purchased',
unit_cost__gt=(CourseMode.min_course_price_for_verified_for_currency(course_id, 'usd'))).count())
......@@ -373,8 +373,8 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase):
""".format(time_str=str(self.now)))
def test_purchased_items_btw_dates(self):
report = initialize_report("itemized_purchase_report")
purchases = report.rows(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
report = initialize_report("itemized_purchase_report", self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
purchases = report.rows()
# since there's not many purchases, just run through the generator to make sure we've got the right number
num_purchases = 0
......@@ -384,7 +384,8 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase):
#self.assertIn(self.reg.orderitem_ptr, purchases)
#self.assertIn(self.cert_item.orderitem_ptr, purchases)
no_purchases = report.rows(self.now + self.FIVE_MINS, self.now + self.FIVE_MINS + self.FIVE_MINS)
report = initialize_report("itemized_purchase_report", self.now + self.FIVE_MINS, self.now + self.FIVE_MINS + self.FIVE_MINS)
no_purchases = report.rows()
num_purchases = 0
for item in no_purchases:
......@@ -395,9 +396,9 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase):
"""
Tests that a generated purchase report CSV is as we expect
"""
report = initialize_report("itemized_purchase_report")
report = initialize_report("itemized_purchase_report", self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
csv_file = StringIO.StringIO()
report.write_csv(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
report.write_csv(csv_file)
csv = csv_file.getvalue()
csv_file.close()
# Using excel mode csv, which automatically ends lines with \r\n, so need to convert to \n
......
# -*- coding: utf-8 -*-
"""
Tests for the Shopping Cart Models
"""
......@@ -128,18 +130,18 @@ class ReportTypeTests(ModuleStoreTestCase):
""".format(time_str=str(self.test_time)))
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
MITx,999 Robot Super Course,6,3,1,2,80.00,0.00,2,80.00
University,Course,Course Announce Date,Course Start Date,Course Registration Close Date,Course Registration Period,Total Enrolled,Audit Enrollment,Honor Code Enrollment,Verified Enrollment,Gross Revenue,Gross Revenue over the Minimum,Number of Verified Students Contributing More than the Minimum,Number of Refunds,Dollars Refunded
MITx,999 Robot Super Course,,,,,6,3,1,2,80.00,0.00,0,2,80.00
""".format(time_str=str(self.test_time)))
self.CORRECT_UNI_REVENUE_SHARE_CSV = dedent("""
University,Course,Number of Transactions,Total Payments Collected,Service Fees (if any),Number of Successful Refunds,Total Amount of Refunds
MITx,999 Robot Super Course,0,80.00,0.00,2,80.00
MITx,999 Robot Super Course,6,80.00,0.00,2,80.00
""".format(time_str=str(self.test_time)))
def test_refund_report_rows(self):
report = initialize_report("refund_report")
refunded_certs = report.rows(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
report = initialize_report("refund_report", self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
refunded_certs = report.rows()
# check that we have the right number
num_certs = 0
......@@ -154,24 +156,24 @@ class ReportTypeTests(ModuleStoreTestCase):
"""
Tests that a generated purchase report CSV is as we expect
"""
report = initialize_report("refund_report")
report = initialize_report("refund_report", self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
csv_file = StringIO.StringIO()
report.write_csv(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
report.write_csv(csv_file)
csv = csv_file.getvalue()
csv_file.close()
# Using excel mode csv, which automatically ends lines with \r\n, so need to convert to \n
self.assertEqual(csv.replace('\r\n', '\n').strip(), self.CORRECT_REFUND_REPORT_CSV.strip())
def test_basic_cert_status_csv(self):
report = initialize_report("certificate_status")
report = initialize_report("certificate_status", self.now - self.FIVE_MINS, self.now + self.FIVE_MINS, 'A', 'Z')
csv_file = StringIO.StringIO()
report.write_csv(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS, 'A', 'Z')
report.write_csv(csv_file)
csv = csv_file.getvalue()
self.assertEqual(csv.replace('\r\n', '\n').strip(), self.CORRECT_CERT_STATUS_CSV.strip())
def test_basic_uni_revenue_share_csv(self):
report = initialize_report("university_revenue_share")
report = initialize_report("university_revenue_share", self.now - self.FIVE_MINS, self.now + self.FIVE_MINS, 'A', 'Z')
csv_file = StringIO.StringIO()
report.write_csv(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS, 'A', 'Z')
report.write_csv(csv_file)
csv = csv_file.getvalue()
self.assertEqual(csv.replace('\r\n', '\n').strip(), self.CORRECT_UNI_REVENUE_SHARE_CSV.strip())
......@@ -369,29 +369,35 @@ class CSVReportViewsTest(ModuleStoreTestCase):
def test_report_csv_itemized(self):
report_type = 'itemized_purchase_report'
start_date = '1970-01-01'
end_date = '2100-01-01'
PaidCourseRegistration.add_to_order(self.cart, self.course_id)
self.cart.purchase()
self.login_user()
self.add_to_download_group(self.user)
response = self.client.post(reverse('payment_csv_report'), {'start_date': '1970-01-01',
'end_date': '2100-01-01',
response = self.client.post(reverse('payment_csv_report'), {'start_date': start_date,
'end_date': end_date,
'requested_report': report_type})
self.assertEqual(response['Content-Type'], 'text/csv')
report = initialize_report(report_type)
report = initialize_report(report_type, start_date, end_date)
self.assertIn(",".join(report.header()), response.content)
self.assertIn(self.CORRECT_CSV_NO_DATE_ITEMIZED_PURCHASE, response.content)
def test_report_csv_university_revenue_share(self):
report_type = 'university_revenue_share'
start_date = '1970-01-01'
end_date = '2100-01-01'
start_letter = 'A'
end_letter = 'Z'
self.login_user()
self.add_to_download_group(self.user)
response = self.client.post(reverse('payment_csv_report'), {'start_date': '1970-01-01',
'end_date': '2100-01-01',
'start_letter': 'A',
'end_letter': 'Z',
response = self.client.post(reverse('payment_csv_report'), {'start_date': start_date,
'end_date': end_date,
'start_letter': start_letter,
'end_letter': end_letter,
'requested_report': report_type})
self.assertEqual(response['Content-Type'], 'text/csv')
report = initialize_report(report_type)
report = initialize_report(report_type, start_date, end_date, start_letter, end_letter)
self.assertIn(",".join(report.header()), response.content)
# TODO add another test here
......
......@@ -4,6 +4,7 @@ from django.conf import settings
urlpatterns = patterns('shoppingcart.views', # nopep8
url(r'^postpay_callback/$', 'postpay_callback'), # Both the ~accept and ~reject callback pages are handled here
url(r'^receipt/(?P<ordernum>[0-9]*)/$', 'show_receipt'),
url(r'^csv_report/$', 'csv_report', name='payment_csv_report'),
)
if settings.FEATURES['ENABLE_SHOPPING_CART']:
......@@ -13,7 +14,6 @@ if settings.FEATURES['ENABLE_SHOPPING_CART']:
url(r'^clear/$', 'clear_cart'),
url(r'^remove_item/$', 'remove_item'),
url(r'^add/course/(?P<course_id>[^/]+/[^/]+/[^/]+)/$', 'add_course_to_cart', name='add_course_to_cart'),
url(r'^csv_report/$', 'csv_report', name='payment_csv_report'),
)
if settings.FEATURES.get('ENABLE_PAYMENT_FAKE'):
......
......@@ -29,13 +29,13 @@ REPORT_TYPES = [
]
def initialize_report(report_type):
def initialize_report(report_type, start_date, end_date, start_letter=None, end_letter=None):
"""
Creates the appropriate type of Report object based on the string report_type.
"""
for item in REPORT_TYPES:
if report_type in item:
return item[1]()
return item[1](start_date, end_date, start_letter, end_letter)
raise ReportTypeDoesNotExistException
@require_POST
......@@ -193,32 +193,31 @@ def csv_report(request):
"""
Downloads csv reporting of orderitems
"""
if not _can_download_report(request.user):
return HttpResponseForbidden(_('You do not have permission to view this page.'))
# TODO temp filler for start letter, end letter
if request.method == 'POST':
start_str = request.POST.get('start_date', '')
end_str = request.POST.get('end_date', '')
start_date = request.POST.get('start_date', '')
end_date = request.POST.get('end_date', '')
start_letter = request.POST.get('start_letter', '')
end_letter = request.POST.get('end_letter', '')
report_type = request.POST.get('requested_report', '')
try:
start_date = _get_date_from_str(start_str) + datetime.timedelta(days=0)
end_date = _get_date_from_str(end_str) + datetime.timedelta(days=1)
start_date = _get_date_from_str(start_date) + datetime.timedelta(days=0)
end_date = _get_date_from_str(end_date) + datetime.timedelta(days=1)
except ValueError:
# Error case: there was a badly formatted user-input date string
return _render_report_form(start_str, end_str, start_letter, end_letter, report_type, date_fmt_error=True)
return _render_report_form(start_date, end_date, start_letter, end_letter, report_type, date_fmt_error=True)
report = initialize_report(report_type)
items = report.rows(start_date, end_date, start_letter, end_letter)
report = initialize_report(report_type, start_date, end_date, start_letter, end_letter)
items = report.rows()
response = HttpResponse(mimetype='text/csv')
filename = "purchases_report_{}.csv".format(datetime.datetime.now(pytz.UTC).strftime("%Y-%m-%d-%H-%M-%S"))
response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
report.write_csv(response, start_date, end_date, start_letter, end_letter)
report.write_csv(response)
return response
elif request.method == 'GET':
......
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