karrio-server-documents 2025.5.2__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.
Files changed (37) hide show
  1. karrio/server/documents/__init__.py +0 -0
  2. karrio/server/documents/admin.py +93 -0
  3. karrio/server/documents/apps.py +13 -0
  4. karrio/server/documents/filters.py +14 -0
  5. karrio/server/documents/generator.py +298 -0
  6. karrio/server/documents/migrations/0001_initial.py +69 -0
  7. karrio/server/documents/migrations/0002_alter_documenttemplate_related_objects.py +18 -0
  8. karrio/server/documents/migrations/0003_rename_related_objects_documenttemplate_related_object.py +18 -0
  9. karrio/server/documents/migrations/0004_documenttemplate_active.py +18 -0
  10. karrio/server/documents/migrations/0005_alter_documenttemplate_description_and_more.py +23 -0
  11. karrio/server/documents/migrations/0006_documenttemplate_metadata.py +25 -0
  12. karrio/server/documents/migrations/0007_alter_documenttemplate_related_object.py +18 -0
  13. karrio/server/documents/migrations/0008_documenttemplate_options.py +26 -0
  14. karrio/server/documents/migrations/__init__.py +0 -0
  15. karrio/server/documents/models.py +61 -0
  16. karrio/server/documents/serializers/__init__.py +4 -0
  17. karrio/server/documents/serializers/base.py +89 -0
  18. karrio/server/documents/serializers/documents.py +9 -0
  19. karrio/server/documents/signals.py +29 -0
  20. karrio/server/documents/tests/__init__.py +6 -0
  21. karrio/server/documents/tests/test_generator.py +474 -0
  22. karrio/server/documents/tests/test_templates.py +318 -0
  23. karrio/server/documents/tests.py +182 -0
  24. karrio/server/documents/urls.py +11 -0
  25. karrio/server/documents/utils.py +2486 -0
  26. karrio/server/documents/views/__init__.py +0 -0
  27. karrio/server/documents/views/printers.py +223 -0
  28. karrio/server/documents/views/templates.py +255 -0
  29. karrio/server/graph/schemas/__init__.py +1 -0
  30. karrio/server/graph/schemas/documents/__init__.py +46 -0
  31. karrio/server/graph/schemas/documents/inputs.py +41 -0
  32. karrio/server/graph/schemas/documents/mutations.py +51 -0
  33. karrio/server/graph/schemas/documents/types.py +45 -0
  34. karrio_server_documents-2025.5.2.dist-info/METADATA +19 -0
  35. karrio_server_documents-2025.5.2.dist-info/RECORD +37 -0
  36. karrio_server_documents-2025.5.2.dist-info/WHEEL +5 -0
  37. karrio_server_documents-2025.5.2.dist-info/top_level.txt +2 -0
