def test_override_pdf_kwargs(self):
        """Test settings override of margin (should override the four default margins)"""

        defaults = clean_pdf_kwargs()
        self.assertEqual(convert_to_inches('2cm'), defaults['marginLeft'])
        self.assertEqual(convert_to_inches('2cm'), defaults['marginRight'])
        self.assertEqual(convert_to_inches('2cm'), defaults['marginTop'])
        self.assertEqual(convert_to_inches('2cm'), defaults['marginBottom'])
Пример #2
0
    def test_unit_conversions_uppercase(self):

        # uppercase is apparently valid CSS. And chromepdf currently takes it. So, preserve that behavior.
        self.assertEqual(1, convert_to_inches('2.54CM'))
        self.assertEqual(1, convert_to_inches('2.54Cm'))

        # uppercase has never worked for this function and I see no reason to change that.
        # must user lowercase. maybe this is inconsistent?
        with self.assertRaises(ValueError):
            convert_to_unit(1, 'IN')
        with self.assertRaises(ValueError):
            convert_to_unit(1, 'In')
    def test_override_pdf_kwargs_three_way_margins(self):
        """
        pdf_kwargs should override everything
        settings' PDF_KWARGS should override only the true defaults.
        True defaults should be final fallback if no overrides were provided anywhere
        """

        pdf_kwargs = {'marginLeft': '3cm', 'marginBottom': '4cm'}
        cleaned = clean_pdf_kwargs(**pdf_kwargs)
        self.assertEqual(convert_to_inches('3cm'), cleaned['marginRight'])  # override in settings
        self.assertEqual(convert_to_inches('3cm'), cleaned['marginLeft'])  # override in pdf_kwargs (takes priority over settings)
        self.assertEqual(convert_to_inches('4cm'), cleaned['marginBottom'])  # override in pdf_kwargs
        self.assertEqual(convert_to_inches('1cm'), cleaned['marginTop'])  # true default (no overrides)
Пример #4
0
    def test_convert_return_types(self):
        """Return types must always be floats or ints (since they are JSON-serializable). Otherwise, Selenium will fail when creating JSON."""

        JSON_NUMERIC_TYPES = (int, float)

        self.assertIsInstance(convert_to_inches(1), JSON_NUMERIC_TYPES)
        self.assertIsInstance(convert_to_inches('1in'), JSON_NUMERIC_TYPES)
        self.assertIsInstance(convert_to_inches('.75in'), JSON_NUMERIC_TYPES)
        # Important! Decimal is NOT json-serializable. It must be converted to int/float
        self.assertIsInstance(convert_to_inches(Decimal('1.75')), JSON_NUMERIC_TYPES)

        self.assertIsInstance(convert_to_unit(1, 'in'), JSON_NUMERIC_TYPES)
        self.assertIsInstance(convert_to_unit('1in', 'in'), JSON_NUMERIC_TYPES)
        self.assertIsInstance(convert_to_unit('.75in', 'in'), JSON_NUMERIC_TYPES)
        # Important! Decimal is NOT json-serializable. It must be converted to int/float
        self.assertIsInstance(convert_to_unit(Decimal('1.75'), 'in'), JSON_NUMERIC_TYPES)

        # string lengths MUST provide a unit type. EG '1in' not '1'
        with self.assertRaises(ValueError):
            convert_to_inches('1')
        with self.assertRaises(ValueError):
            convert_to_unit('1', 'in')

        # test invalid unit types
        with self.assertRaises(ValueError):
            convert_to_unit('1', 'zz')

        # CSS does not allow whitespace in length values, so we do not, either
        with self.assertRaises(ValueError):
            convert_to_inches('1 in')
        with self.assertRaises(ValueError):
            convert_to_unit('1 in', 'in')
Пример #5
0
    def test_clean_pdf_kwargs_priority(self):
        """
        Ensure priority of overrides:
        keyword arguments > Django setting > hardcoded DEFAULT_PDF_KWARGS
        """

        kwargs = clean_pdf_kwargs(marginTop='9cm')
        self.assertEqual(kwargs['marginTop'], convert_to_inches(
            '9cm'))  # overridden by keyword arg (overrides Django setting)
        self.assertEqual(
            kwargs['marginBottom'],
            convert_to_inches('7cm'))  # overridden by Django setting
        self.assertEqual(kwargs['marginLeft'],
                         convert_to_inches('1cm'))  # DEFAULT_PDF_KWARGS value
