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