File without changes
@@ -0,0 +1,223 @@
1
+ import io
2
+ import sys
3
+ import base64
4
+ from django.urls import re_path
5
+ from django.utils import timezone
6
+ from django.http import JsonResponse
7
+ from django.core.files.base import ContentFile
8
+ from django_downloadview import VirtualDownloadView
9
+ from rest_framework import status
10
+
11
+ import karrio.lib as lib
12
+ import karrio.server.openapi as openapi
13
+ import karrio.server.documents.models as models
14
+ import karrio.server.documents.generator as generator
15
+ from karrio.server.core.logging import logger
16
+ from karrio.server.core.utils import validate_resource_token
17
+
18
+
19
+ class TemplateDocsPrinter(VirtualDownloadView):
20
+ def get(self, request, pk: str, slug: str, **kwargs):
21
+ """Generate a document from template."""
22
+ error = validate_resource_token(request, "template", [pk], "render")
23
+ if error:
24
+ return error
25
+
26
+ try:
27
+ template = models.DocumentTemplate.objects.get(pk=pk, slug=slug)
28
+ query_params = request.GET.dict()
29
+
30
+ self.document = generator.Documents.generate_template(
31
+ template, query_params, context=request
32
+ )
33
+ self.name = f"{slug}.pdf"
34
+ self.attachment = "download" in query_params
35
+
36
+ response = super().get(request, pk, slug, **kwargs)
37
+ response["X-Frame-Options"] = "ALLOWALL"
38
+ return response
39
+ except Exception as e:
40
+ logger.exception(
41
+ "Template document generation failed",
42
+ template_id=pk,
43
+ template_slug=slug,
44
+ error=str(e),
45
+ )
46
+ _, __, exc_traceback = sys.exc_info()
47
+ trace = exc_traceback
48
+ while trace and trace.tb_next:
49
+ trace = trace.tb_next
50
+ if "<template>" in str(trace.tb_frame):
51
+ break
52
+
53
+ return JsonResponse(
54
+ dict(error=str(e), line=getattr(trace, "tb_lineno", None)),
55
+ status=status.HTTP_409_CONFLICT,
56
+ )
57
+
58
+ def get_file(self):
59
+ return ContentFile(self.document.getvalue(), name=self.name)
60
+
61
+
62
+ class ShipmentDocsPrinter(VirtualDownloadView):
63
+ @openapi.extend_schema(exclude=True)
64
+ def get(self, request, doc: str = "label", format: str = "pdf", **kwargs):
65
+ """Retrieve batch shipment labels or invoices."""
66
+ from karrio.server.manager.models import Shipment
67
+
68
+ if doc not in ["label", "invoice"]:
69
+ return JsonResponse(
70
+ dict(error=f"Invalid document type: {doc}"),
71
+ status=status.HTTP_400_BAD_REQUEST,
72
+ )
73
+
74
+ resource_ids = request.GET.get("shipments", "").split(",")
75
+ access_type = "batch_labels" if doc == "label" else "batch_invoices"
76
+
77
+ error = validate_resource_token(request, "document", resource_ids, access_type)
78
+ if error:
79
+ return error
80
+
81
+ query_params = request.GET.dict()
82
+ self.attachment = "download" in query_params
83
+ self.format = (format or "").lower()
84
+ self.name = f"{doc}s - {timezone.now()}.{self.format}"
85
+
86
+ _queryset = Shipment.objects.filter(id__in=resource_ids)
87
+ if doc == "label":
88
+ _queryset = _queryset.filter(
89
+ label__isnull=False,
90
+ label_type__contains=self.format.upper(),
91
+ )
92
+ elif doc == "invoice":
93
+ _queryset = _queryset.filter(invoice__isnull=False)
94
+
95
+ self.documents = _queryset.values_list(doc, "label_type")
96
+
97
+ response = super().get(request, doc, self.format, **kwargs)
98
+ response["X-Frame-Options"] = "ALLOWALL"
99
+ return response
100
+
101
+ def get_file(self):
102
+ content = base64.b64decode(
103
+ lib.bundle_base64([doc for doc, _ in self.documents], self.format.upper())
104
+ )
105
+ buffer = io.BytesIO()
106
+ buffer.write(content)
107
+ return ContentFile(buffer.getvalue(), name=self.name)
108
+
109
+
110
+ class OrderDocsPrinter(VirtualDownloadView):
111
+ @openapi.extend_schema(exclude=True)
112
+ def get(self, request, doc: str = "label", format: str = "pdf", **kwargs):
113
+ """Retrieve batch order labels or invoices."""
114
+ from karrio.server.orders.models import Order
115
+
116
+ if doc not in ["label", "invoice"]:
117
+ return JsonResponse(
118
+ dict(error=f"Invalid document type: {doc}"),
119
+ status=status.HTTP_400_BAD_REQUEST,
120
+ )
121
+
122
+ resource_ids = request.GET.get("orders", "").split(",")
123
+ access_type = "batch_labels" if doc == "label" else "batch_invoices"
124
+
125
+ error = validate_resource_token(request, "order", resource_ids, access_type)
126
+ if error:
127
+ return error
128
+
129
+ query_params = request.GET.dict()
130
+ self.attachment = "download" in query_params
131
+ self.format = (format or "").lower()
132
+ self.name = f"{doc}s - {timezone.now()}.{self.format}"
133
+
134
+ _queryset = Order.objects.filter(
135
+ id__in=resource_ids, shipments__id__isnull=False
136
+ ).distinct()
137
+
138
+ if doc == "label":
139
+ _queryset = _queryset.filter(
140
+ shipments__label__isnull=False,
141
+ shipments__label_type__contains=self.format.upper(),
142
+ )
143
+ elif doc == "invoice":
144
+ _queryset = _queryset.filter(shipments__invoice__isnull=False)
145
+
146
+ self.documents = list(
147
+ set(_queryset.values_list(f"shipments__{doc}", "shipments__label_type"))
148
+ )
149
+
150
+ response = super().get(request, doc, self.format, **kwargs)
151
+ response["X-Frame-Options"] = "ALLOWALL"
152
+ return response
153
+
154
+ def get_file(self):
155
+ content = base64.b64decode(
156
+ lib.bundle_base64([doc for doc, _ in self.documents], self.format.upper())
157
+ )
158
+ buffer = io.BytesIO()
159
+ buffer.write(content)
160
+ return ContentFile(buffer.getvalue(), name=self.name)
161
+
162
+
163
+ class ManifestDocsPrinter(VirtualDownloadView):
164
+ @openapi.extend_schema(exclude=True)
165
+ def get(self, request, doc: str = "manifest", format: str = "pdf", **kwargs):
166
+ """Retrieve batch manifests."""
167
+ from karrio.server.manager.models import Manifest
168
+
169
+ if doc not in ["manifest"]:
170
+ return JsonResponse(
171
+ dict(error=f"Invalid document type: {doc}"),
172
+ status=status.HTTP_400_BAD_REQUEST,
173
+ )
174
+
175
+ resource_ids = request.GET.get("manifests", "").split(",")
176
+
177
+ error = validate_resource_token(request, "document", resource_ids, "batch_manifests")
178
+ if error:
179
+ return error
180
+
181
+ query_params = request.GET.dict()
182
+ self.attachment = "download" in query_params
183
+ self.format = (format or "").lower()
184
+ self.name = f"{doc}s - {timezone.now()}.{self.format}"
185
+
186
+ queryset = Manifest.objects.filter(id__in=resource_ids, manifest__isnull=False)
187
+ self.documents = queryset.values_list(doc, "reference")
188
+
189
+ response = super().get(request, doc, self.format, **kwargs)
190
+ response["X-Frame-Options"] = "ALLOWALL"
191
+ return response
192
+
193
+ def get_file(self):
194
+ content = base64.b64decode(
195
+ lib.bundle_base64([doc for doc, _ in self.documents], self.format.upper())
196
+ )
197
+ buffer = io.BytesIO()
198
+ buffer.write(content)
199
+ return ContentFile(buffer.getvalue(), name=self.name)
200
+
201
+
202
+ urlpatterns = [
203
+ re_path(
204
+ r"^documents/templates/(?P<pk>\w+).(?P<slug>\w+)",
205
+ TemplateDocsPrinter.as_view(),
206
+ name="templates-documents-print",
207
+ ),
208
+ re_path(
209
+ r"^documents/shipments/(?P<doc>[a-z0-9]+).(?P<format>[a-zA-Z0-9]+)",
210
+ ShipmentDocsPrinter.as_view(),
211
+ name="shipments-documents-print",
212
+ ),
213
+ re_path(
214
+ r"^documents/orders/(?P<doc>[a-z0-9]+).(?P<format>[a-zA-Z0-9]+)",
215
+ OrderDocsPrinter.as_view(),
216
+ name="orders-documents-print",
217
+ ),
218
+ re_path(
219
+ r"^documents/manifests/(?P<doc>[a-z0-9]+).(?P<format>[a-zA-Z0-9]+)",
220
+ ManifestDocsPrinter.as_view(),
221
+ name="manifests-documents-print",
222
+ ),
223
+ ]
@@ -0,0 +1,255 @@
1
+ import base64
2
+ import django.urls as urls
3
+ from rest_framework import status
4
+ from rest_framework.request import Request
5
+ from rest_framework.response import Response
6
+ import rest_framework.pagination as pagination
7
+
8
+ import karrio.lib as lib
9
+ import karrio.server.openapi as openapi
10
+ import karrio.server.core.views.api as api
11
+ import karrio.server.documents.models as models
12
+ import karrio.server.documents.generator as generator
13
+ import karrio.server.documents.serializers as serializers
14
+ from karrio.server.core.logging import logger
15
+
16
+ ENDPOINT_ID = "&&&&$$" # This endpoint id is used to make operation ids unique make sure not to duplicate
17
+ DocumentTemplates = serializers.PaginatedResult(
18
+ "DocumentTemplateList", serializers.DocumentTemplate
19
+ )
20
+
21
+
22
+ class DocumentTemplateList(api.GenericAPIView):
23
+ queryset = models.DocumentTemplate.objects
24
+ pagination_class = type(
25
+ "CustomPagination",
26
+ (pagination.LimitOffsetPagination,),
27
+ dict(default_limit=20),
28
+ )
29
+ serializer_class = DocumentTemplates
30
+
31
+ @openapi.extend_schema(
32
+ tags=["Documents"],
33
+ operation_id=f"{ENDPOINT_ID}list",
34
+ extensions={"x-operationId": "listDocumentTemplates"},
35
+ summary="List all templates",
36
+ responses={
37
+ 200: DocumentTemplates(),
38
+ 404: serializers.ErrorResponse(),
39
+ 500: serializers.ErrorResponse(),
40
+ },
41
+ )
42
+ def get(self, request: Request):
43
+ """
44
+ Retrieve all templates.
45
+ """
46
+ templates = models.DocumentTemplate.access_by(request)
47
+ response = self.paginate_queryset(
48
+ serializers.DocumentTemplate(templates, many=True).data
49
+ )
50
+ return self.get_paginated_response(response)
51
+
52
+ @openapi.extend_schema(
53
+ tags=["Documents"],
54
+ operation_id=f"{ENDPOINT_ID}create",
55
+ extensions={"x-operationId": "createDocumentTemplate"},
56
+ summary="Create a template",
57
+ request=serializers.DocumentTemplateData(),
58
+ responses={
59
+ 201: serializers.DocumentTemplate(),
60
+ 400: serializers.ErrorResponse(),
61
+ 500: serializers.ErrorResponse(),
62
+ },
63
+ )
64
+ def post(self, request: Request):
65
+ """
66
+ Create a new template.
67
+ """
68
+ template = (
69
+ serializers.DocumentTemplateModelSerializer.map(
70
+ data=request.data, context=request
71
+ )
72
+ .save()
73
+ .instance
74
+ )
75
+
76
+ return Response(
77
+ serializers.DocumentTemplate(template).data,
78
+ status=status.HTTP_201_CREATED,
79
+ )
80
+
81
+
82
+ class DocumentTemplateDetail(api.APIView):
83
+
84
+ @openapi.extend_schema(
85
+ tags=["Documents"],
86
+ operation_id=f"{ENDPOINT_ID}retrieve",
87
+ extensions={"x-operationId": "retrieveDocumentTemplate"},
88
+ summary="Retrieve a template",
89
+ responses={
90
+ 200: serializers.DocumentTemplate(),
91
+ 400: serializers.ErrorResponse(),
92
+ 500: serializers.ErrorResponse(),
93
+ },
94
+ )
95
+ def get(self, request: Request, pk: str):
96
+ """
97
+ Retrieve a template.
98
+ """
99
+ template = models.DocumentTemplate.access_by(request).get(pk=pk)
100
+ return Response(serializers.DocumentTemplate(template).data)
101
+
102
+ @openapi.extend_schema(
103
+ tags=["Documents"],
104
+ operation_id=f"{ENDPOINT_ID}update",
105
+ extensions={"x-operationId": "updateDocumentTemplate"},
106
+ summary="Update a template",
107
+ request=serializers.DocumentTemplateData(),
108
+ responses={
109
+ 200: serializers.DocumentTemplate(),
110
+ 400: serializers.ErrorResponse(),
111
+ 404: serializers.ErrorResponse(),
112
+ 500: serializers.ErrorResponse(),
113
+ },
114
+ )
115
+ def patch(self, request: Request, pk: str):
116
+ """
117
+ update a template.
118
+ """
119
+ template = models.DocumentTemplate.access_by(request).get(pk=pk)
120
+
121
+ serializers.DocumentTemplateModelSerializer.map(
122
+ template,
123
+ data=request.data,
124
+ ).save()
125
+
126
+ return Response(serializers.DocumentTemplate(template).data)
127
+
128
+ @openapi.extend_schema(
129
+ tags=["Documents"],
130
+ operation_id=f"{ENDPOINT_ID}discard",
131
+ extensions={"x-operationId": "discardDocumentTemplate"},
132
+ summary="Delete a template",
133
+ responses={
134
+ 200: serializers.DocumentTemplate(),
135
+ 404: serializers.ErrorResponse(),
136
+ 409: serializers.ErrorResponse(),
137
+ 500: serializers.ErrorResponse(),
138
+ },
139
+ )
140
+ def delete(self, request: Request, pk: str):
141
+ """
142
+ Delete a template.
143
+ """
144
+ template = models.DocumentTemplate.access_by(request).get(pk=pk)
145
+
146
+ template.delete(keep_parents=True)
147
+
148
+ return Response(serializers.DocumentTemplate(template).data)
149
+
150
+
151
+ class DocumentGenerator(api.APIView):
152
+ @openapi.extend_schema(
153
+ tags=["Documents"],
154
+ operation_id=f"{ENDPOINT_ID}generateDocument",
155
+ summary="Generate a document",
156
+ request=serializers.DocumentData(),
157
+ responses={
158
+ 201: serializers.GeneratedDocument(),
159
+ 400: serializers.ErrorResponse(),
160
+ 404: serializers.ErrorResponse(),
161
+ 500: serializers.ErrorResponse(),
162
+ },
163
+ )
164
+ def post(self, request: Request):
165
+ """Generate any document.
166
+ This API is designed to be used to generate GS1 labels,
167
+ invoices and any document that requires external data.
168
+ """
169
+ try:
170
+ data = serializers.DocumentData.map(data=request.data).data
171
+
172
+ if data.get("template") is None and data.get("template_id") is None:
173
+ return Response(
174
+ {"errors": [{"message": "template or template_id is required"}]},
175
+ status=status.HTTP_400_BAD_REQUEST,
176
+ )
177
+
178
+ document_template = lib.identity(
179
+ None
180
+ if data.get("template_id") is None
181
+ else models.DocumentTemplate.objects.get(pk=data.get("template_id"))
182
+ )
183
+
184
+ try:
185
+ doc_file = generator.Documents.generate(
186
+ getattr(document_template, "template", data.get("template")),
187
+ related_object=getattr(document_template, "related_object", None),
188
+ metadata=getattr(document_template, "metadata", {}),
189
+ data=data.get("data", {}),
190
+ options=data.get("options", {}),
191
+ doc_name=data.get("doc_name"),
192
+ doc_format=data.get("doc_format"),
193
+ )
194
+
195
+ document = serializers.GeneratedDocument.map(
196
+ data={
197
+ **data,
198
+ "doc_file": base64.b64encode(doc_file.getvalue()).decode("utf-8"),
199
+ }
200
+ )
201
+
202
+ return Response(document.data, status=status.HTTP_201_CREATED)
203
+
204
+ except generator.TemplateRenderingError as e:
205
+ error_message = e.message
206
+ if e.line_number:
207
+ error_message += f" (line {e.line_number})"
208
+
209
+ return Response(
210
+ {"errors": [{"message": error_message}]},
211
+ status=status.HTTP_400_BAD_REQUEST,
212
+ )
213
+ except models.DocumentTemplate.DoesNotExist:
214
+ return Response(
215
+ {"errors": [{"message": f"Document template with id '{data.get('template_id')}' not found"}]},
216
+ status=status.HTTP_404_NOT_FOUND,
217
+ )
218
+ except Exception as e:
219
+ logger.exception("Document generation failed", template_id=data.get('template_id'), error=str(e))
220
+ return Response(
221
+ {"errors": [{"message": f"Document generation failed: {str(e)}"}]},
222
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
223
+ )
224
+
225
+ except serializers.ValidationError as e:
226
+ logger.error("Document validation error", error=str(e))
227
+ return Response(
228
+ {"errors": [{"message": "Invalid input data"}]},
229
+ status=status.HTTP_400_BAD_REQUEST,
230
+ )
231
+ except Exception as e:
232
+ logger.exception("Unexpected error in document generator", error=str(e))
233
+ return Response(
234
+ {"errors": [{"message": "Internal server error"}]},
235
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
236
+ )
237
+
238
+
239
+ urlpatterns = [
240
+ urls.path(
241
+ "documents/templates",
242
+ DocumentTemplateList.as_view(),
243
+ name="document-template-list",
244
+ ),
245
+ urls.path(
246
+ "documents/templates/<str:pk>",
247
+ DocumentTemplateDetail.as_view(),
248
+ name="document-template-details",
249
+ ),
250
+ urls.path(
251
+ "documents/generate",
252
+ DocumentGenerator.as_view(),
253
+ name="document-generator",
254
+ ),
255
+ ]
@@ -0,0 +1 @@
1
+ __path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore
@@ -0,0 +1,46 @@
1
+ import strawberry
2
+ from strawberry.types import Info
3
+
4
+ import karrio.server.graph.utils as utils
5
+ import karrio.server.graph.schemas.base as base
6
+ import karrio.server.graph.schemas.documents.mutations as mutations
7
+ import karrio.server.graph.schemas.documents.inputs as inputs
8
+ import karrio.server.graph.schemas.documents.types as types
9
+ import karrio.server.documents.models as models
10
+
11
+ extra_types: list = []
12
+
13
+
14
+ @strawberry.type
15
+ class Query:
16
+ document_template: types.DocumentTemplateType = strawberry.field(
17
+ resolver=types.DocumentTemplateType.resolve
18
+ )
19
+ document_templates: utils.Connection[types.DocumentTemplateType] = strawberry.field(
20
+ resolver=types.DocumentTemplateType.resolve_list
21
+ )
22
+
23
+
24
+ @strawberry.type
25
+ class Mutation:
26
+ @strawberry.mutation
27
+ def create_document_template(
28
+ self, info: Info, input: inputs.CreateDocumentTemplateMutationInput
29
+ ) -> mutations.CreateDocumentTemplateMutation:
30
+ return mutations.CreateDocumentTemplateMutation.mutate(info, **input.to_dict())
31
+
32
+ @strawberry.mutation
33
+ def update_document_template(
34
+ self, info: Info, input: inputs.UpdateDocumentTemplateMutationInput
35
+ ) -> mutations.UpdateDocumentTemplateMutation:
36
+ return mutations.UpdateDocumentTemplateMutation.mutate(info, **input.to_dict())
37
+
38
+ @strawberry.mutation
39
+ def delete_document_template(
40
+ self, info: Info, input: base.inputs.DeleteMutationInput
41
+ ) -> base.mutations.DeleteMutation:
42
+ return base.mutations.DeleteMutation.mutate(
43
+ info,
44
+ model=models.DocumentTemplate,
45
+ **input.to_dict()
46
+ )
@@ -0,0 +1,41 @@
1
+ import typing
2
+ import strawberry
3
+
4
+ import karrio.server.graph.utils as utils
5
+ import karrio.server.documents.serializers as serializers
6
+
7
+ TemplateRelatedObjectEnum: typing.Any = strawberry.enum(
8
+ serializers.TemplateRelatedObject
9
+ )
10
+
11
+
12
+ @strawberry.input
13
+ class CreateDocumentTemplateMutationInput(utils.BaseInput):
14
+ slug: str
15
+ name: str
16
+ template: str
17
+ active: typing.Optional[bool] = True
18
+ description: typing.Optional[str] = strawberry.UNSET
19
+ metadata: typing.Optional[utils.JSON] = strawberry.UNSET
20
+ related_object: typing.Optional[TemplateRelatedObjectEnum] = strawberry.UNSET
21
+ options: typing.Optional[utils.JSON] = strawberry.UNSET
22
+
23
+
24
+ @strawberry.input
25
+ class UpdateDocumentTemplateMutationInput(utils.BaseInput):
26
+ id: str
27
+ slug: typing.Optional[str] = strawberry.UNSET
28
+ name: typing.Optional[str] = strawberry.UNSET
29
+ template: typing.Optional[str] = strawberry.UNSET
30
+ active: typing.Optional[bool] = strawberry.UNSET
31
+ description: typing.Optional[str] = strawberry.UNSET
32
+ metadata: typing.Optional[utils.JSON] = strawberry.UNSET
33
+ related_object: typing.Optional[TemplateRelatedObjectEnum] = strawberry.UNSET
34
+ options: typing.Optional[utils.JSON] = strawberry.UNSET
35
+
36
+
37
+ @strawberry.input
38
+ class DocumentTemplateFilter(utils.Paginated):
39
+ name: typing.Optional[str] = strawberry.UNSET
40
+ active: typing.Optional[bool] = strawberry.UNSET
41
+ related_object: typing.Optional[TemplateRelatedObjectEnum] = strawberry.UNSET
@@ -0,0 +1,51 @@
1
+ import typing
2
+ import strawberry
3
+ from strawberry.types import Info
4
+
5
+ import karrio.server.graph.utils as utils
6
+ import karrio.server.graph.schemas.documents.types as types
7
+ import karrio.server.graph.schemas.documents.inputs as inputs
8
+ import karrio.server.documents.serializers as serializers
9
+ import karrio.server.documents.models as models
10
+
11
+
12
+ @strawberry.type
13
+ class CreateDocumentTemplateMutation(utils.BaseMutation):
14
+ template: typing.Optional[types.DocumentTemplateType] = None
15
+
16
+ @staticmethod
17
+ @utils.authentication_required
18
+ @utils.authorization_required(["DOCUMENTS_MANAGEMENT", "manage_data"])
19
+ def mutate(
20
+ info: Info, **input: inputs.CreateDocumentTemplateMutationInput
21
+ ) -> "CreateDocumentTemplateMutation":
22
+ serializer = serializers.DocumentTemplateModelSerializer(
23
+ data=input,
24
+ context=info.context.request,
25
+ )
26
+ serializer.is_valid(raise_exception=True)
27
+
28
+ return CreateDocumentTemplateMutation(template=serializer.save()) # type:ignore
29
+
30
+
31
+ @strawberry.type
32
+ class UpdateDocumentTemplateMutation(utils.BaseMutation):
33
+ template: typing.Optional[types.DocumentTemplateType] = None
34
+
35
+ @staticmethod
36
+ @utils.authentication_required
37
+ @utils.authorization_required(["DOCUMENTS_MANAGEMENT", "manage_data"])
38
+ def mutate(
39
+ info: Info, **input: inputs.UpdateDocumentTemplateMutationInput
40
+ ) -> "UpdateDocumentTemplateMutation":
41
+ instance = models.DocumentTemplate.access_by(info.context.request).get(id=input["id"])
42
+
43
+ serializer = serializers.DocumentTemplateModelSerializer(
44
+ instance,
45
+ data=input,
46
+ partial=True,
47
+ context=info.context.request,
48
+ )
49
+ serializer.is_valid(raise_exception=True)
50
+
51
+ return UpdateDocumentTemplateMutation(template=serializer.save()) # type:ignore
@@ -0,0 +1,45 @@
1
+ import typing
2
+ import datetime
3
+ import strawberry
4
+
5
+ import karrio.lib as lib
6
+ import karrio.server.graph.utils as utils
7
+ import karrio.server.graph.schemas.base.types as base
8
+ import karrio.server.graph.schemas.documents.inputs as inputs
9
+ import karrio.server.documents.models as models
10
+ import karrio.server.documents.filters as filters
11
+
12
+
13
+ @strawberry.type
14
+ class DocumentTemplateType:
15
+ object_type: str
16
+ preview_url: typing.Optional[str]
17
+ id: str
18
+ name: str
19
+ slug: str
20
+ template: str
21
+ active: bool
22
+ description: typing.Optional[str]
23
+ related_object: typing.Optional[inputs.TemplateRelatedObjectEnum]
24
+ metadata: typing.Optional[utils.JSON]
25
+ options: typing.Optional[utils.JSON]
26
+ created_at: typing.Optional[datetime.datetime]
27
+ updated_at: typing.Optional[datetime.datetime]
28
+ created_by: typing.Optional[base.UserType]
29
+
30
+ @staticmethod
31
+ @utils.authentication_required
32
+ def resolve(info, id: str) -> typing.Optional["DocumentTemplateType"]:
33
+ return models.DocumentTemplate.access_by(info.context.request).filter(id=id).first()
34
+
35
+ @staticmethod
36
+ @utils.authentication_required
37
+ def resolve_list(
38
+ info,
39
+ filter: typing.Optional[inputs.DocumentTemplateFilter] = strawberry.UNSET,
40
+ ) -> utils.Connection["DocumentTemplateType"]:
41
+ _filter = filter if filter is not strawberry.UNSET else inputs.DocumentTemplateFilter()
42
+ queryset = filters.DocumentTemplateFilter(
43
+ _filter.to_dict(), models.DocumentTemplate.access_by(info.context.request)
44
+ ).qs
45
+ return utils.paginated_connection(queryset, **_filter.pagination())
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: karrio_server_documents
3
+ Version: 2025.5.2
4
+ Summary: Multi-carrier shipping API apps module
5
+ Author-email: karrio <hello@karrio.io>
6
+ License-Expression: LGPL-3.0
7
+ Project-URL: Homepage, https://github.com/karrioapi/karrio
8
+ Classifier: Programming Language :: Python :: 3
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: pyzint
12
+ Requires-Dist: weasyprint
13
+ Requires-Dist: karrio_server_core
14
+ Requires-Dist: karrio_server_graph
15
+ Requires-Dist: karrio_server_manager
16
+
17
+ # karrio-server
18
+
19
+ Karrio server documents module.