Пример #6
0
    def test_clean_pdf_kwargs_priority2(self):
        """
        Ensure convenience Django settings still get overridden by subsetted keyword arguments.
        """

        kwargs = clean_pdf_kwargs(marginTop='3cm')
        self.assertEqual(
            kwargs['marginTop'],
            convert_to_inches('3cm'))  # overridden by keyword argument
        self.assertEqual(
            kwargs['marginBottom'],
            convert_to_inches('2cm'))  # overridden by Django setting
        self.assertEqual(
            kwargs['marginLeft'],
            convert_to_inches('2cm'))  # overridden by Django setting
        self.assertEqual(
            kwargs['marginRight'],
            convert_to_inches('2cm'))  # overridden by Django setting
Пример #7
0
    def test_unit_conversions(self):

        self.assertEqual(1, convert_to_inches('25.4mm'))
        self.assertEqual(1, convert_to_inches('2.54cm'))
        self.assertEqual(1, convert_to_inches('96px'))
        self.assertEqual(1, convert_to_inches('1in'))

        self.assertEqual(0.0394, round(convert_to_inches('1mm'), 4))
        self.assertEqual(0.3937, round(convert_to_inches('1cm'), 4))
        self.assertEqual(0.0104, round(convert_to_inches('1px'), 4))
        self.assertEqual(1, round(convert_to_inches('1in'), 4))

        self.assertEqual(25.4, convert_to_unit(1, 'mm'))
        self.assertEqual(2.54, convert_to_unit(1, 'cm'))
        self.assertEqual(96, convert_to_unit(1, 'px'))
        self.assertEqual(1, convert_to_unit(1, 'in'))

        self.assertEqual(25.4, convert_to_unit('96px', 'mm'))
        self.assertEqual(2.54, convert_to_unit('96px', 'cm'))
        self.assertEqual(96, convert_to_unit('96px', 'px'))
        self.assertEqual(1, convert_to_unit('96px', 'in'))

        # Allow decimal, int and float
        self.assertEqual(1.75, convert_to_inches(Decimal('1.75')))
        self.assertEqual(1.75, convert_to_inches(1.75))
        self.assertEqual(2, convert_to_inches(2))

        # <1 conversion
        self.assertEqual(0.75, convert_to_inches('0.75in'))  # leading zero
        self.assertEqual(0.75, convert_to_inches('.75in'))  # no leading zero
        self.assertEqual(0.75, convert_to_inches(Decimal('0.75')))
        self.assertEqual(0.75, convert_to_inches(Decimal('.75')))
        self.assertEqual(0.75, convert_to_inches(.75))

        # zero conversion (ensure no division by zero issues)
        self.assertEqual(0, convert_to_inches('0mm'))
        self.assertEqual(0, convert_to_inches('0in'))
        self.assertEqual(0, convert_to_inches(Decimal('0.0')))
        self.assertEqual(0, convert_to_inches(Decimal('0')))
        self.assertEqual(0, convert_to_inches(0))

        self.assertEqual(0, convert_to_unit('0mm', 'cm'))
        self.assertEqual(0, convert_to_unit('0in', 'cm'))
        self.assertEqual(0, convert_to_unit(0, 'cm'))
Пример #8
0
    def test_clean_pdf_kwargs_settings(self):
        """Make sure Django settings overrides are accounted for."""

        kwargs = clean_pdf_kwargs()
        self.assertEqual(kwargs['marginTop'], convert_to_inches('8cm'))
Пример #9
0
    def test_clean_pdf_kwargs_margins(self):

        DEFAULTS = get_default_pdf_kwargs()

        margin_kwargs = ('marginTop', 'marginBottom', 'marginLeft',
                         'marginRight')

        # test setting each margin individually
        for k in margin_kwargs:
            with self.subTest(k=k):
                kwargs = clean_pdf_kwargs(**{k: 2})
                self.assertEqual(kwargs[k], 2)  # overrides the one
                for k2 in margin_kwargs:
                    if k != k2:
                        self.assertEqual(kwargs[k2],
                                         DEFAULTS[k2])  # leaves others alone

        # set all margins to different values, in various formats
        kwargs = clean_pdf_kwargs(marginTop=3,
                                  marginBottom='2.5cm',
                                  marginLeft='10mm',
                                  marginRight='30px')
        self.assertEqual(kwargs['marginTop'], 3)
        self.assertEqual(kwargs['marginBottom'], convert_to_inches('2.5cm'))
        self.assertEqual(kwargs['marginLeft'], convert_to_inches('10mm'))
        self.assertEqual(kwargs['marginRight'], convert_to_inches('30px'))

        # set all four using the 'margin' kwarg:
        kwargs = clean_pdf_kwargs(margin='11mm')
        self.assertTrue('margin' not in kwargs)
        self.assertEqual(kwargs['marginTop'], convert_to_inches('11mm'))
        self.assertEqual(kwargs['marginBottom'], convert_to_inches('11mm'))
        self.assertEqual(kwargs['marginLeft'], convert_to_inches('11mm'))
        self.assertEqual(kwargs['marginRight'], convert_to_inches('11mm'))

        # set all four using margin, but override some as well
        kwargs = clean_pdf_kwargs(margin='11mm',
                                  marginTop='5mm',
                                  marginBottom='6mm')
        self.assertTrue('margin' not in kwargs)
        self.assertEqual(kwargs['marginTop'], convert_to_inches('5mm'))
        self.assertEqual(kwargs['marginBottom'], convert_to_inches('6mm'))
        self.assertEqual(kwargs['marginLeft'], convert_to_inches('11mm'))
        self.assertEqual(kwargs['marginRight'], convert_to_inches('11mm'))

        # cannot set margins to None
        for m in margin_kwargs:
            with self.subTest(m=m):
                with self.assertRaises(TypeError):
                    _kwargs = clean_pdf_kwargs(**{m: None})
