karrio-server-core 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.
Potentially problematic release.
This version of karrio-server-core might be problematic. Click here for more details.
- karrio/server/conf.py +54 -0
- karrio/server/core/__init__.py +3 -0
- karrio/server/core/admin.py +1 -0
- karrio/server/core/apps.py +10 -0
- karrio/server/core/authentication.py +313 -0
- karrio/server/core/context_processors.py +12 -0
- karrio/server/core/datatypes.py +369 -0
- karrio/server/core/dataunits.py +156 -0
- karrio/server/core/exceptions.py +200 -0
- karrio/server/core/fields.py +12 -0
- karrio/server/core/filters.py +823 -0
- karrio/server/core/gateway.py +720 -0
- karrio/server/core/management/commands/cli.py +19 -0
- karrio/server/core/management/commands/create_oauth_client.py +41 -0
- karrio/server/core/middleware.py +95 -0
- karrio/server/core/migrations/0001_initial.py +28 -0
- karrio/server/core/migrations/0002_apilogindex.py +69 -0
- karrio/server/core/migrations/0003_apilogindex_test_mode.py +62 -0
- karrio/server/core/migrations/0004_metafield.py +74 -0
- karrio/server/core/migrations/0005_alter_metafield_type_alter_metafield_value.py +23 -0
- karrio/server/core/migrations/__init__.py +0 -0
- karrio/server/core/models/__init__.py +48 -0
- karrio/server/core/models/base.py +70 -0
- karrio/server/core/models/entity.py +22 -0
- karrio/server/core/models/metafield.py +144 -0
- karrio/server/core/models/third_party.py +21 -0
- karrio/server/core/oauth_validators.py +171 -0
- karrio/server/core/permissions.py +37 -0
- karrio/server/core/renderers.py +11 -0
- karrio/server/core/router.py +3 -0
- karrio/server/core/serializers.py +1898 -0
- karrio/server/core/signals.py +57 -0
- karrio/server/core/tests.py +98 -0
- karrio/server/core/urls.py +12 -0
- karrio/server/core/utils.py +479 -0
- karrio/server/core/validators.py +416 -0
- karrio/server/core/views/__init__.py +2 -0
- karrio/server/core/views/api.py +133 -0
- karrio/server/core/views/metadata.py +44 -0
- karrio/server/core/views/oauth.py +74 -0
- karrio/server/core/views/references.py +82 -0
- karrio/server/core/views/schema.py +310 -0
- karrio/server/filters/__init__.py +2 -0
- karrio/server/filters/abstract.py +26 -0
- karrio/server/iam/__init__.py +0 -0
- karrio/server/iam/admin.py +3 -0
- karrio/server/iam/apps.py +21 -0
- karrio/server/iam/migrations/0001_initial.py +33 -0
- karrio/server/iam/migrations/__init__.py +0 -0
- karrio/server/iam/models.py +48 -0
- karrio/server/iam/permissions.py +134 -0
- karrio/server/iam/serializers.py +39 -0
- karrio/server/iam/signals.py +20 -0
- karrio/server/iam/tests.py +3 -0
- karrio/server/iam/views.py +3 -0
- karrio/server/openapi.py +75 -0
- karrio/server/providers/__init__.py +1 -0
- karrio/server/providers/admin.py +364 -0
- karrio/server/providers/apps.py +10 -0
- karrio/server/providers/extension/__init__.py +1 -0
- karrio/server/providers/extension/models/__init__.py +1 -0
- karrio/server/providers/extension/models/allied_express.py +22 -0
- karrio/server/providers/extension/models/allied_express_local.py +22 -0
- karrio/server/providers/extension/models/amazon_shipping.py +27 -0
- karrio/server/providers/extension/models/aramex.py +25 -0
- karrio/server/providers/extension/models/asendia_us.py +21 -0
- karrio/server/providers/extension/models/australiapost.py +20 -0
- karrio/server/providers/extension/models/boxknight.py +19 -0
- karrio/server/providers/extension/models/bpost.py +21 -0
- karrio/server/providers/extension/models/canadapost.py +21 -0
- karrio/server/providers/extension/models/canpar.py +19 -0
- karrio/server/providers/extension/models/chronopost.py +22 -0
- karrio/server/providers/extension/models/colissimo.py +22 -0
- karrio/server/providers/extension/models/dhl_express.py +23 -0
- karrio/server/providers/extension/models/dhl_parcel_de.py +25 -0
- karrio/server/providers/extension/models/dhl_poland.py +22 -0
- karrio/server/providers/extension/models/dhl_universal.py +19 -0
- karrio/server/providers/extension/models/dicom.py +20 -0
- karrio/server/providers/extension/models/dpd.py +37 -0
- karrio/server/providers/extension/models/dpdhl.py +26 -0
- karrio/server/providers/extension/models/easypost.py +20 -0
- karrio/server/providers/extension/models/eshipper.py +21 -0
- karrio/server/providers/extension/models/fedex.py +25 -0
- karrio/server/providers/extension/models/fedex_ws.py +24 -0
- karrio/server/providers/extension/models/freightcom.py +21 -0
- karrio/server/providers/extension/models/generic.py +35 -0
- karrio/server/providers/extension/models/geodis.py +22 -0
- karrio/server/providers/extension/models/hay_post.py +22 -0
- karrio/server/providers/extension/models/laposte.py +19 -0
- karrio/server/providers/extension/models/locate2u.py +22 -0
- karrio/server/providers/extension/models/nationex.py +22 -0
- karrio/server/providers/extension/models/purolator.py +21 -0
- karrio/server/providers/extension/models/roadie.py +18 -0
- karrio/server/providers/extension/models/royalmail.py +19 -0
- karrio/server/providers/extension/models/sendle.py +22 -0
- karrio/server/providers/extension/models/tge.py +63 -0
- karrio/server/providers/extension/models/tnt.py +23 -0
- karrio/server/providers/extension/models/ups.py +23 -0
- karrio/server/providers/extension/models/usps.py +23 -0
- karrio/server/providers/extension/models/usps_international.py +23 -0
- karrio/server/providers/extension/models/usps_wt.py +24 -0
- karrio/server/providers/extension/models/usps_wt_international.py +24 -0
- karrio/server/providers/extension/models/zoom2u.py +23 -0
- karrio/server/providers/migrations/0001_initial.py +140 -0
- karrio/server/providers/migrations/0002_carrier_active.py +18 -0
- karrio/server/providers/migrations/0003_auto_20201230_0820.py +24 -0
- karrio/server/providers/migrations/0004_auto_20210212_0554.py +178 -0
- karrio/server/providers/migrations/0005_auto_20210212_0555.py +18 -0
- karrio/server/providers/migrations/0006_australiapostsettings.py +29 -0
- karrio/server/providers/migrations/0007_auto_20210213_0206.py +21 -0
- karrio/server/providers/migrations/0008_auto_20210214_0409.py +30 -0
- karrio/server/providers/migrations/0009_auto_20210308_0302.py +18 -0
- karrio/server/providers/migrations/0010_auto_20210409_0852.py +32 -0
- karrio/server/providers/migrations/0011_auto_20210409_0853.py +21 -0
- karrio/server/providers/migrations/0012_alter_carrier_options.py +17 -0
- karrio/server/providers/migrations/0013_tntsettings.py +30 -0
- karrio/server/providers/migrations/0014_auto_20210612_1608.py +46 -0
- karrio/server/providers/migrations/0015_auto_20210615_1601.py +28 -0
- karrio/server/providers/migrations/0016_alter_purolatorsettings_user_token.py +18 -0
- karrio/server/providers/migrations/0017_auto_20210805_0359.py +1293 -0
- karrio/server/providers/migrations/0018_alter_fedexsettings_user_key.py +18 -0
- karrio/server/providers/migrations/0019_dhlpolandsettings_servicelevel.py +65 -0
- karrio/server/providers/migrations/0020_genericsettings_labeltemplate.py +52 -0
- karrio/server/providers/migrations/0021_auto_20211231_2353.py +40 -0
- karrio/server/providers/migrations/0022_carrier_metadata.py +18 -0
- karrio/server/providers/migrations/0023_auto_20220124_1916.py +27 -0
- karrio/server/providers/migrations/0024_alter_genericsettings_custom_carrier_name.py +19 -0
- karrio/server/providers/migrations/0025_alter_servicelevel_service_code.py +19 -0
- karrio/server/providers/migrations/0026_auto_20220208_0132.py +59 -0
- karrio/server/providers/migrations/0027_auto_20220304_1340.py +29 -0
- karrio/server/providers/migrations/0028_auto_20220323_1500.py +33 -0
- karrio/server/providers/migrations/0029_easypostsettings.py +27 -0
- karrio/server/providers/migrations/0030_amazonmwssettings.py +29 -0
- karrio/server/providers/migrations/0031_delete_amazonmwssettings.py +18 -0
- karrio/server/providers/migrations/0032_alter_carrier_test.py +18 -0
- karrio/server/providers/migrations/0033_auto_20220708_1350.py +22 -0
- karrio/server/providers/migrations/0034_amazonmwssettings_dpdhlsettings.py +47 -0
- karrio/server/providers/migrations/0035_alter_carrier_capabilities.py +43 -0
- karrio/server/providers/migrations/0036_upsfreightsettings.py +31 -0
- karrio/server/providers/migrations/0037_chronopostsettings.py +29 -0
- karrio/server/providers/migrations/0038_alter_genericsettings_label_template.py +19 -0
- karrio/server/providers/migrations/0039_auto_20220906_0612.py +23 -0
- karrio/server/providers/migrations/0040_dpdhlsettings_services.py +18 -0
- karrio/server/providers/migrations/0041_auto_20221105_0705.py +38 -0
- karrio/server/providers/migrations/0042_auto_20221215_1642.py +23 -0
- karrio/server/providers/migrations/0043_alter_genericsettings_account_number_and_more.py +39 -0
- karrio/server/providers/migrations/0044_carrier_carrier_capabilities.py +64 -0
- karrio/server/providers/migrations/0045_alter_carrier_active_alter_carrier_carrier_id.py +31 -0
- karrio/server/providers/migrations/0046_remove_dpdhlsettings_signature_and_more.py +41 -0
- karrio/server/providers/migrations/0047_dpdsettings.py +286 -0
- karrio/server/providers/migrations/0048_servicelevel_min_weight_servicelevel_transit_days_and_more.py +64 -0
- karrio/server/providers/migrations/0049_boxknightsettings_geodissettings_lapostesettings_and_more.py +156 -0
- karrio/server/providers/migrations/0050_carrier_is_system_alter_carrier_metadata_and_more.py +106 -0
- karrio/server/providers/migrations/0051_rename_username_upssettings_client_id_and_more.py +31 -0
- karrio/server/providers/migrations/0052_alter_upssettings_account_number_and_more.py +20 -0
- karrio/server/providers/migrations/0053_locate2usettings.py +281 -0
- karrio/server/providers/migrations/0054_zoom2usettings.py +280 -0
- karrio/server/providers/migrations/0055_rename_amazonmwssettings_amazonshippingsettings_and_more.py +44 -0
- karrio/server/providers/migrations/0056_asendiaussettings_geodissettings_code_client_and_more.py +75 -0
- karrio/server/providers/migrations/0057_alter_servicelevel_weight_unit_belgianpostsettings.py +51 -0
- karrio/server/providers/migrations/0058_alliedexpresssettings.py +38 -0
- karrio/server/providers/migrations/0059_ratesheet.py +81 -0
- karrio/server/providers/migrations/0060_belgianpostsettings_rate_sheet_and_more.py +73 -0
- karrio/server/providers/migrations/0061_alliedexpresssettings_service_type.py +17 -0
- karrio/server/providers/migrations/0062_sendlesettings_account_country_code.py +257 -0
- karrio/server/providers/migrations/0063_servicelevel_metadata.py +25 -0
- karrio/server/providers/migrations/0064_alliedexpresslocalsettings.py +43 -0
- karrio/server/providers/migrations/0065_servicelevel_carrier_service_code_and_more.py +66 -0
- karrio/server/providers/migrations/0066_rename_fedexsettings_fedexwssettings_and_more.py +28 -0
- karrio/server/providers/migrations/0067_fedexsettings.py +283 -0
- karrio/server/providers/migrations/0068_fedexsettings_track_api_key_and_more.py +38 -0
- karrio/server/providers/migrations/0069_alter_canadapostsettings_contract_id_and_more.py +23 -0
- karrio/server/providers/migrations/0070_tgesettings_alter_carrier_capabilities.py +65 -0
- karrio/server/providers/migrations/0071_alter_tgesettings_my_toll_token.py +18 -0
- karrio/server/providers/migrations/0072_rename_eshippersettings_eshipperxmlsettings_and_more.py +28 -0
- karrio/server/providers/migrations/0073_delete_eshipperxmlsettings.py +41 -0
- karrio/server/providers/migrations/0074_eshippersettings.py +38 -0
- karrio/server/providers/migrations/0075_haypostsettings.py +40 -0
- karrio/server/providers/migrations/0076_rename_customer_registration_id_uspsinternationalsettings_account_number_and_more.py +125 -0
- karrio/server/providers/migrations/0077_uspswtinternationalsettings_uspswtsettings_and_more.py +165 -0
- karrio/server/providers/migrations/0078_auto_20240813_1552.py +120 -0
- karrio/server/providers/migrations/0079_alter_carrier_options_alter_ratesheet_created_by.py +31 -0
- karrio/server/providers/migrations/0080_alter_aramexsettings_account_country_code_and_more.py +3025 -0
- karrio/server/providers/migrations/0081_remove_alliedexpresssettings_carrier_ptr_and_more.py +338 -0
- karrio/server/providers/migrations/__init__.py +0 -0
- karrio/server/providers/models/__init__.py +17 -0
- karrio/server/providers/models/carrier.py +309 -0
- karrio/server/providers/models/config.py +30 -0
- karrio/server/providers/models/service.py +62 -0
- karrio/server/providers/models/sheet.py +60 -0
- karrio/server/providers/models/template.py +39 -0
- karrio/server/providers/models/utils.py +58 -0
- karrio/server/providers/router.py +3 -0
- karrio/server/providers/serializers/__init__.py +3 -0
- karrio/server/providers/serializers/base.py +277 -0
- karrio/server/providers/signals.py +27 -0
- karrio/server/providers/tests.py +3 -0
- karrio/server/providers/urls.py +11 -0
- karrio/server/providers/views/__init__.py +0 -0
- karrio/server/providers/views/carriers.py +269 -0
- karrio/server/providers/views/connections.py +176 -0
- karrio/server/samples.py +352 -0
- karrio/server/serializers/__init__.py +2 -0
- karrio/server/serializers/abstract.py +506 -0
- karrio/server/tracing/__init__.py +0 -0
- karrio/server/tracing/admin.py +63 -0
- karrio/server/tracing/apps.py +8 -0
- karrio/server/tracing/migrations/0001_initial.py +41 -0
- karrio/server/tracing/migrations/0002_auto_20220710_1307.py +22 -0
- karrio/server/tracing/migrations/0003_auto_20221105_0317.py +43 -0
- karrio/server/tracing/migrations/0004_tracingrecord_carrier_account_idx.py +24 -0
- karrio/server/tracing/migrations/0005_optimise_tracingrecord_request_log_idx.py +25 -0
- karrio/server/tracing/migrations/0006_alter_tracingrecord_options_and_more.py +49 -0
- karrio/server/tracing/migrations/__init__.py +0 -0
- karrio/server/tracing/models.py +80 -0
- karrio/server/tracing/tests.py +3 -0
- karrio/server/tracing/utils.py +112 -0
- karrio/server/user/__init__.py +0 -0
- karrio/server/user/admin.py +96 -0
- karrio/server/user/apps.py +7 -0
- karrio/server/user/forms.py +35 -0
- karrio/server/user/migrations/0001_initial.py +41 -0
- karrio/server/user/migrations/0002_token.py +29 -0
- karrio/server/user/migrations/0003_token_test_mode.py +20 -0
- karrio/server/user/migrations/0004_group.py +26 -0
- karrio/server/user/migrations/0005_token_label.py +21 -0
- karrio/server/user/migrations/0006_workspaceconfig.py +63 -0
- karrio/server/user/migrations/__init__.py +0 -0
- karrio/server/user/models.py +203 -0
- karrio/server/user/serializers.py +46 -0
- karrio/server/user/templates/registration/login.html +108 -0
- karrio/server/user/templates/registration/registration_confirm_email.html +10 -0
- karrio/server/user/templates/registration/registration_confirm_email.txt +3 -0
- karrio/server/user/tests.py +3 -0
- karrio/server/user/urls.py +10 -0
- karrio/server/user/utils.py +60 -0
- karrio/server/user/views.py +9 -0
- karrio_server_core-2025.5rc1.dist-info/METADATA +32 -0
- karrio_server_core-2025.5rc1.dist-info/RECORD +241 -0
- karrio_server_core-2025.5rc1.dist-info/WHEEL +5 -0
- karrio_server_core-2025.5rc1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import typing
|
|
3
|
+
import logging
|
|
4
|
+
import requests # type: ignore
|
|
5
|
+
import phonenumbers
|
|
6
|
+
from constance import config
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
import karrio.lib as lib
|
|
10
|
+
import karrio.core.units as units
|
|
11
|
+
import karrio.server.serializers as serializers
|
|
12
|
+
import karrio.server.core.datatypes as datatypes
|
|
13
|
+
|
|
14
|
+
# Try to import the references module, which may not be available in all environments
|
|
15
|
+
try:
|
|
16
|
+
import karrio.references as references
|
|
17
|
+
except ImportError:
|
|
18
|
+
references = None
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
DIMENSIONS = ["width", "height", "length"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def dimensions_required_together(value):
|
|
25
|
+
any_dimension_specified = any(value.get(dim) is not None for dim in DIMENSIONS)
|
|
26
|
+
has_any_dimension_undefined = any(value.get(dim) is None for dim in DIMENSIONS)
|
|
27
|
+
dimension_unit_is_undefined = value.get("dimension_unit") is None
|
|
28
|
+
|
|
29
|
+
if any_dimension_specified and has_any_dimension_undefined:
|
|
30
|
+
raise serializers.ValidationError(
|
|
31
|
+
{
|
|
32
|
+
"dimensions": "When one dimension is specified, all must be specified with a dimension_unit"
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
any_dimension_specified
|
|
38
|
+
and not has_any_dimension_undefined
|
|
39
|
+
and dimension_unit_is_undefined
|
|
40
|
+
):
|
|
41
|
+
raise serializers.ValidationError(
|
|
42
|
+
{
|
|
43
|
+
"dimension_unit": "dimension_unit is required when dimensions are specified"
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def valid_time_format(prop: str):
|
|
49
|
+
def validate(value):
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
datetime.strptime(value, "%H:%M")
|
|
53
|
+
except Exception:
|
|
54
|
+
raise serializers.ValidationError(
|
|
55
|
+
"The time format must match HH:HM",
|
|
56
|
+
code="invalid",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return validate
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def valid_date_format(prop: str):
|
|
63
|
+
def validate(value):
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
datetime.strptime(value, "%Y-%m-%d")
|
|
67
|
+
except Exception:
|
|
68
|
+
raise serializers.ValidationError(
|
|
69
|
+
"The date format must match YYYY-MM-DD",
|
|
70
|
+
code="invalid",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return validate
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def valid_datetime_format(prop: str):
|
|
77
|
+
def validate(value):
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
datetime.strptime(value, "%Y-%m-%d %H:%M")
|
|
81
|
+
except Exception:
|
|
82
|
+
raise serializers.ValidationError(
|
|
83
|
+
"The datetime format must match YYYY-MM-DD HH:HM",
|
|
84
|
+
code="invalid",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return validate
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def valid_base64(prop: str, max_size: int = 5242880):
|
|
91
|
+
def validate(value: str):
|
|
92
|
+
error = None
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
buffer = lib.to_buffer(value, validate=True)
|
|
96
|
+
|
|
97
|
+
if buffer.getbuffer().nbytes > max_size:
|
|
98
|
+
error = f"Error: file size exceeds {max_size} bytes."
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.exception(e)
|
|
102
|
+
error = "Invalid base64 file content"
|
|
103
|
+
raise serializers.ValidationError(
|
|
104
|
+
error,
|
|
105
|
+
code="invalid",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if error is not None:
|
|
109
|
+
raise serializers.ValidationError(error, code="invalid")
|
|
110
|
+
|
|
111
|
+
return validate
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class OptionDefaultSerializer(serializers.Serializer):
|
|
115
|
+
def __init__(self, instance=None, **kwargs):
|
|
116
|
+
data = kwargs.get("data", {})
|
|
117
|
+
if data:
|
|
118
|
+
# Get existing options from data and instance
|
|
119
|
+
options = {
|
|
120
|
+
**(
|
|
121
|
+
getattr(instance, "options", None) or {}
|
|
122
|
+
), # Start with instance options
|
|
123
|
+
**(data.get("options") or {}), # Override with new options
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Get shipping_date from options or default to next business day
|
|
127
|
+
shipping_date = options.get("shipping_date")
|
|
128
|
+
shipment_date = options.get("shipment_date")
|
|
129
|
+
|
|
130
|
+
if not shipping_date:
|
|
131
|
+
shipping_date = lib.fdatetime(
|
|
132
|
+
lib.to_next_business_datetime(
|
|
133
|
+
lib.to_date(shipment_date) or datetime.now()
|
|
134
|
+
),
|
|
135
|
+
output_format="%Y-%m-%dT%H:%M",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if not shipment_date:
|
|
139
|
+
shipment_date = lib.fdate(
|
|
140
|
+
shipping_date, current_format="%Y-%m-%dT%H:%M"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Update only the date fields in options
|
|
144
|
+
options.update(
|
|
145
|
+
{"shipping_date": shipping_date, "shipment_date": shipment_date}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Update the data with merged options
|
|
149
|
+
kwargs["data"]["options"] = options
|
|
150
|
+
|
|
151
|
+
super().__init__(instance, **kwargs)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class PresetSerializer(serializers.Serializer):
|
|
155
|
+
def validate(self, data):
|
|
156
|
+
import karrio.server.core.dataunits as dataunits
|
|
157
|
+
|
|
158
|
+
dimensions_required_together(data)
|
|
159
|
+
|
|
160
|
+
if data is not None and "package_preset" in data:
|
|
161
|
+
preset = next(
|
|
162
|
+
(
|
|
163
|
+
presets[data["package_preset"]]
|
|
164
|
+
for _, presets in dataunits.REFERENCE_MODELS[
|
|
165
|
+
"package_presets"
|
|
166
|
+
].items()
|
|
167
|
+
if data["package_preset"] in presets
|
|
168
|
+
),
|
|
169
|
+
{},
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
data.update(
|
|
173
|
+
{
|
|
174
|
+
**data,
|
|
175
|
+
"width": data.get("width", preset.get("width")),
|
|
176
|
+
"length": data.get("length", preset.get("length")),
|
|
177
|
+
"height": data.get("height", preset.get("height")),
|
|
178
|
+
"dimension_unit": data.get(
|
|
179
|
+
"dimension_unit", preset.get("dimension_unit")
|
|
180
|
+
),
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return data
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class AugmentedAddressSerializer(serializers.Serializer):
|
|
188
|
+
def validate(self, data):
|
|
189
|
+
# Format and validate Postal Code
|
|
190
|
+
if all(data.get(key) is not None for key in ["country_code", "postal_code"]):
|
|
191
|
+
postal_code = data["postal_code"]
|
|
192
|
+
country_code = data["country_code"]
|
|
193
|
+
|
|
194
|
+
if country_code == units.Country.CA.name:
|
|
195
|
+
formatted = "".join(
|
|
196
|
+
[c for c in postal_code.split() if c not in ["-", "_"]]
|
|
197
|
+
).upper()
|
|
198
|
+
if not re.match(r"^([A-Za-z]\d[A-Za-z][-]?\d[A-Za-z]\d)", formatted):
|
|
199
|
+
raise serializers.ValidationError(
|
|
200
|
+
{"postal_code": "The Canadian postal code must match Z9Z9Z9"}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
elif country_code == units.Country.US.name:
|
|
204
|
+
formatted = "".join(postal_code.split())
|
|
205
|
+
if not re.match(r"^\d{5}(-\d{4})?$", formatted):
|
|
206
|
+
raise serializers.ValidationError(
|
|
207
|
+
{
|
|
208
|
+
"postal_code": "The American postal code must match 12345 or 12345-6789"
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
else:
|
|
213
|
+
formatted = postal_code
|
|
214
|
+
|
|
215
|
+
data.update({**data, "postal_code": formatted})
|
|
216
|
+
|
|
217
|
+
# Format and validate Phone Number
|
|
218
|
+
if all(
|
|
219
|
+
data.get(key) is not None and data.get(key) != ""
|
|
220
|
+
for key in ["country_code", "phone_number"]
|
|
221
|
+
):
|
|
222
|
+
phone_number = data["phone_number"]
|
|
223
|
+
country_code = data["country_code"]
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
formatted = phonenumbers.parse(phone_number, country_code)
|
|
227
|
+
data.update(
|
|
228
|
+
{
|
|
229
|
+
**data,
|
|
230
|
+
"phone_number": phonenumbers.format_number(
|
|
231
|
+
formatted, phonenumbers.PhoneNumberFormat.INTERNATIONAL
|
|
232
|
+
),
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.warning(e)
|
|
237
|
+
raise serializers.ValidationError(
|
|
238
|
+
{"phone_number": "Invalid phone number format"}
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return data
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class AddressValidatorAbstract:
|
|
245
|
+
"""
|
|
246
|
+
Abstract base class for address validators.
|
|
247
|
+
|
|
248
|
+
This class defines the interface that all address validators must implement.
|
|
249
|
+
Note: This class is kept here for backwards compatibility.
|
|
250
|
+
New validators should use the karrio.validators.abstract.AddressValidatorAbstract class.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
@staticmethod
|
|
254
|
+
def get_info(is_authenticated: bool = None) -> dict:
|
|
255
|
+
"""
|
|
256
|
+
Get information about the validator.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
is_authenticated: Whether authenticated information should be returned
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Dictionary with information about the validator
|
|
263
|
+
|
|
264
|
+
Raises:
|
|
265
|
+
Exception: If the method is not implemented
|
|
266
|
+
"""
|
|
267
|
+
raise Exception("get_info method is not implemented")
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
def validate(address: datatypes.Address) -> datatypes.AddressValidation:
|
|
271
|
+
"""
|
|
272
|
+
Validate an address using the service.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
address: Address object to validate
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Address validation result
|
|
279
|
+
|
|
280
|
+
Raises:
|
|
281
|
+
Exception: If the method is not implemented
|
|
282
|
+
"""
|
|
283
|
+
raise Exception("validate method is not implemented")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class Address:
|
|
287
|
+
"""
|
|
288
|
+
Address validation service provider.
|
|
289
|
+
|
|
290
|
+
This class provides methods to validate addresses using various service providers
|
|
291
|
+
which are loaded as plugins.
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
@staticmethod
|
|
295
|
+
def get_info(is_authenticated: bool = True) -> dict:
|
|
296
|
+
"""
|
|
297
|
+
Get information about the available address validation services.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
is_authenticated: Whether to include sensitive information like API keys
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Dictionary with information about the available validators
|
|
304
|
+
"""
|
|
305
|
+
# If references module is available, get validator plugins info
|
|
306
|
+
refs = references.REFERENCES
|
|
307
|
+
if len(refs.get("address_validators", {})) > 0:
|
|
308
|
+
# Return information about the first available validator
|
|
309
|
+
validator_name = next(iter(refs["address_validators"].keys()))
|
|
310
|
+
validator_class = None
|
|
311
|
+
|
|
312
|
+
# Try to get the validator class from the references
|
|
313
|
+
try:
|
|
314
|
+
# Import the validator module dynamically
|
|
315
|
+
import importlib
|
|
316
|
+
module = importlib.import_module(f"karrio.validators.{validator_name}")
|
|
317
|
+
if hasattr(module, "METADATA"):
|
|
318
|
+
validator_class = module.METADATA.Validator
|
|
319
|
+
except (ImportError, AttributeError) as e:
|
|
320
|
+
logger.warning(f"Could not import validator {validator_name}: {e}")
|
|
321
|
+
|
|
322
|
+
if validator_class is not None:
|
|
323
|
+
# Return validator info with is_enabled=True
|
|
324
|
+
validator_info = validator_class.get_info(is_authenticated)
|
|
325
|
+
return {"is_enabled": True, **validator_info}
|
|
326
|
+
|
|
327
|
+
# Check for legacy config-based validation
|
|
328
|
+
is_enabled = any(
|
|
329
|
+
[config.GOOGLE_CLOUD_API_KEY, config.CANADAPOST_ADDRESS_COMPLETE_API_KEY]
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
if is_enabled:
|
|
333
|
+
# Legacy validation is enabled, use the legacy validator
|
|
334
|
+
return {
|
|
335
|
+
"is_enabled": is_enabled,
|
|
336
|
+
**Address._get_legacy_validator().get_info(is_authenticated),
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
# No validation is available
|
|
340
|
+
return dict(is_enabled=False)
|
|
341
|
+
|
|
342
|
+
@staticmethod
|
|
343
|
+
def _get_legacy_validator() -> typing.Type[AddressValidatorAbstract]:
|
|
344
|
+
"""
|
|
345
|
+
Get a legacy validator instance based on configuration.
|
|
346
|
+
|
|
347
|
+
This method is used for backwards compatibility with the old validation system.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
An instance of a validator class
|
|
351
|
+
|
|
352
|
+
Raises:
|
|
353
|
+
Exception: If no validator is configured
|
|
354
|
+
"""
|
|
355
|
+
# For backwards compatibility, check if Google or Canada Post is configured
|
|
356
|
+
if any(config.GOOGLE_CLOUD_API_KEY or ""):
|
|
357
|
+
from karrio.validators.googlegeocoding import Validator as GoogleGeocode
|
|
358
|
+
return GoogleGeocode
|
|
359
|
+
elif any(config.CANADAPOST_ADDRESS_COMPLETE_API_KEY or ""):
|
|
360
|
+
from karrio.validators.addresscomplete import Validator as AddressComplete
|
|
361
|
+
return AddressComplete
|
|
362
|
+
|
|
363
|
+
raise Exception("No address validation service provider configured")
|
|
364
|
+
|
|
365
|
+
@staticmethod
|
|
366
|
+
def get_validator() -> typing.Type:
|
|
367
|
+
"""
|
|
368
|
+
Get a validator instance based on configuration or plugins.
|
|
369
|
+
|
|
370
|
+
This method first checks for validator plugins, then falls back to
|
|
371
|
+
legacy validators if no plugins are available.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
An instance of a validator class
|
|
375
|
+
|
|
376
|
+
Raises:
|
|
377
|
+
Exception: If no validator is configured
|
|
378
|
+
"""
|
|
379
|
+
# If references module is available, check for validator plugins
|
|
380
|
+
refs = references.REFERENCES
|
|
381
|
+
if len(refs.get("address_validators", {})) > 0:
|
|
382
|
+
# Get the first available validator
|
|
383
|
+
validator_name = next(iter(refs["address_validators"].keys()))
|
|
384
|
+
|
|
385
|
+
# Try to get the validator class from the references
|
|
386
|
+
try:
|
|
387
|
+
# Import the validator module dynamically
|
|
388
|
+
import importlib
|
|
389
|
+
module = importlib.import_module(f"karrio.validators.{validator_name}")
|
|
390
|
+
if hasattr(module, "METADATA"):
|
|
391
|
+
return module.METADATA.Validator
|
|
392
|
+
except (ImportError, AttributeError) as e:
|
|
393
|
+
logger.warning(f"Could not import validator {validator_name}: {e}")
|
|
394
|
+
|
|
395
|
+
# Fall back to legacy validator
|
|
396
|
+
return Address._get_legacy_validator()
|
|
397
|
+
|
|
398
|
+
@staticmethod
|
|
399
|
+
def validate(address: datatypes.Address) -> datatypes.AddressValidation:
|
|
400
|
+
"""
|
|
401
|
+
Validate an address using the configured validator.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
address: The address to validate
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
AddressValidation object with validation results
|
|
408
|
+
"""
|
|
409
|
+
validator = Address.get_validator()
|
|
410
|
+
result = validator.validate(address)
|
|
411
|
+
|
|
412
|
+
# Handle the case where the validator returns a dict instead of AddressValidation
|
|
413
|
+
if isinstance(result, dict):
|
|
414
|
+
return datatypes.AddressValidation(**result)
|
|
415
|
+
|
|
416
|
+
return result
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import pydoc
|
|
2
|
+
import typing
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.http import JsonResponse
|
|
5
|
+
from rest_framework import generics, views
|
|
6
|
+
from rest_framework.permissions import IsAuthenticated
|
|
7
|
+
from rest_framework.throttling import UserRateThrottle, AnonRateThrottle
|
|
8
|
+
from rest_framework_tracking import mixins
|
|
9
|
+
from rest_framework import status
|
|
10
|
+
|
|
11
|
+
from karrio.core.utils import DP
|
|
12
|
+
from karrio.server.serializers import link_org
|
|
13
|
+
from karrio.server.tracing.utils import set_tracing_context
|
|
14
|
+
from karrio.server.core.utils import failsafe
|
|
15
|
+
from karrio.server.core.authentication import (
|
|
16
|
+
TokenAuthentication,
|
|
17
|
+
JWTAuthentication,
|
|
18
|
+
TokenBasicAuthentication,
|
|
19
|
+
OAuth2Authentication,
|
|
20
|
+
)
|
|
21
|
+
from karrio.server.core.models import APILogIndex
|
|
22
|
+
|
|
23
|
+
AccessMixin: typing.Any = pydoc.locate(
|
|
24
|
+
getattr(settings, "ACCESS_METHOD", "karrio.server.core.authentication.AccessMixin")
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class LoggingMixin(mixins.LoggingMixin):
|
|
29
|
+
def handle_log(self):
|
|
30
|
+
data = None if "data" not in self.log else DP.jsonify(self.log["data"])
|
|
31
|
+
query_params = (
|
|
32
|
+
None
|
|
33
|
+
if "query_params" not in self.log
|
|
34
|
+
else DP.jsonify(self.log["query_params"])
|
|
35
|
+
)
|
|
36
|
+
response = (
|
|
37
|
+
dict(response=response)
|
|
38
|
+
if "response" not in self.log
|
|
39
|
+
else (
|
|
40
|
+
DP.jsonify(self.log["response"])
|
|
41
|
+
if isinstance(DP.to_object(self.log["response"]), dict)
|
|
42
|
+
else self.log["response"]
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
entity_id = failsafe(lambda: DP.to_dict(response)["id"])
|
|
46
|
+
test_mode = failsafe(lambda: self.request.test_mode)
|
|
47
|
+
|
|
48
|
+
if test_mode is None and '"test_mode": true' in (self.log["response"] or ""):
|
|
49
|
+
test_mode = True
|
|
50
|
+
if test_mode is None and '"test_mode": false' in (self.log["response"] or ""):
|
|
51
|
+
test_mode = False
|
|
52
|
+
|
|
53
|
+
log = APILogIndex(
|
|
54
|
+
**{
|
|
55
|
+
**self.log,
|
|
56
|
+
"data": data,
|
|
57
|
+
"response": response,
|
|
58
|
+
"entity_id": entity_id,
|
|
59
|
+
"test_mode": test_mode,
|
|
60
|
+
"query_params": query_params,
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
log.save()
|
|
65
|
+
link_org(log, self.request)
|
|
66
|
+
|
|
67
|
+
set_tracing_context(
|
|
68
|
+
request_log_id=getattr(log, "id", None),
|
|
69
|
+
object_id=failsafe(lambda: (self.log.get("response") or {}).get("id")),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class BaseView:
|
|
74
|
+
permission_classes = [IsAuthenticated]
|
|
75
|
+
throttle_classes = [UserRateThrottle, AnonRateThrottle]
|
|
76
|
+
authentication_classes = [
|
|
77
|
+
TokenAuthentication,
|
|
78
|
+
JWTAuthentication,
|
|
79
|
+
OAuth2Authentication,
|
|
80
|
+
TokenBasicAuthentication,
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class BaseAPIView(views.APIView, BaseView):
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class BaseGenericAPIView(generics.GenericAPIView, BaseView):
|
|
89
|
+
def get_queryset(self):
|
|
90
|
+
if hasattr(self, "model") and getattr(self, "swagger_fake_view", False):
|
|
91
|
+
# queryset just for schema generation metadata
|
|
92
|
+
return self.model.objects.none()
|
|
93
|
+
|
|
94
|
+
if hasattr(self, "model") and hasattr(self.model, "access_by"):
|
|
95
|
+
return self.model.access_by(self.request)
|
|
96
|
+
|
|
97
|
+
return getattr(self, "queryset", None)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class GenericAPIView(LoggingMixin, BaseGenericAPIView):
|
|
101
|
+
logging_methods = ["POST", "PUT", "PATCH", "DELETE"]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class APIView(LoggingMixin, BaseAPIView):
|
|
105
|
+
logging_methods = ["POST", "PUT", "PATCH", "DELETE"]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class LoginRequiredView(AccessMixin):
|
|
109
|
+
def dispatch(self, request, *args, **kwargs):
|
|
110
|
+
auth = super().dispatch(request, *args, **kwargs)
|
|
111
|
+
if not request.user.is_authenticated:
|
|
112
|
+
return JsonResponse(
|
|
113
|
+
dict(
|
|
114
|
+
errors=[
|
|
115
|
+
{
|
|
116
|
+
"code": "not_authenticated",
|
|
117
|
+
"message": "Authentication credentials were not provided.",
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
),
|
|
121
|
+
status=status.HTTP_401_UNAUTHORIZED,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if not request.user.is_verified():
|
|
125
|
+
return JsonResponse(
|
|
126
|
+
dict(
|
|
127
|
+
errors=[
|
|
128
|
+
{"code": "not_verified", "message": "User is not verified."}
|
|
129
|
+
]
|
|
130
|
+
),
|
|
131
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
132
|
+
)
|
|
133
|
+
return auth
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import rest_framework.request as request
|
|
2
|
+
import rest_framework.response as response
|
|
3
|
+
import rest_framework.renderers as renderers
|
|
4
|
+
import rest_framework.decorators as decorators
|
|
5
|
+
import rest_framework.permissions as permissions
|
|
6
|
+
|
|
7
|
+
import karrio.server.conf as conf
|
|
8
|
+
import karrio.server.openapi as openapi
|
|
9
|
+
import karrio.server.core.dataunits as dataunits
|
|
10
|
+
|
|
11
|
+
ENDPOINT_ID = "&&" # This endpoint id is used to make operation ids unique make sure not to duplicate
|
|
12
|
+
Metadata = openapi.OpenApiResponse(
|
|
13
|
+
openapi.OpenApiTypes.OBJECT,
|
|
14
|
+
examples=[
|
|
15
|
+
openapi.OpenApiExample(
|
|
16
|
+
name="Metadata",
|
|
17
|
+
value={
|
|
18
|
+
"VERSION": "",
|
|
19
|
+
"APP_NAME": "",
|
|
20
|
+
"APP_WEBSITE": "",
|
|
21
|
+
"HOST": "",
|
|
22
|
+
"ADMIN": "",
|
|
23
|
+
"OPENAPI": "",
|
|
24
|
+
"GRAPHQL": "",
|
|
25
|
+
**{flag: True for flag in conf.FEATURE_FLAGS},
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
],
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@openapi.extend_schema(
|
|
33
|
+
auth=[],
|
|
34
|
+
methods=["get"],
|
|
35
|
+
tags=["API"],
|
|
36
|
+
operation_id=f"{ENDPOINT_ID}ping",
|
|
37
|
+
summary="Instance Metadata",
|
|
38
|
+
responses={200: Metadata},
|
|
39
|
+
)
|
|
40
|
+
@decorators.api_view(["GET"])
|
|
41
|
+
@decorators.permission_classes([permissions.AllowAny])
|
|
42
|
+
@decorators.renderer_classes([renderers.JSONRenderer])
|
|
43
|
+
def view(request: request.Request) -> response.Response:
|
|
44
|
+
return response.Response(dataunits.contextual_metadata(request))
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from django.views.decorators.csrf import csrf_exempt
|
|
3
|
+
from django.utils.decorators import method_decorator
|
|
4
|
+
from oauth2_provider.views import TokenView as BaseTokenView
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@method_decorator(csrf_exempt, name='dispatch')
|
|
10
|
+
class CustomTokenView(BaseTokenView):
|
|
11
|
+
"""
|
|
12
|
+
Custom OAuth2 token view that handles grant type format conversion.
|
|
13
|
+
|
|
14
|
+
django-oauth-toolkit stores grant types with hyphens (e.g., 'authorization-code')
|
|
15
|
+
but OAuth2 spec uses underscores (e.g., 'authorization_code').
|
|
16
|
+
This view converts the format before processing.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def post(self, request, *args, **kwargs):
|
|
20
|
+
"""
|
|
21
|
+
Handle token requests with grant type conversion.
|
|
22
|
+
"""
|
|
23
|
+
print(f"CustomTokenView called")
|
|
24
|
+
print(f"Request method: {request.method}")
|
|
25
|
+
print(f"Request content type: {request.content_type}")
|
|
26
|
+
print(f"Request POST: {dict(request.POST)}")
|
|
27
|
+
print(f"Request body: {request.body.decode('utf-8') if request.body else 'Empty'}")
|
|
28
|
+
|
|
29
|
+
# Parse the request body if POST data is empty
|
|
30
|
+
if not request.POST and request.body:
|
|
31
|
+
from django.http import QueryDict
|
|
32
|
+
import urllib.parse
|
|
33
|
+
|
|
34
|
+
# Parse the form data from the request body
|
|
35
|
+
body_data = urllib.parse.parse_qs(request.body.decode('utf-8'))
|
|
36
|
+
# Convert to single values (parse_qs returns lists)
|
|
37
|
+
parsed_data = {k: v[0] if v else '' for k, v in body_data.items()}
|
|
38
|
+
|
|
39
|
+
# Create a QueryDict from the parsed data
|
|
40
|
+
post_data = QueryDict('', mutable=True)
|
|
41
|
+
for key, value in parsed_data.items():
|
|
42
|
+
post_data[key] = value
|
|
43
|
+
|
|
44
|
+
# Replace the request POST data
|
|
45
|
+
request.POST = post_data
|
|
46
|
+
request._post = post_data
|
|
47
|
+
|
|
48
|
+
# Convert OAuth2 spec grant types to django-oauth-toolkit format
|
|
49
|
+
grant_type_mapping = {
|
|
50
|
+
'authorization_code': 'authorization-code',
|
|
51
|
+
'client_credentials': 'client-credentials',
|
|
52
|
+
'refresh_token': 'refresh-token',
|
|
53
|
+
'password': 'password',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
original_grant_type = request.POST.get('grant_type')
|
|
57
|
+
print(f"Original grant type after parsing: {original_grant_type}")
|
|
58
|
+
|
|
59
|
+
if original_grant_type in grant_type_mapping:
|
|
60
|
+
# Create a mutable copy of the POST data
|
|
61
|
+
post_data = request.POST.copy()
|
|
62
|
+
converted_grant_type = grant_type_mapping[original_grant_type]
|
|
63
|
+
print(f"Converting grant type from '{original_grant_type}' to '{converted_grant_type}'")
|
|
64
|
+
post_data['grant_type'] = converted_grant_type
|
|
65
|
+
|
|
66
|
+
# Replace the request POST data
|
|
67
|
+
request.POST = post_data
|
|
68
|
+
request._post = post_data
|
|
69
|
+
print(f"Updated grant type: {request.POST.get('grant_type')}")
|
|
70
|
+
else:
|
|
71
|
+
print(f"No conversion needed for grant type: {original_grant_type}")
|
|
72
|
+
|
|
73
|
+
# Call the parent token view with converted grant type
|
|
74
|
+
return super().post(request, *args, **kwargs)
|