karrio-server-graph 2025.5rc1__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.
- karrio/server/graph/__init__.py +1 -0
- karrio/server/graph/admin.py +3 -0
- karrio/server/graph/apps.py +5 -0
- karrio/server/graph/forms.py +59 -0
- karrio/server/graph/management/__init__.py +0 -0
- karrio/server/graph/management/commands/__init__.py +0 -0
- karrio/server/graph/management/commands/export_schema.py +9 -0
- karrio/server/graph/migrations/0001_initial.py +37 -0
- karrio/server/graph/migrations/0002_auto_20210512_1353.py +22 -0
- karrio/server/graph/migrations/__init__.py +0 -0
- karrio/server/graph/models.py +44 -0
- karrio/server/graph/schema.py +46 -0
- karrio/server/graph/schemas/__init__.py +2 -0
- karrio/server/graph/schemas/base/__init__.py +367 -0
- karrio/server/graph/schemas/base/inputs.py +582 -0
- karrio/server/graph/schemas/base/mutations.py +871 -0
- karrio/server/graph/schemas/base/types.py +1365 -0
- karrio/server/graph/serializers.py +388 -0
- karrio/server/graph/templates/graphql/graphiql.html +142 -0
- karrio/server/graph/templates/karrio/email_change_email.html +13 -0
- karrio/server/graph/templates/karrio/email_change_email.txt +13 -0
- karrio/server/graph/templates/karrio/password_reset_email.html +14 -0
- karrio/server/graph/tests/__init__.py +9 -0
- karrio/server/graph/tests/base.py +124 -0
- karrio/server/graph/tests/test_carrier_connections.py +219 -0
- karrio/server/graph/tests/test_metafield.py +404 -0
- karrio/server/graph/tests/test_rate_sheets.py +348 -0
- karrio/server/graph/tests/test_templates.py +677 -0
- karrio/server/graph/tests/test_user_info.py +71 -0
- karrio/server/graph/urls.py +10 -0
- karrio/server/graph/utils.py +304 -0
- karrio/server/graph/views.py +93 -0
- karrio/server/settings/graph.py +7 -0
- karrio_server_graph-2025.5rc1.dist-info/METADATA +29 -0
- karrio_server_graph-2025.5rc1.dist-info/RECORD +37 -0
- karrio_server_graph-2025.5rc1.dist-info/WHEEL +5 -0
- karrio_server_graph-2025.5rc1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,388 @@
|
|
1
|
+
import typing
|
2
|
+
import strawberry
|
3
|
+
from django.db import transaction
|
4
|
+
from django.contrib.auth import get_user_model
|
5
|
+
from django.utils.translation import gettext_lazy as _
|
6
|
+
from rest_framework import exceptions
|
7
|
+
|
8
|
+
import karrio.server.serializers as serializers
|
9
|
+
import karrio.server.core.validators as validators
|
10
|
+
import karrio.server.providers.models as providers
|
11
|
+
import karrio.server.manager.models as manager
|
12
|
+
import karrio.server.graph.models as graph
|
13
|
+
import karrio.server.core.models as core
|
14
|
+
import karrio.server.user.models as auth
|
15
|
+
|
16
|
+
|
17
|
+
class UserModelSerializer(serializers.ModelSerializer):
|
18
|
+
email = serializers.CharField(required=False)
|
19
|
+
|
20
|
+
class Meta:
|
21
|
+
model = get_user_model()
|
22
|
+
extra_kwargs = {
|
23
|
+
field: {"read_only": True}
|
24
|
+
for field in ["id", "is_staff", "last_login", "date_joined"]
|
25
|
+
}
|
26
|
+
fields = [
|
27
|
+
"email",
|
28
|
+
"full_name",
|
29
|
+
"is_active",
|
30
|
+
"is_staff",
|
31
|
+
"last_login",
|
32
|
+
"date_joined",
|
33
|
+
]
|
34
|
+
|
35
|
+
@transaction.atomic
|
36
|
+
def update(self, instance, data: dict, **kwargs):
|
37
|
+
user = super().update(instance, data)
|
38
|
+
|
39
|
+
if data.get("is_active") == False:
|
40
|
+
user.save(update_fields=["is_active"])
|
41
|
+
|
42
|
+
return user
|
43
|
+
|
44
|
+
|
45
|
+
@serializers.owned_model_serializer
|
46
|
+
class WorkspaceConfigModelSerializer(serializers.ModelSerializer):
|
47
|
+
class Meta:
|
48
|
+
model = auth.WorkspaceConfig
|
49
|
+
extra_kwargs = {field: {"read_only": True} for field in ["id"]}
|
50
|
+
exclude = ["created_at", "updated_at", "created_by"]
|
51
|
+
|
52
|
+
def create(
|
53
|
+
self, validated_data: dict, context: serializers.Context = None, **kwargs
|
54
|
+
):
|
55
|
+
instance = super().create(validated_data, context=context, **kwargs)
|
56
|
+
|
57
|
+
if (
|
58
|
+
hasattr(auth.WorkspaceConfig, "org")
|
59
|
+
and getattr(context, "org", None) is not None
|
60
|
+
):
|
61
|
+
context.org.config = instance
|
62
|
+
context.org.save()
|
63
|
+
|
64
|
+
return instance
|
65
|
+
|
66
|
+
|
67
|
+
@serializers.owned_model_serializer
|
68
|
+
class MetafieldModelSerializer(serializers.ModelSerializer):
|
69
|
+
class Meta:
|
70
|
+
model = core.Metafield
|
71
|
+
extra_kwargs = {field: {"read_only": True} for field in ["id"]}
|
72
|
+
exclude = ["created_at", "updated_at", "created_by"]
|
73
|
+
|
74
|
+
|
75
|
+
@serializers.owned_model_serializer
|
76
|
+
class AddressModelSerializer(
|
77
|
+
validators.AugmentedAddressSerializer, serializers.ModelSerializer
|
78
|
+
):
|
79
|
+
country_code = serializers.CharField(required=False)
|
80
|
+
|
81
|
+
class Meta:
|
82
|
+
model = manager.Address
|
83
|
+
extra_kwargs = {field: {"read_only": True} for field in ["id", "validation"]}
|
84
|
+
exclude = ["created_at", "updated_at", "created_by", "validation"]
|
85
|
+
|
86
|
+
|
87
|
+
@serializers.owned_model_serializer
|
88
|
+
class CommodityModelSerializer(serializers.ModelSerializer):
|
89
|
+
weight_unit = serializers.CharField()
|
90
|
+
value_currency = serializers.CharField(required=False)
|
91
|
+
origin_country = serializers.CharField(required=False)
|
92
|
+
|
93
|
+
class Meta:
|
94
|
+
model = manager.Commodity
|
95
|
+
exclude = ["created_at", "updated_at", "created_by", "parent"]
|
96
|
+
extra_kwargs = {field: {"read_only": True} for field in ["id", "parent"]}
|
97
|
+
|
98
|
+
|
99
|
+
@serializers.owned_model_serializer
|
100
|
+
class CustomsModelSerializer(serializers.ModelSerializer):
|
101
|
+
NESTED_FIELDS = ["commodities"]
|
102
|
+
|
103
|
+
incoterm = serializers.CharField(required=False, allow_null=True, allow_blank=True)
|
104
|
+
commodities = serializers.make_fields_optional(CommodityModelSerializer)(
|
105
|
+
many=True, allow_null=True, required=False
|
106
|
+
)
|
107
|
+
|
108
|
+
class Meta:
|
109
|
+
model = manager.Customs
|
110
|
+
exclude = ["created_at", "updated_at", "created_by"]
|
111
|
+
extra_kwargs = {field: {"read_only": True} for field in ["id"]}
|
112
|
+
|
113
|
+
@transaction.atomic
|
114
|
+
def create(self, validated_data: dict, context: dict):
|
115
|
+
data = {
|
116
|
+
name: value
|
117
|
+
for name, value in validated_data.items()
|
118
|
+
if name not in self.NESTED_FIELDS
|
119
|
+
}
|
120
|
+
|
121
|
+
instance = super().create(data)
|
122
|
+
|
123
|
+
serializers.save_many_to_many_data(
|
124
|
+
"commodities",
|
125
|
+
CommodityModelSerializer,
|
126
|
+
instance,
|
127
|
+
payload=validated_data,
|
128
|
+
context=context,
|
129
|
+
)
|
130
|
+
|
131
|
+
return instance
|
132
|
+
|
133
|
+
@transaction.atomic
|
134
|
+
def update(
|
135
|
+
self, instance: manager.Customs, validated_data: dict, **kwargs
|
136
|
+
) -> manager.Customs:
|
137
|
+
data = {
|
138
|
+
name: value
|
139
|
+
for name, value in validated_data.items()
|
140
|
+
if name not in self.NESTED_FIELDS
|
141
|
+
}
|
142
|
+
|
143
|
+
return super().update(instance, data)
|
144
|
+
|
145
|
+
|
146
|
+
@serializers.owned_model_serializer
|
147
|
+
class ParcelModelSerializer(validators.PresetSerializer, serializers.ModelSerializer):
|
148
|
+
weight_unit = serializers.CharField(
|
149
|
+
required=False, allow_null=True, allow_blank=True
|
150
|
+
)
|
151
|
+
dimension_unit = serializers.CharField(
|
152
|
+
required=False, allow_null=True, allow_blank=True
|
153
|
+
)
|
154
|
+
|
155
|
+
class Meta:
|
156
|
+
model = manager.Parcel
|
157
|
+
exclude = ["created_at", "updated_at", "created_by", "items"]
|
158
|
+
extra_kwargs = {field: {"read_only": True} for field in ["id"]}
|
159
|
+
|
160
|
+
|
161
|
+
@serializers.owned_model_serializer
|
162
|
+
class TemplateModelSerializer(serializers.ModelSerializer):
|
163
|
+
address = serializers.make_fields_optional(AddressModelSerializer)(required=False)
|
164
|
+
customs = serializers.make_fields_optional(CustomsModelSerializer)(required=False)
|
165
|
+
parcel = serializers.make_fields_optional(ParcelModelSerializer)(required=False)
|
166
|
+
|
167
|
+
class Meta:
|
168
|
+
model = graph.Template
|
169
|
+
exclude = ["created_at", "updated_at", "created_by"]
|
170
|
+
extra_kwargs = {field: {"read_only": True} for field in ["id"]}
|
171
|
+
|
172
|
+
@transaction.atomic
|
173
|
+
def create(self, validated_data: dict, context: dict, **kwargs) -> graph.Template:
|
174
|
+
data = {
|
175
|
+
**validated_data,
|
176
|
+
"address": serializers.save_one_to_one_data(
|
177
|
+
"address",
|
178
|
+
AddressModelSerializer,
|
179
|
+
payload=validated_data,
|
180
|
+
context=context,
|
181
|
+
),
|
182
|
+
"customs": serializers.save_one_to_one_data(
|
183
|
+
"customs",
|
184
|
+
CustomsModelSerializer,
|
185
|
+
payload=validated_data,
|
186
|
+
context=context,
|
187
|
+
),
|
188
|
+
"parcel": serializers.save_one_to_one_data(
|
189
|
+
"parcel", ParcelModelSerializer, payload=validated_data, context=context
|
190
|
+
),
|
191
|
+
}
|
192
|
+
|
193
|
+
ensure_unique_default_related_data(validated_data, context=context)
|
194
|
+
|
195
|
+
return super().create(data)
|
196
|
+
|
197
|
+
@transaction.atomic
|
198
|
+
def update(
|
199
|
+
self, instance: graph.Template, validated_data: dict, **kwargs
|
200
|
+
) -> graph.Template:
|
201
|
+
data = {
|
202
|
+
key: value
|
203
|
+
for key, value in validated_data.items()
|
204
|
+
if key not in ["address", "customs", "parcel"]
|
205
|
+
}
|
206
|
+
|
207
|
+
serializers.save_one_to_one_data(
|
208
|
+
"address", AddressModelSerializer, instance, payload=validated_data
|
209
|
+
)
|
210
|
+
serializers.save_one_to_one_data(
|
211
|
+
"customs", CustomsModelSerializer, instance, payload=validated_data
|
212
|
+
)
|
213
|
+
serializers.save_one_to_one_data(
|
214
|
+
"parcel", ParcelModelSerializer, instance, payload=validated_data
|
215
|
+
)
|
216
|
+
|
217
|
+
ensure_unique_default_related_data(validated_data, instance)
|
218
|
+
|
219
|
+
return super().update(instance, data)
|
220
|
+
|
221
|
+
|
222
|
+
def ensure_unique_default_related_data(
|
223
|
+
data: dict = None, instance: typing.Optional[graph.Template] = None, context=None
|
224
|
+
):
|
225
|
+
_get = lambda key: data.get(key, getattr(instance, key, None))
|
226
|
+
if _get("is_default") is not True:
|
227
|
+
return
|
228
|
+
|
229
|
+
if _get("address") is not None:
|
230
|
+
query = dict(address__isnull=False, is_default=True)
|
231
|
+
elif _get("customs") is not None:
|
232
|
+
query = dict(customs__isnull=False, is_default=True)
|
233
|
+
elif _get("parcel") is not None:
|
234
|
+
query = dict(parcel__isnull=False, is_default=True)
|
235
|
+
else:
|
236
|
+
return
|
237
|
+
|
238
|
+
graph.Template.access_by(context or instance.created_by).exclude(
|
239
|
+
id=_get("id")
|
240
|
+
).filter(**query).update(is_default=False)
|
241
|
+
|
242
|
+
|
243
|
+
@serializers.owned_model_serializer
|
244
|
+
class ServiceLevelModelSerializer(serializers.ModelSerializer):
|
245
|
+
dimension_unit = serializers.CharField(
|
246
|
+
required=False, allow_null=True, allow_blank=True
|
247
|
+
)
|
248
|
+
weight_unit = serializers.CharField(
|
249
|
+
required=False, allow_null=True, allow_blank=True
|
250
|
+
)
|
251
|
+
currency = serializers.CharField(required=False, allow_null=True, allow_blank=True)
|
252
|
+
|
253
|
+
class Meta:
|
254
|
+
model = providers.ServiceLevel
|
255
|
+
exclude = ["created_at", "updated_at", "created_by"]
|
256
|
+
extra_kwargs = {field: {"read_only": True} for field in ["id"]}
|
257
|
+
|
258
|
+
def update_zone(self, zone_index: int, zone_data: dict) -> None:
|
259
|
+
"""Update a specific zone in the service level."""
|
260
|
+
if zone_index >= len(self.instance.zones):
|
261
|
+
raise exceptions.ValidationError(
|
262
|
+
_(f"Zone index {zone_index} is out of range"),
|
263
|
+
code="invalid_zone_index",
|
264
|
+
)
|
265
|
+
|
266
|
+
self.instance.zones[zone_index].update(
|
267
|
+
{k: v for k, v in zone_data.items() if v != strawberry.UNSET}
|
268
|
+
)
|
269
|
+
self.instance.save(update_fields=["zones"])
|
270
|
+
|
271
|
+
def update(self, instance, validated_data):
|
272
|
+
"""Handle partial updates of service level data including zones."""
|
273
|
+
zones_data = validated_data.pop("zones", None)
|
274
|
+
instance = super().update(instance, validated_data)
|
275
|
+
|
276
|
+
if zones_data is not None:
|
277
|
+
# Handle zone updates if provided
|
278
|
+
existing_zones = instance.zones or []
|
279
|
+
updated_zones = []
|
280
|
+
|
281
|
+
for idx, zone_data in enumerate(zones_data):
|
282
|
+
if idx < len(existing_zones):
|
283
|
+
# Update existing zone
|
284
|
+
zone = existing_zones[idx].copy()
|
285
|
+
zone.update(
|
286
|
+
{k: v for k, v in zone_data.items() if v != strawberry.UNSET}
|
287
|
+
)
|
288
|
+
updated_zones.append(zone)
|
289
|
+
else:
|
290
|
+
# Add new zone
|
291
|
+
updated_zones.append(zone_data)
|
292
|
+
|
293
|
+
instance.zones = updated_zones
|
294
|
+
instance.save(update_fields=["zones"])
|
295
|
+
|
296
|
+
return instance
|
297
|
+
|
298
|
+
|
299
|
+
@serializers.owned_model_serializer
|
300
|
+
class LabelTemplateModelSerializer(serializers.ModelSerializer):
|
301
|
+
template_type = serializers.CharField(required=False)
|
302
|
+
|
303
|
+
class Meta:
|
304
|
+
model = providers.LabelTemplate
|
305
|
+
exclude = ["created_at", "updated_at", "created_by"]
|
306
|
+
extra_kwargs = {field: {"read_only": True} for field in ["id"]}
|
307
|
+
|
308
|
+
|
309
|
+
@serializers.owned_model_serializer
|
310
|
+
class RateSheetModelSerializer(serializers.ModelSerializer):
|
311
|
+
class Meta:
|
312
|
+
model = providers.RateSheet
|
313
|
+
exclude = ["created_at", "updated_at", "created_by"]
|
314
|
+
extra_kwargs = {field: {"read_only": True} for field in ["id", "services"]}
|
315
|
+
|
316
|
+
def update_services(
|
317
|
+
self, services_data: list, remove_missing: bool = False
|
318
|
+
) -> None:
|
319
|
+
"""Update services of the rate sheet."""
|
320
|
+
existing_services = {s.id: s for s in self.instance.services.all()}
|
321
|
+
|
322
|
+
for service_data in services_data:
|
323
|
+
service_id = service_data.get("id")
|
324
|
+
if service_id and service_id in existing_services:
|
325
|
+
# Update existing service
|
326
|
+
service = existing_services[service_id]
|
327
|
+
service_serializer = ServiceLevelModelSerializer(
|
328
|
+
service,
|
329
|
+
data=service_data,
|
330
|
+
context=self.context,
|
331
|
+
partial=True,
|
332
|
+
)
|
333
|
+
service_serializer.is_valid(raise_exception=True)
|
334
|
+
service_serializer.save()
|
335
|
+
else:
|
336
|
+
# Create new service
|
337
|
+
service_serializer = ServiceLevelModelSerializer(
|
338
|
+
data=service_data,
|
339
|
+
context=self.context,
|
340
|
+
)
|
341
|
+
service_serializer.is_valid(raise_exception=True)
|
342
|
+
service = service_serializer.save()
|
343
|
+
self.instance.services.add(service)
|
344
|
+
|
345
|
+
# Remove services that are not in the update
|
346
|
+
if remove_missing:
|
347
|
+
service_ids = {s.get("id") for s in services_data if "id" in s}
|
348
|
+
for service in existing_services.values():
|
349
|
+
if service.id not in service_ids:
|
350
|
+
self.instance.services.remove(service)
|
351
|
+
service.delete()
|
352
|
+
|
353
|
+
def update_carriers(self, carriers: list) -> None:
|
354
|
+
"""Update carrier associations."""
|
355
|
+
if carriers is not None:
|
356
|
+
_ids = set(
|
357
|
+
[*carriers, *(self.instance.carriers.values_list("id", flat=True))]
|
358
|
+
)
|
359
|
+
_carriers = gateway.Carriers.list(
|
360
|
+
context=self.context,
|
361
|
+
carrier_name=self.instance.carrier_name,
|
362
|
+
).filter(id__in=list(_ids))
|
363
|
+
|
364
|
+
for carrier in _carriers:
|
365
|
+
carrier.settings.rate_sheet = (
|
366
|
+
self.instance if carrier.id in carriers else None
|
367
|
+
)
|
368
|
+
carrier.settings.save(update_fields=["rate_sheet"])
|
369
|
+
|
370
|
+
def update(self, instance, validated_data, **kwargs):
|
371
|
+
"""Handle updates of rate sheet data including services and carriers."""
|
372
|
+
services_data = validated_data.pop("services", None)
|
373
|
+
carriers = (
|
374
|
+
validated_data.pop("carriers", None)
|
375
|
+
if "carriers" in validated_data
|
376
|
+
else None
|
377
|
+
)
|
378
|
+
remove_missing_services = validated_data.pop("remove_missing_services", False)
|
379
|
+
|
380
|
+
instance = super().update(instance, validated_data)
|
381
|
+
|
382
|
+
if services_data is not None:
|
383
|
+
self.update_services(services_data, remove_missing_services)
|
384
|
+
|
385
|
+
if carriers is not None:
|
386
|
+
self.update_carriers(carriers)
|
387
|
+
|
388
|
+
return instance
|
@@ -0,0 +1,142 @@
|
|
1
|
+
{% load i18n static %}
|
2
|
+
<!DOCTYPE html>
|
3
|
+
<html>
|
4
|
+
|
5
|
+
<head>
|
6
|
+
<meta charset="utf-8">
|
7
|
+
<meta name="theme-color" content="#ffffff">
|
8
|
+
<link rel="shortcut icon" href="{% static 'branding/favicon.ico' %}">
|
9
|
+
|
10
|
+
<title>{{ APP_NAME }} Graph</title>
|
11
|
+
|
12
|
+
<style>
|
13
|
+
html, body {
|
14
|
+
height: 100%;
|
15
|
+
margin: 0;
|
16
|
+
overflow: hidden;
|
17
|
+
width: 100%;
|
18
|
+
}
|
19
|
+
|
20
|
+
#graphiql {
|
21
|
+
height: 100vh;
|
22
|
+
display: flex;
|
23
|
+
}
|
24
|
+
|
25
|
+
.docExplorerHide {
|
26
|
+
display: none;
|
27
|
+
}
|
28
|
+
|
29
|
+
.doc-explorer-contents {
|
30
|
+
overflow-y: hidden !important;
|
31
|
+
}
|
32
|
+
|
33
|
+
.docExplorerWrap {
|
34
|
+
width: unset !important;
|
35
|
+
min-width: unset !important;
|
36
|
+
}
|
37
|
+
|
38
|
+
.graphiql-explorer-actions select {
|
39
|
+
margin-left: 4px;
|
40
|
+
}
|
41
|
+
|
42
|
+
.admin-link {
|
43
|
+
position: fixed;
|
44
|
+
bottom: 44px;
|
45
|
+
right: 30px;
|
46
|
+
width: 40px;
|
47
|
+
height: 40px;
|
48
|
+
background-color: rgba(50, 50, 159, 70%);
|
49
|
+
border-radius: 50%;
|
50
|
+
z-index: 10;
|
51
|
+
box-shadow: rgb(0 0 0 / 30%) 0px 0px 20px;
|
52
|
+
}
|
53
|
+
|
54
|
+
@media screen and (max-width: 50rem) {
|
55
|
+
.admin-link {
|
56
|
+
bottom: 112px;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
</style>
|
60
|
+
|
61
|
+
<script
|
62
|
+
crossorigin
|
63
|
+
src="https://unpkg.com/react@17.0.2/umd/react.development.js"
|
64
|
+
integrity="sha384-xQwCoNcK/7P3Lpv50IZSEbJdpqbToWEODAUyI/RECaRXmOE2apWt7htari8kvKa/"
|
65
|
+
></script>
|
66
|
+
<script
|
67
|
+
crossorigin
|
68
|
+
src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"
|
69
|
+
integrity="sha384-E9IgxDsnjKgh0777N3lXen7NwXeTsOpLLJhI01SW7idG046SRqJpsW2rJwsOYk0L"
|
70
|
+
></script>
|
71
|
+
<script
|
72
|
+
crossorigin
|
73
|
+
src="https://unpkg.com/js-cookie@3.0.1/dist/js.cookie.min.js"
|
74
|
+
integrity="sha384-ETDm/j6COkRSUfVFsGNM5WYE4WjyRgfDhy4Pf4Fsc8eNw/eYEMqYZWuxTzMX6FBa"
|
75
|
+
></script>
|
76
|
+
|
77
|
+
<link
|
78
|
+
crossorigin
|
79
|
+
rel="stylesheet"
|
80
|
+
href="https://unpkg.com/graphiql@2.0.3/graphiql.min.css"
|
81
|
+
integrity="sha384-AKx2Bh1kuZ1tUTwbmHASXvBtHBX4WWVwdTQjArDlqPCL2uuBTyJkajuxdczWhzTN"
|
82
|
+
/>
|
83
|
+
</head>
|
84
|
+
|
85
|
+
<body>
|
86
|
+
<div id="graphiql" class="graphiql-container">Loading...</div>
|
87
|
+
|
88
|
+
<script
|
89
|
+
crossorigin
|
90
|
+
src="https://unpkg.com/graphiql@2.0.3/graphiql.min.js"
|
91
|
+
integrity="sha384-WI6ayyBMb7Ln13us9JlWopMH4Kz33Pt9bYbkO5oY/xryP/pbGmz5Q08oS2dcrLmc"
|
92
|
+
></script>
|
93
|
+
<script
|
94
|
+
crossorigin
|
95
|
+
src="https://unpkg.com/@graphiql/plugin-explorer@0.1.0/dist/graphiql-plugin-explorer.umd.js"
|
96
|
+
integrity="sha384-XyAmNqmxnLsRHkMhQYTqC0ub7uXpNbwdkhjn70ZF3J3XSb7bouSdRVfzDojimcMd"
|
97
|
+
></script>
|
98
|
+
<script>
|
99
|
+
const EXAMPLE_QUERY = `# Welcome to GraphiQL`;
|
100
|
+
|
101
|
+
const fetchURL = window.location.href;
|
102
|
+
|
103
|
+
function httpUrlToWebSockeUrl(url) {
|
104
|
+
return url.replace(/(http)(s)?\:\/\//, "ws$2://");
|
105
|
+
}
|
106
|
+
|
107
|
+
const headers = {};
|
108
|
+
const csrfToken = Cookies.get("csrftoken");
|
109
|
+
|
110
|
+
if (csrfToken) {
|
111
|
+
headers["x-csrftoken"] = csrfToken;
|
112
|
+
}
|
113
|
+
|
114
|
+
const fetcher = GraphiQL.createFetcher({
|
115
|
+
url: fetchURL,
|
116
|
+
headers: headers,
|
117
|
+
});
|
118
|
+
|
119
|
+
function GraphiQLWithExplorer() {
|
120
|
+
const [query, setQuery] = React.useState(EXAMPLE_QUERY);
|
121
|
+
const explorerPlugin = GraphiQLPluginExplorer.useExplorerPlugin({
|
122
|
+
query: query,
|
123
|
+
onEdit: setQuery,
|
124
|
+
});
|
125
|
+
return React.createElement(GraphiQL, {
|
126
|
+
fetcher: fetcher,
|
127
|
+
defaultEditorToolsVisibility: true,
|
128
|
+
plugins: [explorerPlugin],
|
129
|
+
query: query,
|
130
|
+
onEditQuery: setQuery,
|
131
|
+
graphiqlHeaderEditorEnabled: true,
|
132
|
+
});
|
133
|
+
}
|
134
|
+
|
135
|
+
ReactDOM.render(
|
136
|
+
React.createElement(GraphiQLWithExplorer),
|
137
|
+
document.getElementById("graphiql")
|
138
|
+
);
|
139
|
+
</script>
|
140
|
+
</body>
|
141
|
+
|
142
|
+
</html>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
{% load i18n %}{% autoescape off %}
|
2
|
+
{% blocktrans %}You're receiving this email because you requested an email change for your user account at {{ APP_NAME }}.{% endblocktrans %}
|
3
|
+
<br /><br />
|
4
|
+
{% trans "Please open the following link to confirm:" %}<br />
|
5
|
+
{% block change_link %}
|
6
|
+
{{ link }}?token={{ token }}
|
7
|
+
{% endblock %}
|
8
|
+
<br /><br />
|
9
|
+
{% trans "Thanks for using our platform!" %}
|
10
|
+
<br />
|
11
|
+
{% blocktrans %}The {{ APP_NAME }} team{% endblocktrans %}
|
12
|
+
|
13
|
+
{% endautoescape %}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
{% load i18n %}{% autoescape off %}
|
2
|
+
{% blocktrans %}You're receiving this email because you requested an email change for your user account at {{ APP_NAME }}.{% endblocktrans %}
|
3
|
+
|
4
|
+
{% trans "Please open the following link to confirm:" %}
|
5
|
+
{% block change_link %}
|
6
|
+
{{ link }}?token={{ token }}
|
7
|
+
{% endblock %}
|
8
|
+
|
9
|
+
{% trans "Thanks for using our platform!" %}
|
10
|
+
|
11
|
+
{% blocktrans %}The {{ APP_NAME }} team{% endblocktrans %}
|
12
|
+
|
13
|
+
{% endautoescape %}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
{% load i18n %}{% autoescape off %}
|
2
|
+
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ app_name }}.{% endblocktrans %}
|
3
|
+
|
4
|
+
{% trans "Please go to the following page and choose a new password:" %}
|
5
|
+
{% block reset_link %}
|
6
|
+
{{ redirect_url }}?uidb64={{ uid }}&token={{ token }}
|
7
|
+
{% endblock %}
|
8
|
+
{% trans 'Your username, in case you’ve forgotten:' %} {{ user.get_username }}
|
9
|
+
|
10
|
+
{% trans "Thanks for using our platform!" %}
|
11
|
+
|
12
|
+
{% blocktrans %}The {{ app_name }} team{% endblocktrans %}
|
13
|
+
|
14
|
+
{% endautoescape %}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
logging.disable(logging.CRITICAL)
|
4
|
+
|
5
|
+
from karrio.server.graph.tests.test_templates import *
|
6
|
+
from karrio.server.graph.tests.test_carrier_connections import *
|
7
|
+
from karrio.server.graph.tests.test_user_info import *
|
8
|
+
from karrio.server.graph.tests.test_rate_sheets import *
|
9
|
+
from karrio.server.graph.tests.test_metafield import *
|