Пример #10
0
    def test_clean_pdf_kwargs_pagesizes(self):

        DEFAULTS = get_default_pdf_kwargs()

        # set the height, width=default
        kwargs = clean_pdf_kwargs(paperWidth=12)
        self.assertEqual(kwargs['paperWidth'], 12)
        self.assertEqual(kwargs['paperHeight'], DEFAULTS['paperHeight'])

        # set the width, height=default
        kwargs = clean_pdf_kwargs(paperHeight=12)
        self.assertEqual(kwargs['paperWidth'], DEFAULTS['paperWidth'])
        self.assertEqual(kwargs['paperHeight'], 12)

        # set width and height to inches
        kwargs = clean_pdf_kwargs(paperWidth=10.5, paperHeight=12.5)
        self.assertEqual(kwargs['paperWidth'], 10.5)
        self.assertEqual(kwargs['paperHeight'], 12.5)

        # set width and height to non-inches value (lowercase)
        kwargs = clean_pdf_kwargs(paperWidth='8cm', paperHeight='12cm')
        self.assertEqual(kwargs['paperWidth'], convert_to_inches('8cm'))
        self.assertEqual(kwargs['paperHeight'], convert_to_inches('12cm'))

        # set width and height to non-inches value (uppercase)
        kwargs = clean_pdf_kwargs(paperWidth='8CM', paperHeight='12CM')
        self.assertEqual(kwargs['paperWidth'], convert_to_inches('8cm'))
        self.assertEqual(kwargs['paperHeight'], convert_to_inches('12cm'))

        # set width and height to float inch string values
        kwargs = clean_pdf_kwargs(paperWidth='8.0in', paperHeight='12.0in')
        self.assertEqual(kwargs['paperWidth'], convert_to_inches(8))
        self.assertEqual(kwargs['paperHeight'], convert_to_inches(12))

        # raise ValueErrors for bad unit types
        with self.assertRaises(ValueError):
            _kwargs = clean_pdf_kwargs(paperWidth='8zz')
        with self.assertRaises(ValueError):
            _kwargs = clean_pdf_kwargs(paperHeight='8zz')
        with self.assertRaises(ValueError):
            _kwargs = clean_pdf_kwargs(paperHeight='inin')
        with self.assertRaises(ValueError):
            _kwargs = clean_pdf_kwargs(paperHeight='888')
        with self.assertRaises(ValueError):
            _kwargs = clean_pdf_kwargs(paperHeight='')
        # disallow whitespace?
        with self.assertRaises(ValueError):
            _kwargs = clean_pdf_kwargs(
                paperHeight='8 in')  # isn't valid CSS so don't allow it here.
        with self.assertRaises(ValueError):
            _kwargs = clean_pdf_kwargs(paperHeight=' 8in')
        with self.assertRaises(ValueError):
            _kwargs = clean_pdf_kwargs(paperHeight='8in ')
        # type error (these cannot be None)
        with self.assertRaises(TypeError):
            _kwargs = clean_pdf_kwargs(paperHeight=None)
        with self.assertRaises(TypeError):
            _kwargs = clean_pdf_kwargs(paperWidth=None)

        # raise ValueError if paperFormat is not recognized
        with self.assertRaises(ValueError):
            kwargs = clean_pdf_kwargs(paperFormat='A9')

        # set a paperFormat (lowercase)
        kwargs = clean_pdf_kwargs(paperFormat='a4')
        self.assertTrue(
            'paperFormat' not in
            kwargs)  # should have affected width and height, then be ditched.
        self.assertEqual(kwargs['paperWidth'], PAPER_FORMATS['a4']['width'])
        self.assertEqual(kwargs['paperHeight'], PAPER_FORMATS['a4']['height'])

        # set a paperFormat (uppercase)
        kwargs = clean_pdf_kwargs(paperFormat='A4')
        self.assertTrue(
            'paperFormat' not in
            kwargs)  # should have affected width and height, then be ditched.
        self.assertEqual(kwargs['paperWidth'], PAPER_FORMATS['a4']['width'])
        self.assertEqual(kwargs['paperHeight'], PAPER_FORMATS['a4']['height'])

        # raise ValueError if paperFormat passed along with paperWidth and/or paperHeight
        with self.assertRaises(ValueError):
            kwargs = clean_pdf_kwargs(paperFormat='A4', paperWidth=10.5)
        with self.assertRaises(ValueError):
            kwargs = clean_pdf_kwargs(paperFormat='A4', paperHeight=12.5)
        with self.assertRaises(ValueError):
            kwargs = clean_pdf_kwargs(paperFormat='A4',
                                      paperWidth=10.5,
                                      paperHeight=12.5)
