mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-14 02:53:08 +00:00
[refactor] Remove django-weasyprint entirely (#9316)
* Remove django-weasyprint entirely * Handle null request * Bug fix
This commit is contained in:
parent
3afafe594b
commit
a453c9b286
@ -148,7 +148,6 @@ class MyLabelPrinter(LabelPrintingMixin, InvenTreePlugin):
|
||||
Send the label to the printer
|
||||
|
||||
kwargs:
|
||||
pdf_file: The PDF file object of the rendered label (WeasyTemplateResponse object)
|
||||
pdf_data: Raw PDF data of the rendered label
|
||||
filename: The filename of this PDF label
|
||||
label_instance: The instance of the label model which triggered the print_label() method
|
||||
|
@ -141,11 +141,7 @@ class LabelPrinterBaseDriver(BaseDriver):
|
||||
label: The LabelTemplate object to render
|
||||
item: The item to render the label with
|
||||
"""
|
||||
return (
|
||||
self.render_to_pdf(label, item, **kwargs)
|
||||
.get_document() # type: ignore
|
||||
.write_pdf()
|
||||
)
|
||||
return self.render_to_pdf(label, item, **kwargs)
|
||||
|
||||
def render_to_html(self, label: LabelTemplate, item: models.Model, **kwargs) -> str:
|
||||
"""Helper method to render a label to HTML format for a specific item.
|
||||
|
@ -86,11 +86,7 @@ class LabelPrintingMixin:
|
||||
pdf_data = kwargs.get('pdf_data')
|
||||
|
||||
if not pdf_data:
|
||||
pdf_data = (
|
||||
self.render_to_pdf(label, instance, request, **kwargs)
|
||||
.get_document()
|
||||
.write_pdf()
|
||||
)
|
||||
pdf_data = self.render_to_pdf(label, instance, request, **kwargs)
|
||||
|
||||
pdf2image_kwargs = {
|
||||
'dpi': kwargs.get('dpi', InvenTreeSetting.get_setting('LABEL_DPI', 300)),
|
||||
@ -152,14 +148,12 @@ class LabelPrintingMixin:
|
||||
for item in items:
|
||||
context = label.get_context(item, request)
|
||||
filename = label.generate_filename(context)
|
||||
pdf_file = self.render_to_pdf(label, item, request, **kwargs)
|
||||
pdf_data = pdf_file.get_document().write_pdf()
|
||||
pdf_data = self.render_to_pdf(label, item, request, **kwargs)
|
||||
png_file = self.render_to_png(
|
||||
label, item, request, pdf_data=pdf_data, **kwargs
|
||||
)
|
||||
|
||||
print_args = {
|
||||
'pdf_file': pdf_file,
|
||||
'pdf_data': pdf_data,
|
||||
'png_file': png_file,
|
||||
'filename': filename,
|
||||
@ -179,9 +173,6 @@ class LabelPrintingMixin:
|
||||
else:
|
||||
# Offload the print task to the background worker
|
||||
|
||||
# Exclude the 'pdf_file' object - cannot be pickled
|
||||
print_args.pop('pdf_file', None)
|
||||
|
||||
# Exclude the 'context' object - cannot be pickled
|
||||
print_args.pop('context', None)
|
||||
|
||||
@ -216,7 +207,6 @@ class LabelPrintingMixin:
|
||||
"""Print a single label (blocking).
|
||||
|
||||
kwargs:
|
||||
pdf_file: The PDF file object of the rendered label (WeasyTemplateResponse object)
|
||||
pdf_data: Raw PDF data of the rendered label
|
||||
filename: The filename of this PDF label
|
||||
label_instance: The instance of the label model which triggered the print_label() method
|
||||
|
@ -1,8 +1,12 @@
|
||||
"""Default label printing plugin (supports PDF generation)."""
|
||||
|
||||
import io
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pypdf import PdfWriter
|
||||
|
||||
from InvenTree.helpers import str2bool
|
||||
from plugin import InvenTreePlugin
|
||||
from plugin.mixins import LabelPrintingMixin, SettingsMixin
|
||||
@ -50,7 +54,7 @@ class InvenTreeLabelPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlugin):
|
||||
output = self.render_to_html(label, instance, None, **kwargs)
|
||||
else:
|
||||
# Output is already provided
|
||||
output = kwargs['pdf_file']
|
||||
output = kwargs.get('pdf_data')
|
||||
|
||||
self.outputs.append(output)
|
||||
|
||||
@ -65,15 +69,17 @@ class InvenTreeLabelPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlugin):
|
||||
filename = 'labels.html'
|
||||
else:
|
||||
# Stitch together the PDF outputs
|
||||
pages = []
|
||||
pdf_writer = PdfWriter()
|
||||
|
||||
for output in self.outputs:
|
||||
doc = output.get_document()
|
||||
output_file = io.BytesIO(output)
|
||||
pdf_writer.append(output_file)
|
||||
|
||||
for page in doc.pages:
|
||||
pages.append(page)
|
||||
pdf_file = io.BytesIO()
|
||||
pdf_writer.write(pdf_file)
|
||||
data = pdf_file.getvalue()
|
||||
pdf_file.close()
|
||||
|
||||
data = self.outputs[0].get_document().copy(pages).write_pdf()
|
||||
filename = kwargs.get('filename', 'labels.pdf')
|
||||
|
||||
return ContentFile(data, name=filename)
|
||||
|
@ -1,25 +1,24 @@
|
||||
"""Report template model definitions."""
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.validators import FileExtensionValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context, Template
|
||||
from django.template.exceptions import TemplateDoesNotExist
|
||||
from django.template.loader import render_to_string
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import structlog
|
||||
from pypdf import PdfWriter
|
||||
|
||||
import InvenTree.exceptions
|
||||
import InvenTree.helpers
|
||||
@ -33,10 +32,10 @@ from plugin import InvenTreePlugin
|
||||
from plugin.registry import registry
|
||||
|
||||
try:
|
||||
from django_weasyprint import WeasyTemplateResponseMixin
|
||||
from weasyprint import HTML
|
||||
except OSError as err: # pragma: no cover
|
||||
print(f'OSError: {err}')
|
||||
print("Unable to import 'django_weasyprint' module.")
|
||||
print("Unable to import 'weasyprint' module.")
|
||||
print('You may require some further system packages to be installed.')
|
||||
sys.exit(1)
|
||||
|
||||
@ -44,32 +43,6 @@ except OSError as err: # pragma: no cover
|
||||
logger = structlog.getLogger('inventree')
|
||||
|
||||
|
||||
MOCK_PRINT_HOST = 'localhost'
|
||||
|
||||
|
||||
@override_settings(SITE_URL=MOCK_PRINT_HOST)
|
||||
def dummy_print_request() -> HttpRequest:
|
||||
"""Generate a dummy HTTP request object.
|
||||
|
||||
- This is required for internal print calls, as WeasyPrint *requires* a request object.
|
||||
- Additionally, we have to mock the HOST header, as WeasyPrint requires a valid HOST URL.
|
||||
"""
|
||||
factory = RequestFactory()
|
||||
request = factory.get('/', headers={'host': MOCK_PRINT_HOST})
|
||||
request.user = AnonymousUser()
|
||||
return request
|
||||
|
||||
|
||||
class WeasyprintReport(WeasyTemplateResponseMixin):
|
||||
"""Class for rendering a HTML template to a PDF."""
|
||||
|
||||
def __init__(self, request, template, **kwargs):
|
||||
"""Initialize the report mixin with some standard attributes."""
|
||||
self.request = request
|
||||
self.template_name = template
|
||||
self.pdf_filename = kwargs.get('filename', 'output.pdf')
|
||||
|
||||
|
||||
def rename_template(instance, filename):
|
||||
"""Function to rename a report template once uploaded.
|
||||
|
||||
@ -200,33 +173,34 @@ class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel):
|
||||
|
||||
return template_string.render(Context(context))
|
||||
|
||||
def render_as_string(self, instance, request=None, **kwargs):
|
||||
def render_as_string(self, instance, request=None, **kwargs) -> str:
|
||||
"""Render the report to a HTML string.
|
||||
|
||||
Useful for debug mode (viewing generated code)
|
||||
Arguments:
|
||||
instance: The model instance to render against
|
||||
request: A HTTPRequest object (optional)
|
||||
|
||||
Returns:
|
||||
str: HTML string
|
||||
"""
|
||||
context = self.get_context(instance, request, **kwargs)
|
||||
|
||||
return render_to_string(self.template_name, context, request)
|
||||
|
||||
def render(self, instance, request=None, **kwargs):
|
||||
def render(self, instance, request=None, **kwargs) -> bytes:
|
||||
"""Render the template to a PDF file.
|
||||
|
||||
Uses django-weasyprint plugin to render HTML template against Weasyprint
|
||||
Arguments:
|
||||
instance: The model instance to render against
|
||||
request: A HTTPRequest object (optional)
|
||||
|
||||
Returns:
|
||||
bytes: PDF data
|
||||
"""
|
||||
context = self.get_context(instance, request)
|
||||
html = self.render_as_string(instance, request, **kwargs)
|
||||
pdf = HTML(string=html).write_pdf()
|
||||
|
||||
# Render HTML template to PDF
|
||||
wp = WeasyprintReport(
|
||||
request,
|
||||
self.template_name,
|
||||
base_url=get_base_url(request=request),
|
||||
presentational_hints=True,
|
||||
filename=self.generate_filename(context),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return wp.render_to_response(context, **kwargs)
|
||||
return pdf
|
||||
|
||||
filename_pattern = models.CharField(
|
||||
default='output.pdf',
|
||||
@ -407,10 +381,6 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
|
||||
# Start with a default report name
|
||||
report_name = None
|
||||
|
||||
if request is None:
|
||||
# Generate a dummy request object
|
||||
request = dummy_print_request()
|
||||
|
||||
report_plugins = registry.with_mixin('report')
|
||||
|
||||
# If a ReportOutput object is not provided, create a new one
|
||||
@ -419,7 +389,7 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
|
||||
template=self,
|
||||
items=len(items),
|
||||
user=request.user
|
||||
if request.user and request.user.is_authenticated
|
||||
if request and request.user and request.user.is_authenticated
|
||||
else None,
|
||||
progress=0,
|
||||
complete=False,
|
||||
@ -451,12 +421,11 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
|
||||
|
||||
# Attach the generated report to the model instance (if required)
|
||||
if self.attach_to_model and not debug_mode:
|
||||
data = report.get_document().write_pdf()
|
||||
instance.create_attachment(
|
||||
attachment=ContentFile(data, report_name),
|
||||
attachment=ContentFile(report, report_name),
|
||||
comment=_(f'Report generated from template {self.name}'),
|
||||
upload_user=request.user
|
||||
if request.user.is_authenticated
|
||||
if request and request.user.is_authenticated
|
||||
else None,
|
||||
)
|
||||
|
||||
@ -481,7 +450,7 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
|
||||
raise ValidationError({
|
||||
'error': _('Error generating report'),
|
||||
'detail': str(exc),
|
||||
'path': request.path,
|
||||
'path': request.path if request else None,
|
||||
})
|
||||
|
||||
if not report_name.endswith('.pdf'):
|
||||
@ -492,18 +461,23 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
|
||||
data = '\n'.join(outputs)
|
||||
report_name = report_name.replace('.pdf', '.html')
|
||||
else:
|
||||
pages = []
|
||||
# Merge the outputs back together into a single PDF file
|
||||
pdf_writer = PdfWriter()
|
||||
|
||||
try:
|
||||
for report in outputs:
|
||||
doc = report.get_document()
|
||||
for page in doc.pages:
|
||||
pages.append(page)
|
||||
# Construct file object with raw PDF data
|
||||
report_file = io.BytesIO(report)
|
||||
pdf_writer.append(report_file)
|
||||
|
||||
data = outputs[0].get_document().copy(pages).write_pdf()
|
||||
except TemplateDoesNotExist as exc:
|
||||
t_name = str(exc) or self.template
|
||||
raise ValidationError(f'Template file {t_name} does not exist')
|
||||
# Generate raw output
|
||||
pdf_file = io.BytesIO()
|
||||
pdf_writer.write(pdf_file)
|
||||
data = pdf_file.getvalue()
|
||||
pdf_file.close()
|
||||
except Exception:
|
||||
InvenTree.exceptions.log_error('report.print')
|
||||
data = None
|
||||
|
||||
# Save the generated report to the database
|
||||
output.complete = True
|
||||
@ -622,7 +596,9 @@ class LabelTemplate(TemplateUploadMixin, ReportTemplateBase):
|
||||
template=self,
|
||||
items=len(items),
|
||||
plugin=plugin.slug,
|
||||
user=request.user if request else None,
|
||||
user=request.user
|
||||
if request and request.user.is_authenticated
|
||||
else None,
|
||||
progress=0,
|
||||
complete=False,
|
||||
)
|
||||
@ -630,11 +606,6 @@ class LabelTemplate(TemplateUploadMixin, ReportTemplateBase):
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
if request is None:
|
||||
# If the request object is not provided, we need to create a dummy one
|
||||
# Otherwise, WeasyPrint throws an error
|
||||
request = dummy_print_request()
|
||||
|
||||
try:
|
||||
if hasattr(plugin, 'before_printing'):
|
||||
plugin.before_printing()
|
||||
|
@ -26,7 +26,6 @@ django-structlog # Structured logging
|
||||
django-stdimage # Advanced ImageField management
|
||||
django-taggit # Tagging support
|
||||
django-otp==1.3.0 # Two-factor authentication (legacy to ensure migrations) https://github.com/inventree/InvenTree/pull/6293
|
||||
django-weasyprint # django weasyprint integration
|
||||
djangorestframework<3.15 # DRF framework # FIXED 2024-06-26 see https://github.com/inventree/InvenTree/pull/7521
|
||||
djangorestframework-simplejwt[crypto] # JWT authentication
|
||||
django-xforwardedfor-middleware # IP forwarding metadata
|
||||
@ -40,6 +39,7 @@ pillow # Image manipulation
|
||||
pint # Unit conversion
|
||||
pip-licenses # License information for installed packages
|
||||
ppf.datamatrix # Data Matrix barcode generator
|
||||
pypdf # PDF manipulation tools
|
||||
python-barcode[images] # Barcode generator
|
||||
python-dotenv # Environment variable management
|
||||
pyyaml>=6.0.1 # YAML parsing
|
||||
|
@ -411,7 +411,6 @@ django==4.2.20 \
|
||||
# django-stdimage
|
||||
# django-structlog
|
||||
# django-taggit
|
||||
# django-weasyprint
|
||||
# django-xforwardedfor-middleware
|
||||
# djangorestframework
|
||||
# djangorestframework-simplejwt
|
||||
@ -517,10 +516,6 @@ django-taggit==6.1.0 \
|
||||
--hash=sha256:ab776264bbc76cb3d7e49e1bf9054962457831bd21c3a42db9138b41956e4cf0 \
|
||||
--hash=sha256:c4d1199e6df34125dd36db5eb0efe545b254dec3980ce5dd80e6bab3e78757c3
|
||||
# via -r src/backend/requirements.in
|
||||
django-weasyprint==2.3.1 \
|
||||
--hash=sha256:09cc1c40c92db34bed80154be7c959fea03d6001dc46fd599f3fd464d6a6dc72 \
|
||||
--hash=sha256:cd35b8bd24b28128a17a2416d0e6f3e64cb727f25c53467150b4be16ccd01c19
|
||||
# via -r src/backend/requirements.in
|
||||
django-xforwardedfor-middleware==2.0 \
|
||||
--hash=sha256:16fd1cb27f33a5541b6f3e0b43afb1b7334a76f27a1255b69e14ec5c440f0b24
|
||||
# via -r src/backend/requirements.in
|
||||
@ -1213,6 +1208,10 @@ pyjwt[crypto]==2.10.1 \
|
||||
# via
|
||||
# django-allauth
|
||||
# djangorestframework-simplejwt
|
||||
pypdf==5.4.0 \
|
||||
--hash=sha256:9af476a9dc30fcb137659b0dec747ea94aa954933c52cf02ee33e39a16fe9175 \
|
||||
--hash=sha256:db994ab47cadc81057ea1591b90e5b543e2b7ef2d0e31ef41a9bfe763c119dab
|
||||
# via -r src/backend/requirements.in
|
||||
pyphen==0.17.2 \
|
||||
--hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \
|
||||
--hash=sha256:f60647a9c9b30ec6c59910097af82bc5dd2d36576b918e44148d8b07ef3b4aa3
|
||||
@ -1626,6 +1625,7 @@ typing-extensions==4.12.2 \
|
||||
# opentelemetry-sdk
|
||||
# pint
|
||||
# py-moneyed
|
||||
# pypdf
|
||||
# referencing
|
||||
# structlog
|
||||
tzdata==2025.1 \
|
||||
@ -1652,9 +1652,7 @@ wcwidth==0.2.13 \
|
||||
weasyprint==64.1 \
|
||||
--hash=sha256:28b02f2c6409bafce1b1220d9d76a7345875bd3bd08c4f6dfbf510bb92a94757 \
|
||||
--hash=sha256:f7c88ea8ce0ce0c527cbb9c802689e035fae50016d7efc5dfdaba4b75abf68f4
|
||||
# via
|
||||
# -r src/backend/requirements.in
|
||||
# django-weasyprint
|
||||
# via -r src/backend/requirements.in
|
||||
webencodings==0.5.1 \
|
||||
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
|
||||
--hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
|
||||
|
Loading…
x
Reference in New Issue
Block a user