From e80265bb9ace5b91dbf47e7a2cc1e93dcc77723c Mon Sep 17 00:00:00 2001
From: Diana Huang <dkh@edx.org>
Date: Mon, 26 Aug 2019 15:48:35 -0400
Subject: [PATCH] Remove shoppingcart pdf generation.

DEPR-40
---
 lms/djangoapps/instructor/tests/test_api.py   |  20 -
 lms/djangoapps/instructor/views/api.py        |  11 -
 lms/djangoapps/shoppingcart/models.py         |  72 +--
 lms/djangoapps/shoppingcart/pdf.py            | 486 ------------------
 lms/djangoapps/shoppingcart/tests/test_pdf.py | 243 ---------
 requirements/edx/base.in                      |   1 -
 requirements/edx/base.txt                     |   1 -
 requirements/edx/development.txt              |   1 -
 requirements/edx/testing.txt                  |   1 -
 9 files changed, 2 insertions(+), 834 deletions(-)
 delete mode 100644 lms/djangoapps/shoppingcart/pdf.py
 delete mode 100644 lms/djangoapps/shoppingcart/tests/test_pdf.py

diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py
index f37cec6d66c..122f60910f5 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 6d5cd38f254..6e9b65055e6 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 9cf522807d8..054936135b6 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 1cc9d48a3b2..00000000000
--- 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", "<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)
diff --git a/lms/djangoapps/shoppingcart/tests/test_pdf.py b/lms/djangoapps/shoppingcart/tests/test_pdf.py
deleted file mode 100644
index 47db5d5d59c..00000000000
--- 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 c557c6dee8a..f5f246f617b 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 f119a1ff1f8..2498c474c92 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 67ea48ea83d..f32cfa22036 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 ec82f86616b..f11ac17186a 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
-- 
GitLab