Пример #11
0
def clean_pdf_kwargs(**options):
    """
    Clean the pdf_kwargs into a format that Page.printToPDF accepts.
    Our defaults should match the default arguments of the Page.printToPDF API.
    For more information, see: https://chromedevtools.github.io/devtools-protocol/1-3/Page/#method-printToPDF

    :param scale: Scale of the PDF. Default 1.
    :param landscape: True to use landscape mode. Default False.

    :param displayHeaderFooter: True to display header and footer. Default False.
    :param headerTemplate: HTML containing the header for all pages. Default is an empty string.
        You may pass html tags with specific classes in order to insert values:
            * date: formatted print date
            * title: document title
            * url: document location
            * pageNumber: current page number
            * totalPages: total pages in the document
        For example, <span class="title"></span> would generate span containing the title.
    :param footerTemplate: HTML containing the footer for all pages. Default is an empty string.
        You may pass html tags with specific classes in order to insert values (same as above)
    :param printBackground: True to print background graphics. Default False.

    :param paperWidth: Width of the paper, in inches. Can also use some CSS string values, like "30cm". Default: 8.5
    :param paperHeight: Height of the paper, in inches. Can also use some CSS string values, like "30cm". Default: 11
    :param paperFormat: A string indicating a paper size format, such as "letter" or "A4". Case-insensitive.
        This will override paperWidth and paperHeight.
        Not part of Page.printToPDF API.  Provided for convenience.

    :param margin: Shortcut used to set all four margin values at once.
        Not part of Page.printToPDF API.  Provided for convenience.
    :param marginTop: Top margin. Default '1cm'
    :param marginBottom: Bottom margin. Default '1cm'.
    :param marginLeft: Left margin. Default '1cm'.
    :param marginRight: Right margin. Default '1cm'.

    :param pageRanges: String indicating page ranges to use. Example: '1-5, 8, 11-13'
    :param ignoreInvalidPageRanges: If True, will silently ignore invalid "pageRanges" values. Default False.

    :param _defaults: Internal-only. An optional dict of default parameter overrides that have already been 'cleaned'.

    :return: A dict containing an options dict that be used for Page.printToPDF
    """

    if '_defaults' in options:
        defaults = options.pop('_defaults')
    else:
        defaults = get_default_pdf_kwargs()

    try:
        scale = float(options.get(
            'scale',
            defaults['scale']))  # can be int or float, or numeric string
    except ValueError:  # passed a character string?
        raise TypeError(
            'You must pass a numeric value for the "scale" of the PDF.')

    landscape = bool(options.get('landscape', defaults['landscape']))

    displayHeaderFooter = bool(
        options.get('displayHeaderFooter', defaults['displayHeaderFooter']))
    headerTemplate = options.get('headerTemplate', defaults['headerTemplate'])
    footerTemplate = options.get('footerTemplate', defaults['footerTemplate'])
    printBackground = bool(
        options.get('printBackground', defaults['printBackground']))

    paperWidth = defaults['paperWidth']
    paperHeight = defaults['paperHeight']
    if 'paperFormat' in options:  # convenience option that's not part of Chrome's API
        if options['paperFormat'].lower() not in PAPER_FORMATS:
            raise ValueError('Unrecognized paper format: "%s"' %
                             options['paperFormat'])
        if 'paperWidth' in options or 'paperHeight' in options:
            raise ValueError(
                'Cannot pass a paperFormat at the same time as a paperWidth/paperHeight.'
            )

        paperFormat = PAPER_FORMATS.get(options.pop(
            'paperFormat').lower())  # pop it so we can validate options below
        paperWidth = paperFormat['width']
        paperHeight = paperFormat['height']
    else:
        if 'paperWidth' in options:
            paperWidth = convert_to_inches(options['paperWidth'])
        if 'paperHeight' in options:
            paperHeight = convert_to_inches(options['paperHeight'])

    if paperWidth is None:
        raise TypeError('You must set a paperWidth for this PDF.')
    if paperHeight is None:
        raise TypeError('You must set a paperHeight for this PDF.')

    marginTop = defaults['marginTop']
    marginBottom = defaults['marginBottom']
    marginLeft = defaults['marginLeft']
    marginRight = defaults['marginRight']

    # margin affects all four sides. margin in options will override default side-specific defaults.
    if 'margin' in options:
        margin = convert_to_inches(options.pop('margin'))
        marginTop = marginBottom = marginLeft = marginRight = margin

    # margin overrides for specific sides
    marginTop = convert_to_inches(options.get('marginTop', marginTop))
    marginBottom = convert_to_inches(options.get('marginBottom', marginBottom))
    marginLeft = convert_to_inches(options.get('marginLeft', marginLeft))
    marginRight = convert_to_inches(options.get('marginRight', marginRight))

    if marginTop is None:
        raise TypeError('You cannot set marginTop to None')
    if marginBottom is None:
        raise TypeError('You cannot set marginBottom to None')
    if marginLeft is None:
        raise TypeError('You cannot set marginLeft to None')
    if marginRight is None:
        raise TypeError('You cannot set marginRight to None')

    pageRanges = str(options.get('pageRanges', defaults['pageRanges']))
    ignoreInvalidPageRanges = bool(
        options.get('ignoreInvalidPageRanges',
                    defaults['ignoreInvalidPageRanges']))
    # transferMode: not applicable.

    #     preferCSSPageSize = options.get('preferCSSPageSize','')

    # the actual dict that we will pass to Chrome (convenience kwargs like margin and paperFormat are removed)
    parameters = dict(
        scale=scale,
        landscape=landscape,
        #         preferCSSPageSize=preferCSSPageSize, # too new, causes error
        displayHeaderFooter=displayHeaderFooter,
        headerTemplate=headerTemplate,
        footerTemplate=footerTemplate,
        printBackground=printBackground,
        paperWidth=paperWidth,
        paperHeight=paperHeight,
        marginTop=marginTop,
        marginBottom=marginBottom,
        marginLeft=marginLeft,
        marginRight=marginRight,
        pageRanges=pageRanges,
        ignoreInvalidPageRanges=ignoreInvalidPageRanges,
    )

    # check for bad options
    parameters_keys = set(parameters.keys())
    options_keys = set(options.keys())
    if not options_keys.issubset(parameters_keys):
        raise ValueError(
            'Unrecognized pdf_kwargs passed to generate_pdf(): %s' %
            (', '.join(options_keys - parameters_keys)))

    return parameters
