diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py
index f37cec6d66..122f60910f 100644
--- a/lms/djangoapps/instructor/tests/test_api.py
+++ b/lms/djangoapps/instructor/tests/test_api.py
@@ -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
diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py
index 6d5cd38f25..6e9b65055e 100644
--- a/lms/djangoapps/instructor/views/api.py
+++ b/lms/djangoapps/instructor/views/api.py
@@ -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)
diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py
index 9cf522807d..054936135b 100644
--- a/lms/djangoapps/shoppingcart/models.py
+++ b/lms/djangoapps/shoppingcart/models.py
@@ -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.
diff --git a/lms/djangoapps/shoppingcart/pdf.py b/lms/djangoapps/shoppingcart/pdf.py
deleted file mode 100644
index 1cc9d48a3b..0000000000
--- a/lms/djangoapps/shoppingcart/pdf.py
+++ /dev/null
@@ -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", "
"), para_style)
- disclaimer_para = Paragraph(self.disclaimer_text.replace("\n", "
"), para_style)
- billing_address_para = Paragraph(self.billing_address_text.replace("\n", "
"), 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", "
"), 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)
diff --git a/lms/djangoapps/shoppingcart/tests/test_pdf.py b/lms/djangoapps/shoppingcart/tests/test_pdf.py
deleted file mode 100644
index 47db5d5d59..0000000000
--- a/lms/djangoapps/shoppingcart/tests/test_pdf.py
+++ /dev/null
@@ -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))
diff --git a/requirements/edx/base.in b/requirements/edx/base.in
index c557c6dee8..f5f246f617 100644
--- a/requirements/edx/base.in
+++ b/requirements/edx/base.in
@@ -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
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index f119a1ff1f..2498c474c9 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -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
diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt
index 67ea48ea83..f32cfa2203 100644
--- a/requirements/edx/development.txt
+++ b/requirements/edx/development.txt
@@ -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
diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt
index ec82f86616..f11ac17186 100644
--- a/requirements/edx/testing.txt
+++ b/requirements/edx/testing.txt
@@ -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