-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 4aa722e
Showing
16 changed files
with
578 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# IDE Folders | ||
/.idea/* | ||
/.vscode/* | ||
|
||
# Python Files | ||
*.pyc | ||
|
||
# SQL Files | ||
*.db | ||
*.sqlite3 | ||
*.sql | ||
|
||
# Test's | ||
/htmlcov/* | ||
.coverage/ | ||
.coverage* | ||
coverage.xml | ||
pylint.txt | ||
|
||
# SonarQube | ||
.scannerwork | ||
|
||
# Others | ||
/venv/* | ||
/dist/* | ||
/drf_pdf_renderer.egg-info/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2021 Shinneider | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
of the Software, and to permit persons to whom the Software is furnished to | ||
do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | ||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | ||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
include LICENSE | ||
include README.md | ||
|
||
recursive-include drf_pdf_renderer/templates * | ||
recursive-include drf_pdf_renderer/static * | ||
recursive-include drf_pdf_renderer/locale * | ||
global-exclude *.py[co] | ||
prune __pycache__ | ||
prune test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
DRF PDF Renderer | ||
= | ||
A simplistic/very extendable pdf renderer. | ||
|
||
If you use or like the project, click `Star` and `Watch` to generate metrics and i evaluate project continuity. | ||
|
||
OBS | ||
= | ||
This project is a `Beta` version, however is used in production in a big project (with custom pdf template, and manually fields instead of automatic). | ||
however, due to my low availability, updates may take some time. | ||
but i will keep an eye on the PR. | ||
|
||
# Install: | ||
pip install drf-pdf-renderer | ||
|
||
# Usage: | ||
1. Add to your `INSTALLED_APPS`, in `settings.py`: | ||
``` | ||
INSTALLED_APPS = [ | ||
... | ||
'drf_pdf_renderer', | ||
... | ||
] | ||
``` | ||
|
||
1. In your file: | ||
``` | ||
from rest_framework.settings import api_settings | ||
from drf_pdf_renderer.renderer import PDFRendererPaginated | ||
class YourView(...) | ||
renderer_classes = (*api_settings.DEFAULT_RENDERER_CLASSES, PDFRendererPaginated) | ||
pdf_display_fields = (['id', 'Label for ID'], ) # used only in automatic field (caution: refactor planned in futures versions) | ||
pdf_display_fields = '' # contains two built-in templates ['pdf/list_landscape.html', 'pdf/list_portrait.html'] | ||
... | ||
... | ||
1. Mixin for paginated results | ||
- if you have a pagination on DRF, but require a PDF with all registries, you can use this Mixin | ||
``` | ||
from rest_framework.settings import api_settings | ||
from drf_pdf_renderer.mixin import PdfAllResultsMixin | ||
|
||
class YourView(PdfAllResultsMixin, ...) | ||
pdf_display_fields = (['id', 'Label for ID'], ) # used only in automatic field (caution: refactor planned in futures versions) | ||
pdf_display_fields = '' # contains two built-in templates ['pdf/list_landscape.html', 'pdf/list_portrait.html'] | ||
... | ||
... | ||
|
||
# Advanced | ||
1. Custom PDF Template | ||
- this project use [xhtml2pdf](https://github.com/xhtml2pdf/xhtml2pdf), check documentation of html constructor [here](https://xhtml2pdf.readthedocs.io/en/latest/format_html.html). | ||
|
||
1. Changing PDF title | ||
``` | ||
# First way | ||
class YourView(...) | ||
pdf_title = 'My Title' | ||
# Second way | ||
class YourView(...) | ||
def pdf_get_title(self, data, context) | ||
return '' | ||
``` | ||
|
||
1. Changing PDF download name | ||
``` | ||
# First way | ||
class YourView(...) | ||
pdf_filename = 'My Title' | ||
# Second way | ||
class YourView(...) | ||
def pdf_get_filename(self, pdf, data) | ||
return '' | ||
``` | ||
|
||
1. Custom data to render context | ||
``` | ||
- By default `data`, `request`, `title` and `fields` will always be present (but can be rewrited) | ||
# Second way | ||
class YourView(...) | ||
def pdf_mount_context(data) | ||
return {'adm': True} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# -*- coding: utf-8 -*- | ||
try: | ||
import django | ||
except ImportError: | ||
django = None | ||
|
||
__version__ = '0.1.0' | ||
|
||
if django and django.VERSION < (3, 2): # pragma: no cover | ||
default_app_config = 'drf_pdf_renderer.apps.DjangoPdfRendererConfig' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# -*- coding: utf-8 -*- | ||
from django.apps import AppConfig | ||
from django.utils.translation import ugettext_lazy as _ | ||
|
||
|
||
class DjangoPdfRendererConfig(AppConfig): # Our app config class | ||
name = 'django_admin_search' | ||
verbose_name = _('Django Admin Search') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from rest_framework.settings import api_settings | ||
|
||
from drf_pdf_renderer.renderer import PDFRendererPaginated | ||
|
||
|
||
class PdfAllResultsMixin: | ||
renderer_classes = (*api_settings.DEFAULT_RENDERER_CLASSES, PDFRendererPaginated) | ||
|
||
def paginate_queryset(self, queryset): | ||
if self.paginator and self.request.accepted_renderer.format == "pdf": | ||
self.paginator.page_size = 99999 | ||
return super().paginate_queryset(queryset) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import re | ||
|
||
from django.http import HttpResponse | ||
from django.template.loader import render_to_string | ||
from rest_framework.renderers import BaseRenderer | ||
from xhtml2pdf import pisa | ||
|
||
from drf_pdf_renderer.utils import link_callback | ||
|
||
|
||
class MountPdfMixin: | ||
|
||
def get_template(self): | ||
if hasattr(self.context['view'], 'pdf_get_renderer_template'): | ||
return self.context['view'].pdf_get_renderer_template(self.data) | ||
elif hasattr(self.context['view'], 'pdf_renderer_template'): | ||
return self.context['view'].pdf_renderer_template | ||
return 'pdf/list_portrait.html' | ||
|
||
def mount_show_fields(self): | ||
if hasattr(self.context['view'], 'pdf_get_display_fields'): | ||
return self.context['view'].pdf_get_display_fields(self.data) | ||
elif hasattr(self.context['view'], 'pdf_display_fields'): | ||
return self.context['view'].pdf_display_fields | ||
|
||
return [] | ||
|
||
def get_title(self): | ||
if hasattr(self.context['view'], 'pdf_get_title'): | ||
return self.context['view'].pdf_get_title(self.data) | ||
elif hasattr(self.context['view'], 'pdf_title'): | ||
return self.context['view'].pdf_title | ||
|
||
return re.sub(r"(\w)([A-Z])", r"\1 \2", self.context['view'].__class__.__name__) | ||
|
||
def mount_context(self): | ||
base_context = { | ||
'data': self.data, | ||
'request': self.context['request'], | ||
**self.get_additional_context() | ||
} | ||
|
||
if hasattr(self.context['view'], 'pdf_mount_context'): | ||
return { | ||
**self.context['view'].pdf_mount_context(self.data), | ||
**base_context | ||
} | ||
|
||
return base_context | ||
|
||
def get_additional_context(self): | ||
return { | ||
'fields': self.mount_show_fields(), | ||
'title': self.get_title() | ||
} | ||
|
||
def render_template(self): | ||
return render_to_string(template_name=self.get_template(), context=self.mount_context()) | ||
|
||
@staticmethod | ||
def render_pdf(template): | ||
return pisa.CreatePDF(template, link_callback=link_callback).dest | ||
|
||
|
||
class PDFRenderer(MountPdfMixin, BaseRenderer): | ||
media_type = 'application/pdf' | ||
format = 'pdf' | ||
|
||
def get_pdf_filename(self, pdf): | ||
if hasattr(self.context['view'], 'pdf_get_filename'): | ||
return self.context['view'].pdf_get_filename(pdf, self.data) | ||
elif hasattr(self.context['view'], 'pdf_filename'): | ||
return self.context['view'].pdf_filename | ||
|
||
return 'report.pdf' | ||
|
||
def mount_response(self, pdf): | ||
filename = self.get_pdf_filename(pdf) | ||
response = HttpResponse(pdf.getvalue(), content_type='application/pdf') | ||
response['Content-Disposition'] = f'attachment; filename={filename}' | ||
return response | ||
|
||
def process_render(self): | ||
if self.data is None or self.context['response'].status_code >= 300: | ||
return self.data | ||
|
||
template = self.render_template() | ||
pdf = self.render_pdf(template) | ||
return self.mount_response(pdf) | ||
|
||
def render(self, data, accepted_media_type=None, renderer_context=None): | ||
self.data = data | ||
self.context = renderer_context | ||
return self.process_render() | ||
|
||
|
||
class PDFRendererPaginated(PDFRenderer): | ||
results_field = 'results' | ||
|
||
def render(self, data, *args, **kwargs): | ||
if not isinstance(data, list): | ||
data = data.get(self.results_field, []) | ||
return super().render(data, *args, **kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<html> | ||
<head> | ||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | ||
<style> | ||
{% block page_definition %} | ||
@page { | ||
size: a4 landscape; | ||
@frame header_frame { | ||
-pdf-frame-content: header_content; | ||
width: 832pt; | ||
height: 40pt; | ||
top: 0pt; | ||
margin: 5pt 5pt 0pt 5pt; | ||
} | ||
@frame content_frame { | ||
width: 832pt; | ||
top: 40pt; | ||
height: 520pt; | ||
margin: 0pt 5pt 0pt 5pt; | ||
} | ||
@frame footer_frame { | ||
-pdf-frame-content: footer_content; | ||
width: 832pt; | ||
top: 570pt; | ||
height: 20pt; | ||
margin: 0pt 5pt 5pt 5pt; | ||
} | ||
{% block custom_page_attrs %}{% endblock %} | ||
} | ||
{% endblock %} | ||
{% block custom_styles %}{% endblock %} | ||
</style> | ||
</head> | ||
|
||
<body> | ||
<!-- Content for Static Frame 'header_frame' --> | ||
<div id="header_content">{% block header %}{% endblock %}</div> | ||
|
||
<!-- Content for Static Frame 'footer_frame' --> | ||
<div id="footer_content"> | ||
{% block footer %} | ||
Page <pdf:pagenumber> of <pdf:pagecount> | ||
{% endblock %} | ||
</div> | ||
|
||
<!-- HTML Content --> | ||
{% block content %}{% endblock %} | ||
|
||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<html> | ||
<head> | ||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | ||
<style> | ||
{% block page_definition %} | ||
@page { | ||
size: a4 portrait; | ||
@frame header_frame { /* Static Frame */ | ||
-pdf-frame-content: header_content; | ||
width: 585pt; | ||
height: 40pt; | ||
top: 0pt; | ||
margin: 5pt 5pt 0pt 5pt; | ||
} | ||
@frame content_frame { /* Content Frame */ | ||
width: 585pt; | ||
top: 40pt; | ||
height: 770pt; | ||
margin: 0pt 5pt 0pt 5pt; | ||
} | ||
@frame footer_frame { /* Another static Frame */ | ||
-pdf-frame-content: footer_content; | ||
width: 585pt; | ||
top: 820pt; | ||
height: 20pt; | ||
margin: 0pt 5pt 5pt 5pt; | ||
} | ||
{% block custom_page_attrs %}{% endblock %} | ||
} | ||
{% endblock %} | ||
{% block custom_styles %}{% endblock %} | ||
</style> | ||
</head> | ||
|
||
<body> | ||
<!-- Content for Static Frame 'header_frame' --> | ||
<div id="header_content">{% block header %}{% endblock %}</div> | ||
|
||
<!-- Content for Static Frame 'footer_frame' --> | ||
<div id="footer_content"> | ||
{% block footer %} | ||
Page <pdf:pagenumber> of <pdf:pagecount> | ||
{% endblock %} | ||
</div> | ||
|
||
<!-- HTML Content --> | ||
{% block content %}{% endblock %} | ||
|
||
</body> | ||
</html> |
Oops, something went wrong.