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