{"id":33574266,"url":"https://github.com/loftylabs/django-hardcopy","last_synced_at":"2025-11-28T13:05:15.404Z","repository":{"id":47370147,"uuid":"98444397","full_name":"loftylabs/django-hardcopy","owner":"loftylabs","description":"Render PDFs from HTML in Python/Django using Headless Chrome","archived":false,"fork":false,"pushed_at":"2022-09-16T19:22:12.000Z","size":32,"stargazers_count":128,"open_issues_count":11,"forks_count":11,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-10-25T22:56:54.324Z","etag":null,"topics":["django","pdf-generation","python","python3"],"latest_commit_sha":null,"homepage":"http://hirelofty.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/loftylabs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-07-26T16:35:31.000Z","updated_at":"2025-05-10T19:44:07.000Z","dependencies_parsed_at":"2022-08-19T19:01:02.471Z","dependency_job_id":null,"html_url":"https://github.com/loftylabs/django-hardcopy","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/loftylabs/django-hardcopy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loftylabs%2Fdjango-hardcopy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loftylabs%2Fdjango-hardcopy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loftylabs%2Fdjango-hardcopy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loftylabs%2Fdjango-hardcopy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/loftylabs","download_url":"https://codeload.github.com/loftylabs/django-hardcopy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/loftylabs%2Fdjango-hardcopy/sbom","scorecard":{"id":596994,"data":{"date":"2025-08-11","repo":{"name":"github.com/loftylabs/django-hardcopy","commit":"2f8987b6b45311c1e7cfa1f5938481e798d4ab18"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.1,"checks":[{"name":"Code-Review","score":4,"reason":"Found 6/14 approved changesets -- score normalized to 4","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 22 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T23:22:51.823Z","repository_id":47370147,"created_at":"2025-08-20T23:22:51.823Z","updated_at":"2025-08-20T23:22:51.823Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286079811,"owners_count":27282121,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-26T02:00:06.075Z","response_time":193,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","pdf-generation","python","python3"],"created_at":"2025-11-28T13:02:30.672Z","updated_at":"2025-11-28T13:05:15.388Z","avatar_url":"https://github.com/loftylabs.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# django-hardcopy:  Render PDFs and PNGs in Django with headless Chrome\n\nChrome [introduced headless mode in v59](https://developers.google.com/web/updates/2017/04/headless-chrome) opening the possibility of using Chrome as a fast and elegant way of generating PDF data or PNG screenshots programatically via HTML.  `django-hardcopy` is an alternative to other projects which leverage `wkhtmltopdf`, a great tool but one that lacks the portability, ease of installation, the performance, and reliability of Chrome.\n\n\n## Requirements\n- Django\n- Chrome, Chromium, or Chrome Canary \u003e= v59\n- Currently only tested against Django 1.10+ and Python 3.6 (other versions may be supported, submit an issue if not!)\n\n## Installation\n\nInstall the library:\n\n    pip install django-hardcopy\n    \nInstall Chrome or a derivative:\n\n    apt-get install chromium-browser\n    \nSet your Chrome path (optional):\n\n    # settings.py\n    \n    CHROME_PATH = '/path/to/chrome-or-chromium'\n    \nThis can be useful if you want to use `chrome-canary` or `chromium-browser` (available by default in Ubuntu).  Django-hardcopy will attempt to smartly default the appropriate chrome path for your os. If you're on Mac OSX, just upgrade to the latest Chrome and you're good to go!\n\nSet the rendering window size (optional, default: 1280,720):\n \n    # settings.py\n    \n    CHROME_WINDOW_SIZE = '800,600'\n\n## Usage\n\nThe easiest way to use `django-hardcopy` is to use its CBV mixin:\n\n```python\nfrom django.views.generic import TemplateView\nfrom hardcopy.views import PDFViewMixin, PNGViewMixin\n\n\nclass MyPDFView(PDFViewMixin, TemplateView):\n    template_name = \"pdf_me.html\"\n\nclass MyPNGView(PNGViewMixin, TemplateView):\n    template_name = \"png_me.html\"\n    height = '1080'\n    width = '1920'\n\n```\n\nIt works with any Django Class Based View, and implements PDF or PNG rendering on the `GET` HTTP method. Further, if the `?html` querystring variable is provided the mixin will render the view normally for designing and debugging of the raw HTML.  The CBV mixin supports several options for extension and customization covered in the FAQ section.\n\nThere are two methods which implement a lower level API which can be used directly for PDFs:\n\n### file_to_pdf(input_file, output_file, **extra_args)\n\nArguments:\n- `input_file`:  An open for reading \"file-like\" object to read HTML from for rendering\n- `output_file`: An open for writing \"file-like\" object to write the PDF to after rendering\n- `**extra_args`:  See below\n\nThis function will read the contents of `input_file` (an HTML bytestring), render it with Chrome and store the binary PDF data in `output_file`.  Any `kwargs` are translated as commandline arguments to chrome when starting the headless browser for rendering, i.e.:\n\n```python\nfrom hardcopy import file_to_pdf\n\nextra_args = {\n    'virtual-time-budget': 6000\n}\n\nfile_to_pdf(open('myfile.html'), open('myfile.pdf'), **extra_args)\n# translates to --virtual-time-budget=6000 when starting chrome\n\nextra_args = {\n    'disable-gpu': None\n}\n\nfile_to_pdf(open('myfile.html'), open('myfile.pdf'), **extra_args)\n# translates to --disable-gpu when starting chrome (currently on by default and required by Chrome)\n\n```\n\n### bytestring_to_pdf(html_data, output_file, **extra_args)\n\nArguments:\n- `html_data`:  A bytestring of HTML data. _Note: We use bytestrings because the most common execution path is to generate a PDF from a rendered Django template response_\n- `output_file`: An open for writing \"file-like\" object to write the PDF to after rendering\n- `**extra_args`:  See below\n\nThis render the contents of `html_data` with Chrome and store the binary PDF data in `output_file`.  Any `kwargs` are translated as commandline arguments to chrome when starting the headless browser for rendering, i.e.:\n\n\n```python\nfrom hardcopy import bytestring_to_pdf\n\nextra_args = {\n    'virtual-time-budget': 6000\n}\n\nbytestring_to_pdf(b\"\u003chtml\u003e\u003ch1\u003eHello Chrome!\u003c/h1\u003e\u003c/html\u003e\", open('myfile.pdf'), **extra_args)\n# translates to --virtual-time-budget=6000 when starting chrome\n\nextra_args = {\n    'disable-gpu': None\n}\n\nbytestring_to_pdf(b\"\u003chtml\u003e\u003ch1\u003eHello Chrome!\u003c/h1\u003e\u003c/html\u003e\", open('myfile.pdf'), **extra_args)\n# translates to --disable-gpu when starting chrome (currently on by default and required by Chrome)\n```\n\nSimilar functions are available for PNG generation:\n\n### file_to_png(input_file, output_file, width, height, **extra_args)\n\nArguments:\n- `input_file`:  An open for reading \"file-like\" object to read HTML from for rendering\n- `output_file`: An open for writing \"file-like\" object to write the PNG to after rendering\n- `width`: width of the viewport in pixels\n- `height`: height of the viewport in pixels\n- `**extra_args`:  See above\n\n### bytestring_to_png(html_data, output_file, width, height, **extra_args)\n\nArguments:\n- `html_data`:  A bytestring of HTML data. _Note: We use bytestrings because the most common execution path is to generate a PNG from a rendered Django template response_\n- `output_file`: An open for writing \"file-like\" object to write the PNG to after rendering\n- `width`: width of the viewport in pixels\n- `height`: height of the viewport in pixels\n- `**extra_args`:  See below\n\n## FAQ\n\n- How do I configure a view to download the PDF/PNG file?\n  \n  Set the `download_attachment` property to `True`:\n  ```python\n  class MyView(PDFViewMixin, TemplateView):\n      download_attachment = True\n\n- How do I override the chrome window size defined in a view?\n  \n  Set the `chrome_window_size` property to a string of your choice:\n  ```python\n  class MyView(PDFViewMixin, TemplateView):\n      chrome_window_size = '1920,1600'\n  ```\n- How do I customize the file name of the generated PDF/PNG?\n  \n  Override the `get_filename` method of your view:\n  ```python\n  class MyView(PDFViewMixin, TemplateView):\n      def get_filename(self):\n          return \"my_file_{}.pdf\".format(now().strftime('Y-m-d'))\n  ```\n- How do I add context data with django-hardcopy ?\n\n  There's no magic here, simply override the `TemplateView.get_context_data` method,\n  like you would do in a normal view:\n  ```python\n  class MyView(PDFViewMixin, TemplateView):\n      def get_context_data(self, **kwargs):\n          context = super(MyView, self).get_context_data(**kwargs)\n          context['example_data'] = self.request.GET.get('example')\n          return context\n  ```\n- I want to process the rendered HTML content before it is converted to PDF or PNG, how to do this ?\n\n  Just override the mixin method `process_html_content` in your view:\n  ```python\n  class MyView(PDFViewMixin, TemplateView):\n      def process_html_content(self, content):\n          return make_absolute_paths(content)\n  ```\n\n ## Caveats\n \n ### Static files\nUnder the hood, django-hardcopy writes HTML content to a temporary file which is loaded in chrome via the `file://` protocol. This does no magic on handling static files, and since Django is not necessarily serving static files at the URL rendered in the template (say if `STATIC_URL` is set to a relative path like most development envirnoments).  In production environments where static assets are served via a media server or s3 bucket at an absoulte URL, this is probably fine.\n\nIn local development however, Chrome will recieve a connection refused error on attempts to load static files in templates included like `\u003clink href=\"{% static 'style.css' %}\" rel=\"stylesheet\"\u003e`.   The best workaround for this is to include static assets inline in PDF/PNG templates.\n\nA nice feature for the roadmap of `django-hardcopy` would be dynamic parsing of templates to convert linked static assets to inline assets automatically.  (PRs welcome :))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floftylabs%2Fdjango-hardcopy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Floftylabs%2Fdjango-hardcopy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Floftylabs%2Fdjango-hardcopy/lists"}