karrio-server-documents 2025.5rc13__py3-none-any.whl → 2025.5rc15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of karrio-server-documents might be problematic. Click here for more details.
- karrio/server/documents/generator.py +113 -45
- karrio/server/documents/tests.py +182 -0
- karrio/server/documents/views/templates.py +64 -24
- {karrio_server_documents-2025.5rc13.dist-info → karrio_server_documents-2025.5rc15.dist-info}/METADATA +1 -1
- {karrio_server_documents-2025.5rc13.dist-info → karrio_server_documents-2025.5rc15.dist-info}/RECORD +7 -6
- {karrio_server_documents-2025.5rc13.dist-info → karrio_server_documents-2025.5rc15.dist-info}/WHEEL +0 -0
- {karrio_server_documents-2025.5rc13.dist-info → karrio_server_documents-2025.5rc15.dist-info}/top_level.txt +0 -0
|
@@ -37,6 +37,15 @@ UNITS = {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
class TemplateRenderingError(Exception):
|
|
41
|
+
"""Custom exception for template rendering errors"""
|
|
42
|
+
def __init__(self, message, line_number=None, template_error=None):
|
|
43
|
+
self.message = message
|
|
44
|
+
self.line_number = line_number
|
|
45
|
+
self.template_error = template_error
|
|
46
|
+
super().__init__(self.message)
|
|
47
|
+
|
|
48
|
+
|
|
40
49
|
class Documents:
|
|
41
50
|
@staticmethod
|
|
42
51
|
def generate(
|
|
@@ -47,6 +56,8 @@ class Documents:
|
|
|
47
56
|
) -> io.BytesIO:
|
|
48
57
|
options = kwargs.get("options") or {}
|
|
49
58
|
metadata = kwargs.get("metadata") or {}
|
|
59
|
+
|
|
60
|
+
# Build contexts based on related_object and provided data
|
|
50
61
|
shipment_contexts = data.get("shipments_context") or lib.identity(
|
|
51
62
|
get_shipments_context(data["shipments"])
|
|
52
63
|
if "shipments" in data and related_object == "shipment"
|
|
@@ -57,63 +68,120 @@ class Documents:
|
|
|
57
68
|
if "orders" in data and related_object == "order"
|
|
58
69
|
else []
|
|
59
70
|
)
|
|
71
|
+
|
|
72
|
+
# For generic contexts, include the full data structure
|
|
60
73
|
generic_contexts = data.get("generic_context") or lib.identity(
|
|
61
|
-
[
|
|
74
|
+
[data] if related_object is None else []
|
|
62
75
|
)
|
|
76
|
+
|
|
77
|
+
# If no specific contexts are provided but we have a related_object, create a context with the data
|
|
78
|
+
if not shipment_contexts and not order_contexts and not generic_contexts:
|
|
79
|
+
if related_object == "shipment" and data:
|
|
80
|
+
# For shipment templates, ensure we have the expected structure
|
|
81
|
+
generic_contexts = [data]
|
|
82
|
+
elif related_object == "order" and data:
|
|
83
|
+
# For order templates, ensure we have the expected structure
|
|
84
|
+
generic_contexts = [data]
|
|
85
|
+
else:
|
|
86
|
+
# Default fallback
|
|
87
|
+
generic_contexts = [data]
|
|
88
|
+
|
|
63
89
|
filename = lib.identity(
|
|
64
90
|
dict(filename=kwargs.get("doc_name")) if kwargs.get("doc_name") else {}
|
|
65
91
|
)
|
|
66
92
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
)
|
|
81
|
-
or ""
|
|
93
|
+
def safe_render_prefetch(ctx):
|
|
94
|
+
"""Safely render prefetch templates with error handling"""
|
|
95
|
+
result = {}
|
|
96
|
+
for key, value in options.get("prefetch", {}).items():
|
|
97
|
+
try:
|
|
98
|
+
template_obj = jinja2.Template(value)
|
|
99
|
+
rendered = template_obj.render(
|
|
100
|
+
**ctx,
|
|
101
|
+
metadata=metadata,
|
|
102
|
+
units=UNITS,
|
|
103
|
+
utils=utils,
|
|
104
|
+
lib=lib,
|
|
82
105
|
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
106
|
+
result[key] = str(rendered or "")
|
|
107
|
+
except jinja2.TemplateError as e:
|
|
108
|
+
# Log the error but continue with empty string
|
|
109
|
+
result[key] = ""
|
|
110
|
+
return result
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
jinja_template = jinja2.Template(template)
|
|
114
|
+
except jinja2.TemplateSyntaxError as e:
|
|
115
|
+
raise TemplateRenderingError(
|
|
116
|
+
f"Template syntax error: {e.message}",
|
|
117
|
+
line_number=e.lineno,
|
|
118
|
+
template_error=e
|
|
88
119
|
)
|
|
89
|
-
for k, v in o.items()
|
|
90
|
-
}
|
|
91
120
|
|
|
92
|
-
jinja_template = jinja2.Template(template)
|
|
93
121
|
all_contexts = shipment_contexts + order_contexts + generic_contexts
|
|
94
|
-
rendered_pages = lib.run_asynchronously(
|
|
95
|
-
lambda ctx: jinja_template.render(
|
|
96
|
-
**ctx,
|
|
97
|
-
metadata=metadata,
|
|
98
|
-
units=UNITS,
|
|
99
|
-
utils=utils,
|
|
100
|
-
lib=lib,
|
|
101
|
-
prefetch=prefetch(ctx),
|
|
102
|
-
),
|
|
103
|
-
all_contexts,
|
|
104
|
-
)
|
|
105
|
-
content = PAGE_SEPARATOR.join(rendered_pages)
|
|
106
122
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
buffer,
|
|
111
|
-
stylesheets=STYLESHEETS,
|
|
112
|
-
font_config=FONT_CONFIG,
|
|
113
|
-
optimize_size=("fonts", "images"),
|
|
114
|
-
)
|
|
123
|
+
# If no contexts are available, create a default one
|
|
124
|
+
if not all_contexts:
|
|
125
|
+
all_contexts = [data or {}]
|
|
115
126
|
|
|
116
|
-
|
|
127
|
+
rendered_pages = []
|
|
128
|
+
for ctx in all_contexts:
|
|
129
|
+
try:
|
|
130
|
+
# Add prefetch data safely
|
|
131
|
+
ctx_with_prefetch = {
|
|
132
|
+
**ctx,
|
|
133
|
+
"prefetch": safe_render_prefetch(ctx)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
rendered_page = jinja_template.render(
|
|
137
|
+
**ctx_with_prefetch,
|
|
138
|
+
metadata=metadata,
|
|
139
|
+
units=UNITS,
|
|
140
|
+
utils=utils,
|
|
141
|
+
lib=lib,
|
|
142
|
+
)
|
|
143
|
+
rendered_pages.append(rendered_page)
|
|
144
|
+
except jinja2.UndefinedError as e:
|
|
145
|
+
raise TemplateRenderingError(
|
|
146
|
+
f"Template variable error: {str(e)}. Available variables: {list(ctx.keys())}",
|
|
147
|
+
template_error=e
|
|
148
|
+
)
|
|
149
|
+
except jinja2.TemplateRuntimeError as e:
|
|
150
|
+
raise TemplateRenderingError(
|
|
151
|
+
f"Template runtime error: {str(e)}",
|
|
152
|
+
line_number=getattr(e, 'lineno', None),
|
|
153
|
+
template_error=e
|
|
154
|
+
)
|
|
155
|
+
except jinja2.TemplateError as e:
|
|
156
|
+
raise TemplateRenderingError(
|
|
157
|
+
f"Template error: {str(e)}",
|
|
158
|
+
line_number=getattr(e, 'lineno', None),
|
|
159
|
+
template_error=e
|
|
160
|
+
)
|
|
161
|
+
except Exception as e:
|
|
162
|
+
raise TemplateRenderingError(
|
|
163
|
+
f"Unexpected error during template rendering: {str(e)}",
|
|
164
|
+
template_error=e
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
content = PAGE_SEPARATOR.join(rendered_pages)
|
|
168
|
+
|
|
169
|
+
# Handle PDF generation errors
|
|
170
|
+
try:
|
|
171
|
+
buffer = io.BytesIO()
|
|
172
|
+
html = weasyprint.HTML(string=content, encoding="utf-8")
|
|
173
|
+
html.write_pdf(
|
|
174
|
+
buffer,
|
|
175
|
+
stylesheets=STYLESHEETS,
|
|
176
|
+
font_config=FONT_CONFIG,
|
|
177
|
+
optimize_size=("fonts", "images"),
|
|
178
|
+
)
|
|
179
|
+
return buffer
|
|
180
|
+
except Exception as e:
|
|
181
|
+
raise TemplateRenderingError(
|
|
182
|
+
f"PDF generation error: {str(e)}",
|
|
183
|
+
template_error=e
|
|
184
|
+
)
|
|
117
185
|
|
|
118
186
|
@staticmethod
|
|
119
187
|
def generate_template(
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from django.test import TestCase
|
|
3
|
+
from rest_framework.test import APIClient
|
|
4
|
+
from rest_framework import status
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
from karrio.server.documents.generator import Documents, TemplateRenderingError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DocumentGenerationErrorHandlingTest(TestCase):
|
|
11
|
+
def setUp(self):
|
|
12
|
+
self.client = APIClient()
|
|
13
|
+
# Mock authentication for testing
|
|
14
|
+
with patch('karrio.server.core.views.api.APIView.permission_classes', []):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
def test_template_with_undefined_variable_returns_error(self):
|
|
18
|
+
"""Test that template with undefined variables returns proper error message"""
|
|
19
|
+
template = "<div>{{ shipment.shipper.address_line1 }}</div>"
|
|
20
|
+
data = {
|
|
21
|
+
"template": template,
|
|
22
|
+
"doc_format": "html",
|
|
23
|
+
"doc_name": "Test Document",
|
|
24
|
+
"data": {
|
|
25
|
+
"object": {"id": "test123"},
|
|
26
|
+
# Note: no shipment data provided, so shipment.shipper.address_line1 will be undefined
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Mock the permission check
|
|
31
|
+
with patch('karrio.server.core.views.api.APIView.check_permissions'):
|
|
32
|
+
response = self.client.post(
|
|
33
|
+
'/v1/documents/generate',
|
|
34
|
+
data=json.dumps(data),
|
|
35
|
+
content_type='application/json'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
39
|
+
self.assertIn('errors', response.json())
|
|
40
|
+
self.assertIn('Template variable error', response.json()['errors'][0]['message'])
|
|
41
|
+
self.assertIn('shipment', response.json()['errors'][0]['message'])
|
|
42
|
+
|
|
43
|
+
def test_template_syntax_error_returns_error(self):
|
|
44
|
+
"""Test that template with syntax errors returns proper error message"""
|
|
45
|
+
template = "<div>{{ unclosed_tag</div>" # Missing closing }}
|
|
46
|
+
data = {
|
|
47
|
+
"template": template,
|
|
48
|
+
"doc_format": "html",
|
|
49
|
+
"doc_name": "Test Document",
|
|
50
|
+
"data": {"test": "data"}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
with patch('karrio.server.core.views.api.APIView.check_permissions'):
|
|
54
|
+
response = self.client.post(
|
|
55
|
+
'/v1/documents/generate',
|
|
56
|
+
data=json.dumps(data),
|
|
57
|
+
content_type='application/json'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
61
|
+
self.assertIn('errors', response.json())
|
|
62
|
+
self.assertIn('Template syntax error', response.json()['errors'][0]['message'])
|
|
63
|
+
|
|
64
|
+
def test_missing_template_returns_error(self):
|
|
65
|
+
"""Test that missing template returns proper error message"""
|
|
66
|
+
data = {
|
|
67
|
+
"doc_format": "html",
|
|
68
|
+
"doc_name": "Test Document",
|
|
69
|
+
"data": {"test": "data"}
|
|
70
|
+
# Note: no template or template_id provided
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
with patch('karrio.server.core.views.api.APIView.check_permissions'):
|
|
74
|
+
response = self.client.post(
|
|
75
|
+
'/v1/documents/generate',
|
|
76
|
+
data=json.dumps(data),
|
|
77
|
+
content_type='application/json'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
81
|
+
self.assertIn('errors', response.json())
|
|
82
|
+
self.assertEqual(
|
|
83
|
+
response.json()['errors'][0]['message'],
|
|
84
|
+
'template or template_id is required'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def test_valid_template_with_data_succeeds(self):
|
|
88
|
+
"""Test that valid template with proper data succeeds"""
|
|
89
|
+
template = "<div>Hello {{ name }}!</div>"
|
|
90
|
+
data = {
|
|
91
|
+
"template": template,
|
|
92
|
+
"doc_format": "html",
|
|
93
|
+
"doc_name": "Test Document",
|
|
94
|
+
"data": {
|
|
95
|
+
"name": "World",
|
|
96
|
+
"shipment": {
|
|
97
|
+
"shipper": {"address_line1": "123 Test St"}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Mock PDF generation to avoid WeasyPrint dependency in tests
|
|
103
|
+
with patch('karrio.server.core.views.api.APIView.check_permissions'), \
|
|
104
|
+
patch('karrio.server.documents.generator.weasyprint.HTML') as mock_html:
|
|
105
|
+
|
|
106
|
+
# Mock the PDF generation
|
|
107
|
+
mock_buffer = b"fake pdf content"
|
|
108
|
+
mock_html.return_value.write_pdf.return_value = None
|
|
109
|
+
|
|
110
|
+
# Patch the buffer creation
|
|
111
|
+
with patch('karrio.server.documents.generator.io.BytesIO') as mock_bytesio:
|
|
112
|
+
mock_bytesio.return_value.getvalue.return_value = mock_buffer
|
|
113
|
+
|
|
114
|
+
response = self.client.post(
|
|
115
|
+
'/v1/documents/generate',
|
|
116
|
+
data=json.dumps(data),
|
|
117
|
+
content_type='application/json'
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Should succeed (201) or fail gracefully with a proper error message
|
|
121
|
+
self.assertIn(response.status_code, [status.HTTP_201_CREATED, status.HTTP_400_BAD_REQUEST])
|
|
122
|
+
|
|
123
|
+
if response.status_code == status.HTTP_400_BAD_REQUEST:
|
|
124
|
+
# If it fails, it should be with a proper error message, not a 500
|
|
125
|
+
self.assertIn('errors', response.json())
|
|
126
|
+
error_message = response.json()['errors'][0]['message']
|
|
127
|
+
# Should not be a generic "internal server error"
|
|
128
|
+
self.assertNotIn('Internal server error', error_message)
|
|
129
|
+
|
|
130
|
+
def test_template_rendering_error_direct(self):
|
|
131
|
+
"""Test TemplateRenderingError class directly"""
|
|
132
|
+
try:
|
|
133
|
+
# Test with a template that has undefined variables
|
|
134
|
+
Documents.generate(
|
|
135
|
+
template="<div>{{ undefined_var.nested.property }}</div>",
|
|
136
|
+
data={"some_data": "test"}
|
|
137
|
+
)
|
|
138
|
+
self.fail("Should have raised TemplateRenderingError")
|
|
139
|
+
except TemplateRenderingError as e:
|
|
140
|
+
self.assertIn("Template variable error", e.message)
|
|
141
|
+
self.assertIn("undefined_var", e.message)
|
|
142
|
+
self.assertIn("Available variables", e.message)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
self.fail(f"Should have raised TemplateRenderingError, got {type(e).__name__}: {e}")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class DocumentGenerationContextTest(TestCase):
|
|
148
|
+
"""Test that data contexts are properly handled"""
|
|
149
|
+
|
|
150
|
+
def test_shipment_context_properly_passed(self):
|
|
151
|
+
"""Test that shipment data is properly passed to template context"""
|
|
152
|
+
template = "<div>{{ shipment.shipper.address_line1 }}</div>"
|
|
153
|
+
data = {
|
|
154
|
+
"shipment": {
|
|
155
|
+
"shipper": {"address_line1": "123 Test Street"}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
# This should work without errors
|
|
161
|
+
result = Documents.generate(template, data=data)
|
|
162
|
+
# If we get here, the context was properly passed
|
|
163
|
+
self.assertIsNotNone(result)
|
|
164
|
+
except TemplateRenderingError as e:
|
|
165
|
+
# If it fails, it should be due to PDF generation, not template rendering
|
|
166
|
+
if "PDF generation error" not in e.message:
|
|
167
|
+
self.fail(f"Template rendering failed: {e.message}")
|
|
168
|
+
|
|
169
|
+
def test_generic_context_fallback(self):
|
|
170
|
+
"""Test that generic context fallback works for arbitrary data"""
|
|
171
|
+
template = "<div>{{ custom_field }}</div>"
|
|
172
|
+
data = {
|
|
173
|
+
"custom_field": "test value"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
result = Documents.generate(template, data=data)
|
|
178
|
+
self.assertIsNotNone(result)
|
|
179
|
+
except TemplateRenderingError as e:
|
|
180
|
+
# Should work for simple templates
|
|
181
|
+
if "PDF generation error" not in e.message:
|
|
182
|
+
self.fail(f"Template rendering failed: {e.message}")
|
|
@@ -167,34 +167,74 @@ class DocumentGenerator(api.APIView):
|
|
|
167
167
|
This API is designed to be used to generate GS1 labels,
|
|
168
168
|
invoices and any document that requires external data.
|
|
169
169
|
"""
|
|
170
|
-
|
|
170
|
+
try:
|
|
171
|
+
data = serializers.DocumentData.map(data=request.data).data
|
|
171
172
|
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
if data.get("template") is None and data.get("template_id") is None:
|
|
174
|
+
return Response(
|
|
175
|
+
{"errors": [{"message": "template or template_id is required"}]},
|
|
176
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
177
|
+
)
|
|
174
178
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
document_template = lib.identity(
|
|
180
|
+
None
|
|
181
|
+
if data.get("template_id") is None
|
|
182
|
+
else models.DocumentTemplate.objects.get(pk=data.get("template_id"))
|
|
183
|
+
)
|
|
180
184
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
185
|
+
try:
|
|
186
|
+
doc_file = generator.Documents.generate(
|
|
187
|
+
getattr(document_template, "template", data.get("template")),
|
|
188
|
+
related_object=getattr(document_template, "related_object", None),
|
|
189
|
+
metadata=getattr(document_template, "metadata", {}),
|
|
190
|
+
data=data.get("data", {}),
|
|
191
|
+
options=data.get("options", {}),
|
|
192
|
+
doc_name=data.get("doc_name"),
|
|
193
|
+
doc_format=data.get("doc_format"),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
document = serializers.GeneratedDocument.map(
|
|
197
|
+
data={
|
|
198
|
+
**data,
|
|
199
|
+
"doc_file": base64.b64encode(doc_file.getvalue()).decode("utf-8"),
|
|
200
|
+
}
|
|
201
|
+
)
|
|
196
202
|
|
|
197
|
-
|
|
203
|
+
return Response(document.data, status=status.HTTP_201_CREATED)
|
|
204
|
+
|
|
205
|
+
except generator.TemplateRenderingError as e:
|
|
206
|
+
error_message = e.message
|
|
207
|
+
if e.line_number:
|
|
208
|
+
error_message += f" (line {e.line_number})"
|
|
209
|
+
|
|
210
|
+
return Response(
|
|
211
|
+
{"errors": [{"message": error_message}]},
|
|
212
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
213
|
+
)
|
|
214
|
+
except models.DocumentTemplate.DoesNotExist:
|
|
215
|
+
return Response(
|
|
216
|
+
{"errors": [{"message": f"Document template with id '{data.get('template_id')}' not found"}]},
|
|
217
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
218
|
+
)
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logger.exception(f"Unexpected error during document generation: {e}")
|
|
221
|
+
return Response(
|
|
222
|
+
{"errors": [{"message": f"Document generation failed: {str(e)}"}]},
|
|
223
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
except serializers.ValidationError as e:
|
|
227
|
+
logger.error(f"Validation error: {e}")
|
|
228
|
+
return Response(
|
|
229
|
+
{"errors": [{"message": "Invalid input data"}]},
|
|
230
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
231
|
+
)
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.exception(f"Unexpected error in document generator: {e}")
|
|
234
|
+
return Response(
|
|
235
|
+
{"errors": [{"message": "Internal server error"}]},
|
|
236
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
237
|
+
)
|
|
198
238
|
|
|
199
239
|
|
|
200
240
|
urlpatterns = [
|
{karrio_server_documents-2025.5rc13.dist-info → karrio_server_documents-2025.5rc15.dist-info}/RECORD
RENAMED
|
@@ -2,9 +2,10 @@ karrio/server/documents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
2
2
|
karrio/server/documents/admin.py,sha256=TAkENFDUEnRvOXvKL_6Nz-kmi5sosHvM04W6zCLeIBA,3464
|
|
3
3
|
karrio/server/documents/apps.py,sha256=cH7FOtpePV56R4mW1KIlTdySGavZYZZ9hEwo3xmwgLo,359
|
|
4
4
|
karrio/server/documents/filters.py,sha256=cLJNRFAnmzRNErUwnx9xSj413q2da5hrjRN2U3KPbrU,460
|
|
5
|
-
karrio/server/documents/generator.py,sha256=
|
|
5
|
+
karrio/server/documents/generator.py,sha256=Dh__0UAU0bKgMr28O3FXHKB_yN1a_Bdm-aO_Lz_96xk,10326
|
|
6
6
|
karrio/server/documents/models.py,sha256=btjONslnF7BYmDR6_EAmI2K8F96xC2w2WV2F8FNE9m4,1724
|
|
7
7
|
karrio/server/documents/signals.py,sha256=HY4GvQuLEgCgQ2ONNVVKLuZtoOVZvbkVYOpwnHHFyEk,937
|
|
8
|
+
karrio/server/documents/tests.py,sha256=_DhWOXdu2jl1xPbyveqlZiJReSHHt0kMQ8fsvmbRuI8,7437
|
|
8
9
|
karrio/server/documents/urls.py,sha256=JPGYWhRS_Ts3g4QRPJ6r93JDf_V7ucII_5xSOP93NHg,273
|
|
9
10
|
karrio/server/documents/utils.py,sha256=ElTfhi4lkU3ZjzYf_eW6FdXhAHmXuKR0u7YpDymJodQ,119469
|
|
10
11
|
karrio/server/documents/migrations/0001_initial.py,sha256=_4SvF3POad9c77tYuyjgjidatJRvYPlGIakE3ja-bhg,2406
|
|
@@ -24,13 +25,13 @@ karrio/server/documents/tests/test_generator.py,sha256=JgTziVt9Sn_VuVU2axvUUOTp0
|
|
|
24
25
|
karrio/server/documents/tests/test_templates.py,sha256=fenggMLz5svtSSQc3k1GnFMnnHTmJqN0hpX2uHuaNrY,11268
|
|
25
26
|
karrio/server/documents/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
27
|
karrio/server/documents/views/printers.py,sha256=OsgMLIZ2y64Sr_9Z020n0mWL28TlUjq8iN4wULJuxsM,7343
|
|
27
|
-
karrio/server/documents/views/templates.py,sha256=
|
|
28
|
+
karrio/server/documents/views/templates.py,sha256=uR2fwdaHSrQTlEe4mYl8EsVIaVhHi-aoxIOko0DpEMU,8881
|
|
28
29
|
karrio/server/graph/schemas/__init__.py,sha256=iOEMwnlORWezdO8-2vxBIPSR37D7JGjluZ8f55vzxls,81
|
|
29
30
|
karrio/server/graph/schemas/documents/__init__.py,sha256=GV9yk56ddIYnB9PP_AtXnucs3SIszUJpe2Jv9sHdzlo,1620
|
|
30
31
|
karrio/server/graph/schemas/documents/inputs.py,sha256=Ri2P_VJjUsFZYy8dIuHnUzHlbWxgSWJhpy9J8TS8Y1Q,1288
|
|
31
32
|
karrio/server/graph/schemas/documents/mutations.py,sha256=5m-3w7ZVRsl4SiagpN4XN1nN1TW_3eLrqRCjhaFRtG0,1849
|
|
32
33
|
karrio/server/graph/schemas/documents/types.py,sha256=_UgtdgnGDUeMXW9HGHNVdcP7YeN6hnsnTHYZZgaiH5M,1537
|
|
33
|
-
karrio_server_documents-2025.
|
|
34
|
-
karrio_server_documents-2025.
|
|
35
|
-
karrio_server_documents-2025.
|
|
36
|
-
karrio_server_documents-2025.
|
|
34
|
+
karrio_server_documents-2025.5rc15.dist-info/METADATA,sha256=W3-Uhahv0mPkokGzqDC4fVmwp_JBYxqeb6FN4zkpMzA,565
|
|
35
|
+
karrio_server_documents-2025.5rc15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
36
|
+
karrio_server_documents-2025.5rc15.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
|
|
37
|
+
karrio_server_documents-2025.5rc15.dist-info/RECORD,,
|
{karrio_server_documents-2025.5rc13.dist-info → karrio_server_documents-2025.5rc15.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|