Пример #12
0
from chromepdf.conf import get_chromepdf_settings_dict
from chromepdf.sizes import PAPER_FORMATS, convert_to_inches

# Specified in printToPDF API - https://chromedevtools.github.io/devtools-protocol/1-3/Page/#method-printToPDF
# These are the "TRUE" defaults.
# If no overrides are provided in Django's settings.CHROMEPDF['PDF_KWARGS'], and no pdf_kwargs are passed to render functions,
# Then these are the parameters that would be used and passed to Chrome.
# You do NOT need to call convert_to_inches() when overriding these values yourself.
DEFAULT_PDF_KWARGS = dict(
    landscape=False,
    displayHeaderFooter=False,
    printBackground=False,
    scale=1,
    paperWidth=convert_to_inches('8.5in'),
    paperHeight=convert_to_inches('11in'),
    marginTop=convert_to_inches('1cm'),
    marginLeft=convert_to_inches('1cm'),
    marginRight=convert_to_inches('1cm'),
    marginBottom=convert_to_inches('1cm'),
    pageRanges='',
    ignoreInvalidPageRanges=False,
    headerTemplate='',
    footerTemplate='',
)


def get_default_pdf_kwargs():
    """
    Return the default pdf_kwargs used for rendering a PDF in Chrome.
    Use the values specified in Django settings.CHROMEPDF['PDF_KWARGS'] if they exist.
    Otherwise, fallback to the `DEFAULT_PDF_KWARGS` above.