Remove shoppingcart pdf generation.

DEPR-40
This commit is contained in:
Diana Huang
2019-08-26 15:48:35 -04:00
parent 1cc925b624
commit e80265bb9a
9 changed files with 2 additions and 834 deletions

View File

@@ -79,7 +79,6 @@ from shoppingcart.models import (
PaidCourseRegistration,
RegistrationCodeRedemption
)
from shoppingcart.pdf import PDFInvoice
from student.models import (
ALLOWEDTOENROLL_TO_ENROLLED,
ALLOWEDTOENROLL_TO_UNENROLLED,
@@ -5131,25 +5130,6 @@ class TestCourseRegistrationCodes(SharedModuleStoreTestCase):
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
self.assertEqual(len(body.split('\n')), 11)
def test_pdf_file_throws_exception(self):
"""
test to mock the pdf file generation throws an exception
when generating registration codes.
"""
generate_code_url = reverse(
'generate_registration_codes', kwargs={'course_id': text_type(self.course.id)}
)
data = {
'total_registration_codes': 9, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com',
'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123',
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
}
with patch.object(PDFInvoice, 'generate_pdf', side_effect=Exception):
response = self.client.post(generate_code_url, data)
self.assertEqual(response.status_code, 200, response.content)
def test_get_codes_with_sale_invoice(self):
"""
Test to generate a response of all the course registration codes

View File

@@ -1809,12 +1809,6 @@ def generate_registration_codes(request, course_id):
dashboard=reverse('dashboard')
)
try:
pdf_file = sale_invoice.generate_pdf_invoice(course, course_price, int(quantity), float(sale_price))
except Exception: # pylint: disable=broad-except
log.exception('Exception at creating pdf file.')
pdf_file = None
from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
context = {
'invoice': sale_invoice,
@@ -1867,11 +1861,6 @@ def generate_registration_codes(request, course_id):
email.to = [recipient]
email.attach(u'RegistrationCodes.csv', csv_file.getvalue(), 'text/csv')
email.attach(u'Invoice.txt', invoice_attachment, 'text/plain')
if pdf_file is not None:
email.attach(u'Invoice.pdf', pdf_file.getvalue(), 'application/pdf')
else:
file_buffer = StringIO(_('pdf download unavailable right now, please contact support.'))
email.attach(u'pdf_unavailable.txt', file_buffer.getvalue(), 'text/plain')
email.send()
return registration_codes_csv("Registration_Codes.csv", registration_codes)

View File

@@ -10,7 +10,6 @@ import smtplib
from collections import namedtuple
from datetime import datetime, timedelta
from decimal import Decimal
from io import BytesIO
import pytz
import six
@@ -39,7 +38,6 @@ from courseware.courses import get_course_by_id
from edxmako.shortcuts import render_to_string
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangolib.markup import HTML, Text
from shoppingcart.pdf import PDFInvoice
from student.models import CourseEnrollment, EnrollStatusChange
from student.signals import UNENROLL_DONE
from track import segment
@@ -307,34 +305,6 @@ class Order(models.Model):
self.save()
return old_to_new_id_map
def generate_pdf_receipt(self, order_items):
"""
Generates the pdf receipt for the given order_items
and returns the pdf_buffer.
"""
items_data = []
for item in order_items:
item_total = item.qty * item.unit_cost
items_data.append({
'item_description': item.pdf_receipt_display_name,
'quantity': item.qty,
'list_price': item.get_list_price(),
'discount': item.get_list_price() - item.unit_cost,
'item_total': item_total
})
pdf_buffer = BytesIO()
PDFInvoice(
items_data=items_data,
item_id=str(self.id),
date=self.purchase_time,
is_invoice=False,
total_cost=self.total_cost,
payment_received=self.total_cost,
balance=0
).generate_pdf(pdf_buffer)
return pdf_buffer
def generate_registration_codes_csv(self, orderitems, site_name):
"""
this function generates the csv file
@@ -355,7 +325,7 @@ class Order(models.Model):
return csv_file, course_names
def send_confirmation_emails(self, orderitems, is_order_type_business, csv_file, pdf_file, site_name, course_names):
def send_confirmation_emails(self, orderitems, is_order_type_business, csv_file, site_name, course_names):
"""
send confirmation e-mail
"""
@@ -420,11 +390,6 @@ class Order(models.Model):
if csv_file:
email.attach(u'RegistrationCodesRedemptionUrls.csv', csv_file.getvalue(), 'text/csv')
if pdf_file is not None:
email.attach(u'ReceiptOrder{}.pdf'.format(str(self.id)), pdf_file.getvalue(), 'application/pdf')
else:
file_buffer = six.StringIO(_('pdf download unavailable right now, please contact support.'))
email.attach(u'pdf_not_available.txt', file_buffer.getvalue(), 'text/plain')
email.send()
except (smtplib.SMTPException, BotoServerError): # sadly need to handle diff. mail backends individually
log.error(u'Failed sending confirmation e-mail for order %d', self.id)
@@ -491,16 +456,10 @@ class Order(models.Model):
#
csv_file, course_names = self.generate_registration_codes_csv(orderitems, site_name)
try:
pdf_file = self.generate_pdf_receipt(orderitems)
except Exception: # pylint: disable=broad-except
log.exception('Exception at creating pdf file.')
pdf_file = None
try:
self.send_confirmation_emails(
orderitems, self.order_type == OrderTypes.BUSINESS,
csv_file, pdf_file, site_name, course_names
csv_file, site_name, course_names
)
except Exception: # pylint: disable=broad-except
# Catch all exceptions here, since the Django view implicitly
@@ -891,33 +850,6 @@ class Invoice(TimeStampedModel):
total = result.get('total', 0)
return total if total else 0
def generate_pdf_invoice(self, course, course_price, quantity, sale_price):
"""
Generates the pdf invoice for the given course
and returns the pdf_buffer.
"""
discount_per_item = float(course_price) - sale_price / quantity
list_price = course_price - discount_per_item
items_data = [{
'item_description': course.display_name,
'quantity': quantity,
'list_price': list_price,
'discount': discount_per_item,
'item_total': quantity * list_price
}]
pdf_buffer = BytesIO()
PDFInvoice(
items_data=items_data,
item_id=str(self.id),
date=datetime.now(pytz.utc),
is_invoice=True,
total_cost=float(self.total_amount),
payment_received=0,
balance=float(self.total_amount)
).generate_pdf(pdf_buffer)
return pdf_buffer
def snapshot(self):
"""Create a snapshot of the invoice.

View File

@@ -1,486 +0,0 @@
"""
Template for PDF Receipt/Invoice Generation
"""
from __future__ import absolute_import
import logging
from django.conf import settings
from django.utils.translation import ugettext as _
from PIL import Image
from reportlab.lib import colors
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.pdfgen.canvas import Canvas
from reportlab.platypus import Paragraph
from reportlab.platypus.tables import Table, TableStyle
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from xmodule.modulestore.django import ModuleI18nService
log = logging.getLogger("PDF Generation")
class NumberedCanvas(Canvas):
"""
Canvas child class with auto page-numbering.
"""
def __init__(self, *args, **kwargs):
"""
__init__
"""
Canvas.__init__(self, *args, **kwargs)
self._saved_page_states = []
def insert_page_break(self):
"""
Starts a new page.
"""
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
def current_page_count(self):
"""
Returns the page count in the current pdf document.
"""
return len(self._saved_page_states) + 1
def save(self):
"""
Adds page numbering to each page (page x of y)
"""
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
if num_pages > 1:
self.draw_page_number(num_pages)
Canvas.showPage(self)
Canvas.save(self)
def draw_page_number(self, page_count):
"""
Draws the String "Page x of y" at the bottom right of the document.
"""
self.setFontSize(7)
self.drawRightString(
200 * mm,
12 * mm,
_(u"Page {page_number} of {page_count}").format(page_number=self._pageNumber, page_count=page_count)
)
class PDFInvoice(object):
"""
PDF Generation Class
"""
def __init__(self, items_data, item_id, date, is_invoice, total_cost, payment_received, balance):
"""
Accepts the following positional arguments
items_data - A list having the following items for each row.
item_description - String
quantity - Integer
list_price - float
discount - float
item_total - float
id - String
date - datetime
is_invoice - boolean - True (for invoice) or False (for Receipt)
total_cost - float
payment_received - float
balance - float
"""
# From settings
self.currency = settings.PAID_COURSE_REGISTRATION_CURRENCY[1]
self.logo_path = configuration_helpers.get_value("PDF_RECEIPT_LOGO_PATH", settings.PDF_RECEIPT_LOGO_PATH)
self.cobrand_logo_path = configuration_helpers.get_value(
"PDF_RECEIPT_COBRAND_LOGO_PATH", settings.PDF_RECEIPT_COBRAND_LOGO_PATH
)
self.tax_label = configuration_helpers.get_value("PDF_RECEIPT_TAX_ID_LABEL", settings.PDF_RECEIPT_TAX_ID_LABEL)
self.tax_id = configuration_helpers.get_value("PDF_RECEIPT_TAX_ID", settings.PDF_RECEIPT_TAX_ID)
self.footer_text = configuration_helpers.get_value("PDF_RECEIPT_FOOTER_TEXT", settings.PDF_RECEIPT_FOOTER_TEXT)
self.disclaimer_text = configuration_helpers.get_value(
"PDF_RECEIPT_DISCLAIMER_TEXT", settings.PDF_RECEIPT_DISCLAIMER_TEXT,
)
self.billing_address_text = configuration_helpers.get_value(
"PDF_RECEIPT_BILLING_ADDRESS", settings.PDF_RECEIPT_BILLING_ADDRESS
)
self.terms_conditions_text = configuration_helpers.get_value(
"PDF_RECEIPT_TERMS_AND_CONDITIONS", settings.PDF_RECEIPT_TERMS_AND_CONDITIONS
)
self.brand_logo_height = configuration_helpers.get_value(
"PDF_RECEIPT_LOGO_HEIGHT_MM", settings.PDF_RECEIPT_LOGO_HEIGHT_MM
) * mm
self.cobrand_logo_height = configuration_helpers.get_value(
"PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM", settings.PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM
) * mm
# From Context
self.items_data = items_data
self.item_id = item_id
self.date = ModuleI18nService().strftime(date, 'SHORT_DATE')
self.is_invoice = is_invoice
self.total_cost = '{currency}{amount:.2f}'.format(currency=self.currency, amount=total_cost)
self.payment_received = '{currency}{amount:.2f}'.format(currency=self.currency, amount=payment_received)
self.balance = '{currency}{amount:.2f}'.format(currency=self.currency, amount=balance)
# initialize the pdf variables
self.margin = 15 * mm
self.page_width = letter[0]
self.page_height = letter[1]
self.min_clearance = 3 * mm
self.second_page_available_height = ''
self.second_page_start_y_pos = ''
self.first_page_available_height = ''
self.pdf = None
def is_on_first_page(self):
"""
Returns True if it's the first page of the pdf, False otherwise.
"""
return self.pdf.current_page_count() == 1
def generate_pdf(self, file_buffer):
"""
Takes in a buffer and puts the generated pdf into that buffer.
"""
self.pdf = NumberedCanvas(file_buffer, pagesize=letter)
self.draw_border()
y_pos = self.draw_logos()
self.second_page_available_height = y_pos - self.margin - self.min_clearance
self.second_page_start_y_pos = y_pos
y_pos = self.draw_title(y_pos)
self.first_page_available_height = y_pos - self.margin - self.min_clearance
y_pos = self.draw_course_info(y_pos)
y_pos = self.draw_totals(y_pos)
self.draw_footer(y_pos)
self.pdf.insert_page_break()
self.pdf.save()
def draw_border(self):
"""
Draws a big border around the page leaving a margin of 15 mm on each side.
"""
self.pdf.setStrokeColorRGB(0.5, 0.5, 0.5)
self.pdf.setLineWidth(0.353 * mm)
self.pdf.rect(self.margin, self.margin,
self.page_width - (self.margin * 2), self.page_height - (self.margin * 2),
stroke=True, fill=False)
@staticmethod
def load_image(img_path):
"""
Loads an image given a path. An absolute path is assumed.
If the path points to an image file, it loads and returns the Image object, None otherwise.
"""
try:
img = Image.open(img_path)
except IOError as ex:
log.exception(u'Pdf unable to open the image file: %s', str(ex))
img = None
return img
def draw_logos(self):
"""
Draws logos.
"""
horizontal_padding_from_border = self.margin + 9 * mm
vertical_padding_from_border = 11 * mm
img_y_pos = self.page_height - (
self.margin + vertical_padding_from_border + max(self.cobrand_logo_height, self.brand_logo_height)
)
# Left-Aligned cobrand logo
if self.cobrand_logo_path:
cobrand_img = self.load_image(self.cobrand_logo_path)
if cobrand_img:
img_width = float(cobrand_img.size[0]) / (float(cobrand_img.size[1]) / self.cobrand_logo_height)
self.pdf.drawImage(cobrand_img.filename, horizontal_padding_from_border, img_y_pos, img_width,
self.cobrand_logo_height, mask='auto')
# Right aligned brand logo
if self.logo_path:
logo_img = self.load_image(self.logo_path)
if logo_img:
img_width = float(logo_img.size[0]) / (float(logo_img.size[1]) / self.brand_logo_height)
self.pdf.drawImage(
logo_img.filename,
self.page_width - (horizontal_padding_from_border + img_width),
img_y_pos,
img_width,
self.brand_logo_height,
mask='auto'
)
return img_y_pos - self.min_clearance
def draw_title(self, y_pos):
"""
Draws the title, order/receipt ID and the date.
"""
if self.is_invoice:
title = (_('Invoice'))
id_label = (_('Invoice'))
else:
title = (_('Receipt'))
id_label = (_('Order'))
# Draw Title "RECEIPT" OR "INVOICE"
vertical_padding = 5 * mm
horizontal_padding_from_border = self.margin + 9 * mm
font_size = 21
self.pdf.setFontSize(font_size)
self.pdf.drawString(horizontal_padding_from_border, y_pos - vertical_padding - font_size / 2, title)
y_pos = y_pos - vertical_padding - font_size / 2 - self.min_clearance
horizontal_padding_from_border = self.margin + 11 * mm
font_size = 12
self.pdf.setFontSize(font_size)
y_pos = y_pos - font_size / 2 - vertical_padding
# Draw Order/Invoice No.
self.pdf.drawString(horizontal_padding_from_border, y_pos,
_(u'{id_label} # {item_id}').format(id_label=id_label, item_id=self.item_id))
y_pos = y_pos - font_size / 2 - vertical_padding
# Draw Date
self.pdf.drawString(
horizontal_padding_from_border, y_pos, _(u'Date: {date}').format(date=self.date)
)
return y_pos - self.min_clearance
def draw_course_info(self, y_pos):
"""
Draws the main table containing the data items.
"""
course_items_data = [
['', (_('Description')), (_('Quantity')), (_('List Price\nper item')), (_('Discount\nper item')),
(_('Amount')), '']
]
for row_item in self.items_data:
course_items_data.append([
'',
Paragraph(row_item['item_description'], getSampleStyleSheet()['Normal']),
row_item['quantity'],
'{currency}{list_price:.2f}'.format(list_price=row_item['list_price'], currency=self.currency),
'{currency}{discount:.2f}'.format(discount=row_item['discount'], currency=self.currency),
'{currency}{item_total:.2f}'.format(item_total=row_item['item_total'], currency=self.currency),
''
])
padding_width = 7 * mm
desc_col_width = 60 * mm
qty_col_width = 26 * mm
list_price_col_width = 21 * mm
discount_col_width = 21 * mm
amount_col_width = 40 * mm
course_items_table = Table(
course_items_data,
[
padding_width,
desc_col_width,
qty_col_width,
list_price_col_width,
discount_col_width,
amount_col_width,
padding_width
],
splitByRow=1,
repeatRows=1
)
course_items_table.setStyle(TableStyle([
#List Price, Discount, Amount data items
('ALIGN', (3, 1), (5, -1), 'RIGHT'),
# Amount header
('ALIGN', (5, 0), (5, 0), 'RIGHT'),
# Amount column (header + data items)
('RIGHTPADDING', (5, 0), (5, -1), 7 * mm),
# Quantity, List Price, Discount header
('ALIGN', (2, 0), (4, 0), 'CENTER'),
# Description header
('ALIGN', (1, 0), (1, -1), 'LEFT'),
# Quantity data items
('ALIGN', (2, 1), (2, -1), 'CENTER'),
# Lines below the header and at the end of the table.
('LINEBELOW', (0, 0), (-1, 0), 1.00, '#cccccc'),
('LINEBELOW', (0, -1), (-1, -1), 1.00, '#cccccc'),
# Innergrid around the data rows.
('INNERGRID', (1, 1), (-2, -1), 0.50, '#cccccc'),
# Entire table
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('TOPPADDING', (0, 0), (-1, -1), 2 * mm),
('BOTTOMPADDING', (0, 0), (-1, -1), 2 * mm),
('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
]))
rendered_width, rendered_height = course_items_table.wrap(0, 0)
table_left_padding = (self.page_width - rendered_width) / 2
split_tables = course_items_table.split(0, self.first_page_available_height)
if len(split_tables) > 1:
# The entire Table won't fit in the available space and requires splitting.
# Draw the part that can fit, start a new page
# and repeat the process with the rest of the table.
split_table = split_tables[0]
__, rendered_height = split_table.wrap(0, 0)
split_table.drawOn(self.pdf, table_left_padding, y_pos - rendered_height)
self.prepare_new_page()
split_tables = split_tables[1].split(0, self.second_page_available_height)
while len(split_tables) > 1:
split_table = split_tables[0]
__, rendered_height = split_table.wrap(0, 0)
split_table.drawOn(self.pdf, table_left_padding, self.second_page_start_y_pos - rendered_height)
self.prepare_new_page()
split_tables = split_tables[1].split(0, self.second_page_available_height)
split_table = split_tables[0]
__, rendered_height = split_table.wrap(0, 0)
split_table.drawOn(self.pdf, table_left_padding, self.second_page_start_y_pos - rendered_height)
else:
# Table will fit without the need for splitting.
course_items_table.drawOn(self.pdf, table_left_padding, y_pos - rendered_height)
if not self.is_on_first_page():
y_pos = self.second_page_start_y_pos
return y_pos - rendered_height - self.min_clearance
def prepare_new_page(self):
"""
Inserts a new page and includes the border and the logos.
"""
self.pdf.insert_page_break()
self.draw_border()
y_pos = self.draw_logos()
return y_pos
def draw_totals(self, y_pos):
"""
Draws the boxes containing the totals and the tax id.
"""
totals_data = [
[(_('Total')), self.total_cost],
[(_('Payment Received')), self.payment_received],
[(_('Balance')), self.balance]
]
if self.is_invoice:
# only print TaxID if we are generating an Invoice
totals_data.append(
['', u'{tax_label}: {tax_id}'.format(tax_label=self.tax_label, tax_id=self.tax_id)]
)
heights = 8 * mm
totals_table = Table(totals_data, 40 * mm, heights)
styles = [
# Styling for the totals table.
('ALIGN', (0, 0), (-1, -1), 'RIGHT'),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
# Styling for the Amounts cells
# NOTE: since we are not printing the TaxID for Credit Card
# based receipts, we need to change the cell range for
# these formatting rules
('RIGHTPADDING', (-1, 0), (-1, 2), 7 * mm),
('GRID', (-1, 0), (-1, 2), 3.0, colors.white),
('BACKGROUND', (-1, 0), (-1, 2), '#EEEEEE'),
]
totals_table.setStyle(TableStyle(styles))
__, rendered_height = totals_table.wrap(0, 0)
left_padding = 97 * mm
if y_pos - (self.margin + self.min_clearance) <= rendered_height:
# if space left on page is smaller than the rendered height, render the table on the next page.
self.prepare_new_page()
totals_table.drawOn(self.pdf, self.margin + left_padding, self.second_page_start_y_pos - rendered_height)
return self.second_page_start_y_pos - rendered_height - self.min_clearance
else:
totals_table.drawOn(self.pdf, self.margin + left_padding, y_pos - rendered_height)
return y_pos - rendered_height - self.min_clearance
def draw_footer(self, y_pos):
"""
Draws the footer.
"""
para_style = getSampleStyleSheet()['Normal']
para_style.fontSize = 8
footer_para = Paragraph(self.footer_text.replace("\n", "<br/>"), para_style)
disclaimer_para = Paragraph(self.disclaimer_text.replace("\n", "<br/>"), para_style)
billing_address_para = Paragraph(self.billing_address_text.replace("\n", "<br/>"), para_style)
footer_data = [
['', footer_para],
[(_('Billing Address')), ''],
['', billing_address_para],
[(_('Disclaimer')), ''],
['', disclaimer_para]
]
footer_style = [
# Styling for the entire footer table.
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
('FONTSIZE', (0, 0), (-1, -1), 9),
('TEXTCOLOR', (0, 0), (-1, -1), '#AAAAAA'),
# Billing Address Header styling
('LEFTPADDING', (0, 1), (0, 1), 5 * mm),
# Disclaimer Header styling
('LEFTPADDING', (0, 3), (0, 3), 5 * mm),
('TOPPADDING', (0, 3), (0, 3), 2 * mm),
# Footer Body styling
# ('BACKGROUND', (1, 0), (1, 0), '#EEEEEE'),
# Billing Address Body styling
('BACKGROUND', (1, 2), (1, 2), '#EEEEEE'),
# Disclaimer Body styling
('BACKGROUND', (1, 4), (1, 4), '#EEEEEE'),
]
if self.is_invoice:
terms_conditions_para = Paragraph(self.terms_conditions_text.replace("\n", "<br/>"), para_style)
footer_data.append([(_('TERMS AND CONDITIONS')), ''])
footer_data.append(['', terms_conditions_para])
# TERMS AND CONDITIONS header styling
footer_style.append(('LEFTPADDING', (0, 5), (0, 5), 5 * mm))
footer_style.append(('TOPPADDING', (0, 5), (0, 5), 2 * mm))
# TERMS AND CONDITIONS body styling
footer_style.append(('BACKGROUND', (1, 6), (1, 6), '#EEEEEE'))
footer_table = Table(footer_data, [5 * mm, 176 * mm])
footer_table.setStyle(TableStyle(footer_style))
__, rendered_height = footer_table.wrap(0, 0)
if y_pos - (self.margin + self.min_clearance) <= rendered_height:
self.prepare_new_page()
footer_table.drawOn(self.pdf, self.margin, self.margin + 5 * mm)

View File

@@ -1,243 +0,0 @@
"""
Tests for Pdf file
"""
from __future__ import absolute_import
import unittest
from datetime import datetime
from io import BytesIO
from django.conf import settings
from django.test.utils import override_settings
from six.moves import range
from shoppingcart.pdf import PDFInvoice
from shoppingcart.utils import parse_pages
PDF_RECEIPT_DISCLAIMER_TEXT = "THE SITE AND ANY INFORMATION, CONTENT OR SERVICES MADE AVAILABLE ON OR THROUGH " \
"THE SITE ARE PROVIDED \"AS IS\" AND \"AS AVAILABLE\" WITHOUT WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR" \
" OTHERWISE), INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A " \
"PARTICULAR PURPOSE AND NON-INFRINGEMENT, EXCEPT INSOFAR AS ANY SUCH IMPLIED WARRANTIES MAY NOT BE DISCLAIMED" \
" UNDER APPLICABLE LAW."
PDF_RECEIPT_BILLING_ADDRESS = "edX\n141 Portland St.\n9th Floor\nCambridge,\nMA 02139"
PDF_RECEIPT_FOOTER_TEXT = "EdX offers online courses that include opportunities for professor-to-student and" \
" student-to-student interactivity, individual assessment of a student's work and, for students who demonstrate" \
" their mastery of subjects, a certificate of achievement or other acknowledgment."
PDF_RECEIPT_TAX_ID = "46-0807740"
PDF_RECEIPT_TAX_ID_LABEL = "edX Tax ID"
PDF_RECEIPT_TERMS_AND_CONDITIONS = "Enrollments:\nEnrollments must be completed within 7 full days from the course " \
"start date.\nPayment Terms:\nPayment is due immediately. Preferred method of payment is wire transfer. Full " \
"instructions and remittance details will be included on your official invoice. Please note that our terms are " \
"net zero. For questions regarding payment instructions or extensions, please contact " \
"onlinex-registration@mit.edu and include the words \"payment question\" in your subject line.\nCancellations:" \
"\nCancellation requests must be submitted to onlinex-registration@mit.edu 14 days prior to the course start " \
"date to be eligible for a refund. If you submit a cancellation request within 14 days prior to the course start " \
"date, you will not be eligible for a refund. Please see our Terms of Service page for full details." \
"\nSubstitutions:\nThe MIT Professional Education Online X Programs office must receive substitution requests " \
"before the course start date in order for the request to be considered. Please email " \
"onlinex-registration@mit.edu to request a substitution.Please see our Terms of Service page for our detailed " \
"policies, including terms and conditions of use."
class TestPdfFile(unittest.TestCase):
"""
Unit test cases for pdf file generation
"""
def setUp(self):
super(TestPdfFile, self).setUp()
self.items_data = [self.get_item_data(1)]
self.item_id = '1'
self.date = datetime.now()
self.is_invoice = False
self.total_cost = 1000
self.payment_received = 1000
self.balance = 0
self.pdf_buffer = BytesIO()
def get_item_data(self, index, discount=0):
"""
return the dictionary with the dummy data
"""
return {
'item_description': u'Course %s Description' % index,
'quantity': index,
'list_price': 10,
'discount': discount,
'item_total': 10
}
@override_settings(
PDF_RECEIPT_DISCLAIMER_TEXT=PDF_RECEIPT_DISCLAIMER_TEXT,
PDF_RECEIPT_BILLING_ADDRESS=PDF_RECEIPT_BILLING_ADDRESS,
PDF_RECEIPT_FOOTER_TEXT=PDF_RECEIPT_FOOTER_TEXT,
PDF_RECEIPT_TAX_ID=PDF_RECEIPT_TAX_ID,
PDF_RECEIPT_TAX_ID_LABEL=PDF_RECEIPT_TAX_ID_LABEL,
PDF_RECEIPT_TERMS_AND_CONDITIONS=PDF_RECEIPT_TERMS_AND_CONDITIONS,
)
def test_pdf_receipt_configured_generation(self):
PDFInvoice(
items_data=self.items_data,
item_id=self.item_id,
date=self.date,
is_invoice=self.is_invoice,
total_cost=self.total_cost,
payment_received=self.payment_received,
balance=self.balance
).generate_pdf(self.pdf_buffer)
pdf_content = parse_pages(self.pdf_buffer, 'test_pass')
self.assertTrue(any('Receipt' in s for s in pdf_content))
self.assertTrue(any(str(self.total_cost) in s for s in pdf_content))
self.assertTrue(any(str(self.payment_received) in s for s in pdf_content))
self.assertTrue(any(str(self.balance) in s for s in pdf_content))
self.assertFalse(any('edX Tax ID' in s for s in pdf_content))
# PDF_RECEIPT_TERMS_AND_CONDITIONS not displayed in the receipt pdf
self.assertFalse(any(
'Enrollments:\nEnrollments must be completed within 7 full days from the course'
' start date.\nPayment Terms:\nPayment is due immediately.' in s for s in pdf_content
))
self.assertTrue(any('edX\n141 Portland St.\n9th Floor\nCambridge,\nMA 02139' in s for s in pdf_content))
def test_pdf_receipt_not_configured_generation(self):
PDFInvoice(
items_data=self.items_data,
item_id=self.item_id,
date=self.date,
is_invoice=self.is_invoice,
total_cost=self.total_cost,
payment_received=self.payment_received,
balance=self.balance
).generate_pdf(self.pdf_buffer)
pdf_content = parse_pages(self.pdf_buffer, 'test_pass')
self.assertTrue(any('Receipt' in s for s in pdf_content))
self.assertTrue(any(settings.PDF_RECEIPT_DISCLAIMER_TEXT in s for s in pdf_content))
self.assertTrue(any(settings.PDF_RECEIPT_BILLING_ADDRESS in s for s in pdf_content))
self.assertTrue(any(settings.PDF_RECEIPT_FOOTER_TEXT in s for s in pdf_content))
# PDF_RECEIPT_TERMS_AND_CONDITIONS not displayed in the receipt pdf
self.assertFalse(any(settings.PDF_RECEIPT_TERMS_AND_CONDITIONS in s for s in pdf_content))
@override_settings(
PDF_RECEIPT_DISCLAIMER_TEXT=PDF_RECEIPT_DISCLAIMER_TEXT,
PDF_RECEIPT_BILLING_ADDRESS=PDF_RECEIPT_BILLING_ADDRESS,
PDF_RECEIPT_FOOTER_TEXT=PDF_RECEIPT_FOOTER_TEXT,
PDF_RECEIPT_TAX_ID=PDF_RECEIPT_TAX_ID,
PDF_RECEIPT_TAX_ID_LABEL=PDF_RECEIPT_TAX_ID_LABEL,
PDF_RECEIPT_TERMS_AND_CONDITIONS=PDF_RECEIPT_TERMS_AND_CONDITIONS,
)
def test_pdf_receipt_file_item_data_pagination(self):
for i in range(2, 50):
self.items_data.append(self.get_item_data(i))
PDFInvoice(
items_data=self.items_data,
item_id=self.item_id,
date=self.date,
is_invoice=self.is_invoice,
total_cost=self.total_cost,
payment_received=self.payment_received,
balance=self.balance
).generate_pdf(self.pdf_buffer)
pdf_content = parse_pages(self.pdf_buffer, 'test_pass')
self.assertTrue(any('Receipt' in s for s in pdf_content))
self.assertTrue(any('Page 3 of 3' in s for s in pdf_content))
def test_pdf_receipt_file_totals_pagination(self):
for i in range(2, 48):
self.items_data.append(self.get_item_data(i))
PDFInvoice(
items_data=self.items_data,
item_id=self.item_id,
date=self.date,
is_invoice=self.is_invoice,
total_cost=self.total_cost,
payment_received=self.payment_received,
balance=self.balance
).generate_pdf(self.pdf_buffer)
pdf_content = parse_pages(self.pdf_buffer, 'test_pass')
self.assertTrue(any('Receipt' in s for s in pdf_content))
self.assertTrue(any('Page 3 of 3' in s for s in pdf_content))
@override_settings(PDF_RECEIPT_LOGO_PATH='wrong path')
def test_invalid_image_path(self):
PDFInvoice(
items_data=self.items_data,
item_id=self.item_id,
date=self.date,
is_invoice=self.is_invoice,
total_cost=self.total_cost,
payment_received=self.payment_received,
balance=self.balance
).generate_pdf(self.pdf_buffer)
pdf_content = parse_pages(self.pdf_buffer, 'test_pass')
self.assertTrue(any('Receipt' in s for s in pdf_content))
def test_pdf_receipt_file_footer_pagination(self):
for i in range(2, 44):
self.items_data.append(self.get_item_data(i))
PDFInvoice(
items_data=self.items_data,
item_id=self.item_id,
date=self.date,
is_invoice=self.is_invoice,
total_cost=self.total_cost,
payment_received=self.payment_received,
balance=self.balance
).generate_pdf(self.pdf_buffer)
pdf_content = parse_pages(self.pdf_buffer, 'test_pass')
self.assertTrue(any('Receipt' in s for s in pdf_content))
@override_settings(
PDF_RECEIPT_DISCLAIMER_TEXT=PDF_RECEIPT_DISCLAIMER_TEXT,
PDF_RECEIPT_BILLING_ADDRESS=PDF_RECEIPT_BILLING_ADDRESS,
PDF_RECEIPT_FOOTER_TEXT=PDF_RECEIPT_FOOTER_TEXT,
PDF_RECEIPT_TAX_ID=PDF_RECEIPT_TAX_ID,
PDF_RECEIPT_TAX_ID_LABEL=PDF_RECEIPT_TAX_ID_LABEL,
PDF_RECEIPT_TERMS_AND_CONDITIONS=PDF_RECEIPT_TERMS_AND_CONDITIONS,
)
def test_pdf_invoice_with_settings_from_patch(self):
self.is_invoice = True
PDFInvoice(
items_data=self.items_data,
item_id=self.item_id,
date=self.date,
is_invoice=self.is_invoice,
total_cost=self.total_cost,
payment_received=self.payment_received,
balance=self.balance
).generate_pdf(self.pdf_buffer)
pdf_content = parse_pages(self.pdf_buffer, 'test_pass')
self.assertTrue(any('46-0807740' in s for s in pdf_content))
self.assertTrue(any('Invoice' in s for s in pdf_content))
self.assertTrue(any(str(self.total_cost) in s for s in pdf_content))
self.assertTrue(any(str(self.payment_received) in s for s in pdf_content))
self.assertTrue(any(str(self.balance) in s for s in pdf_content))
self.assertTrue(any('edX Tax ID' in s for s in pdf_content))
self.assertTrue(any(
'Enrollments:\nEnrollments must be completed within 7 full'
' days from the course start date.\nPayment Terms:\nPayment'
' is due immediately.' in s for s in pdf_content))
def test_pdf_invoice_with_default_settings(self):
self.is_invoice = True
PDFInvoice(
items_data=self.items_data,
item_id=self.item_id,
date=self.date,
is_invoice=self.is_invoice,
total_cost=self.total_cost,
payment_received=self.payment_received,
balance=self.balance
).generate_pdf(self.pdf_buffer)
pdf_content = parse_pages(self.pdf_buffer, 'test_pass')
self.assertTrue(any(settings.PDF_RECEIPT_TAX_ID in s for s in pdf_content))
self.assertTrue(any('Invoice' in s for s in pdf_content))
self.assertTrue(any(settings.PDF_RECEIPT_TERMS_AND_CONDITIONS in s for s in pdf_content))

View File

@@ -129,7 +129,6 @@ python3-openid ; python_version>='3'
python3-saml
pyuca==1.1 # For more accurate sorting of translated country names in django-countries
recommender-xblock # https://github.com/edx/RecommenderXBlock
reportlab # Used for shopping cart's pdf invoice/receipt generation
rest-condition # DRF's recommendation for supporting complex permissions
rfc6266-parser # Used to generate Content-Disposition headers.
social-auth-app-django<3.0.0

View File

@@ -209,7 +209,6 @@ pyuca==1.1
pyyaml==5.1.2
recommender-xblock==1.4.4
redis==2.10.6
reportlab==3.5.26
requests-oauthlib==1.1.0
requests==2.22.0
rest-condition==1.0.3

View File

@@ -282,7 +282,6 @@ radon==4.0.0
recommender-xblock==1.4.4
recommonmark==0.6.0
redis==2.10.6
reportlab==3.5.26
requests-oauthlib==1.1.0
requests==2.22.0
rest-condition==1.0.3

View File

@@ -272,7 +272,6 @@ pyyaml==5.1.2
radon==4.0.0
recommender-xblock==1.4.4
redis==2.10.6
reportlab==3.5.26
requests-oauthlib==1.1.0
requests==2.22.0
rest-condition==1.0.3