karrio-server-core 2025.5__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 (213) hide show
  1. karrio/server/conf.py +54 -0
  2. karrio/server/core/__init__.py +3 -0
  3. karrio/server/core/admin.py +1 -0
  4. karrio/server/core/apps.py +10 -0
  5. karrio/server/core/authentication.py +347 -0
  6. karrio/server/core/config.py +31 -0
  7. karrio/server/core/context_processors.py +12 -0
  8. karrio/server/core/datatypes.py +394 -0
  9. karrio/server/core/dataunits.py +187 -0
  10. karrio/server/core/exceptions.py +404 -0
  11. karrio/server/core/fields.py +12 -0
  12. karrio/server/core/filters.py +837 -0
  13. karrio/server/core/gateway.py +1011 -0
  14. karrio/server/core/logging.py +403 -0
  15. karrio/server/core/management/commands/cli.py +19 -0
  16. karrio/server/core/management/commands/create_oauth_client.py +41 -0
  17. karrio/server/core/management/commands/runserver.py +5 -0
  18. karrio/server/core/middleware.py +197 -0
  19. karrio/server/core/migrations/0001_initial.py +28 -0
  20. karrio/server/core/migrations/0002_apilogindex.py +69 -0
  21. karrio/server/core/migrations/0003_apilogindex_test_mode.py +62 -0
  22. karrio/server/core/migrations/0004_metafield.py +74 -0
  23. karrio/server/core/migrations/0005_alter_metafield_type_alter_metafield_value.py +23 -0
  24. karrio/server/core/migrations/0006_add_api_log_requested_at_index.py +22 -0
  25. karrio/server/core/migrations/__init__.py +0 -0
  26. karrio/server/core/models/__init__.py +48 -0
  27. karrio/server/core/models/base.py +103 -0
  28. karrio/server/core/models/entity.py +24 -0
  29. karrio/server/core/models/metafield.py +144 -0
  30. karrio/server/core/models/third_party.py +21 -0
  31. karrio/server/core/oauth_validators.py +170 -0
  32. karrio/server/core/permissions.py +36 -0
  33. karrio/server/core/renderers.py +11 -0
  34. karrio/server/core/router.py +3 -0
  35. karrio/server/core/serializers.py +1971 -0
  36. karrio/server/core/signals.py +55 -0
  37. karrio/server/core/telemetry.py +573 -0
  38. karrio/server/core/tests.py +99 -0
  39. karrio/server/core/tests_resource_token.py +411 -0
  40. karrio/server/core/urls.py +12 -0
  41. karrio/server/core/utils.py +1025 -0
  42. karrio/server/core/validators.py +264 -0
  43. karrio/server/core/views/__init__.py +2 -0
  44. karrio/server/core/views/api.py +133 -0
  45. karrio/server/core/views/metadata.py +44 -0
  46. karrio/server/core/views/oauth.py +75 -0
  47. karrio/server/core/views/references.py +82 -0
  48. karrio/server/core/views/schema.py +310 -0
  49. karrio/server/filters/__init__.py +2 -0
  50. karrio/server/filters/abstract.py +26 -0
  51. karrio/server/iam/__init__.py +0 -0
  52. karrio/server/iam/admin.py +3 -0
  53. karrio/server/iam/apps.py +21 -0
  54. karrio/server/iam/migrations/0001_initial.py +33 -0
  55. karrio/server/iam/migrations/__init__.py +0 -0
  56. karrio/server/iam/models.py +48 -0
  57. karrio/server/iam/permissions.py +155 -0
  58. karrio/server/iam/serializers.py +54 -0
  59. karrio/server/iam/signals.py +18 -0
  60. karrio/server/iam/tests.py +3 -0
  61. karrio/server/iam/views.py +3 -0
  62. karrio/server/openapi.py +75 -0
  63. karrio/server/providers/__init__.py +1 -0
  64. karrio/server/providers/admin.py +364 -0
  65. karrio/server/providers/apps.py +10 -0
  66. karrio/server/providers/management/commands/migrate_rate_sheets.py +101 -0
  67. karrio/server/providers/migrations/0001_initial.py +140 -0
  68. karrio/server/providers/migrations/0002_carrier_active.py +18 -0
  69. karrio/server/providers/migrations/0003_auto_20201230_0820.py +24 -0
  70. karrio/server/providers/migrations/0004_auto_20210212_0554.py +178 -0
  71. karrio/server/providers/migrations/0005_auto_20210212_0555.py +18 -0
  72. karrio/server/providers/migrations/0006_australiapostsettings.py +29 -0
  73. karrio/server/providers/migrations/0007_auto_20210213_0206.py +21 -0
  74. karrio/server/providers/migrations/0008_auto_20210214_0409.py +30 -0
  75. karrio/server/providers/migrations/0009_auto_20210308_0302.py +18 -0
  76. karrio/server/providers/migrations/0010_auto_20210409_0852.py +32 -0
  77. karrio/server/providers/migrations/0011_auto_20210409_0853.py +21 -0
  78. karrio/server/providers/migrations/0012_alter_carrier_options.py +17 -0
  79. karrio/server/providers/migrations/0013_tntsettings.py +30 -0
  80. karrio/server/providers/migrations/0014_auto_20210612_1608.py +46 -0
  81. karrio/server/providers/migrations/0015_auto_20210615_1601.py +28 -0
  82. karrio/server/providers/migrations/0016_alter_purolatorsettings_user_token.py +18 -0
  83. karrio/server/providers/migrations/0017_auto_20210805_0359.py +1293 -0
  84. karrio/server/providers/migrations/0018_alter_fedexsettings_user_key.py +18 -0
  85. karrio/server/providers/migrations/0019_dhlpolandsettings_servicelevel.py +65 -0
  86. karrio/server/providers/migrations/0020_genericsettings_labeltemplate.py +52 -0
  87. karrio/server/providers/migrations/0021_auto_20211231_2353.py +40 -0
  88. karrio/server/providers/migrations/0022_carrier_metadata.py +18 -0
  89. karrio/server/providers/migrations/0023_auto_20220124_1916.py +27 -0
  90. karrio/server/providers/migrations/0024_alter_genericsettings_custom_carrier_name.py +19 -0
  91. karrio/server/providers/migrations/0025_alter_servicelevel_service_code.py +19 -0
  92. karrio/server/providers/migrations/0026_auto_20220208_0132.py +59 -0
  93. karrio/server/providers/migrations/0027_auto_20220304_1340.py +29 -0
  94. karrio/server/providers/migrations/0028_auto_20220323_1500.py +33 -0
  95. karrio/server/providers/migrations/0029_easypostsettings.py +27 -0
  96. karrio/server/providers/migrations/0030_amazonmwssettings.py +29 -0
  97. karrio/server/providers/migrations/0031_delete_amazonmwssettings.py +18 -0
  98. karrio/server/providers/migrations/0032_alter_carrier_test.py +18 -0
  99. karrio/server/providers/migrations/0033_auto_20220708_1350.py +22 -0
  100. karrio/server/providers/migrations/0034_amazonmwssettings_dpdhlsettings.py +47 -0
  101. karrio/server/providers/migrations/0035_alter_carrier_capabilities.py +43 -0
  102. karrio/server/providers/migrations/0036_upsfreightsettings.py +31 -0
  103. karrio/server/providers/migrations/0037_chronopostsettings.py +29 -0
  104. karrio/server/providers/migrations/0038_alter_genericsettings_label_template.py +19 -0
  105. karrio/server/providers/migrations/0039_auto_20220906_0612.py +23 -0
  106. karrio/server/providers/migrations/0040_dpdhlsettings_services.py +18 -0
  107. karrio/server/providers/migrations/0041_auto_20221105_0705.py +38 -0
  108. karrio/server/providers/migrations/0042_auto_20221215_1642.py +23 -0
  109. karrio/server/providers/migrations/0043_alter_genericsettings_account_number_and_more.py +39 -0
  110. karrio/server/providers/migrations/0044_carrier_carrier_capabilities.py +64 -0
  111. karrio/server/providers/migrations/0045_alter_carrier_active_alter_carrier_carrier_id.py +31 -0
  112. karrio/server/providers/migrations/0046_remove_dpdhlsettings_signature_and_more.py +41 -0
  113. karrio/server/providers/migrations/0047_dpdsettings.py +286 -0
  114. karrio/server/providers/migrations/0048_servicelevel_min_weight_servicelevel_transit_days_and_more.py +64 -0
  115. karrio/server/providers/migrations/0049_boxknightsettings_geodissettings_lapostesettings_and_more.py +156 -0
  116. karrio/server/providers/migrations/0050_carrier_is_system_alter_carrier_metadata_and_more.py +106 -0
  117. karrio/server/providers/migrations/0051_rename_username_upssettings_client_id_and_more.py +31 -0
  118. karrio/server/providers/migrations/0052_alter_upssettings_account_number_and_more.py +20 -0
  119. karrio/server/providers/migrations/0053_locate2usettings.py +281 -0
  120. karrio/server/providers/migrations/0054_zoom2usettings.py +280 -0
  121. karrio/server/providers/migrations/0055_rename_amazonmwssettings_amazonshippingsettings_and_more.py +44 -0
  122. karrio/server/providers/migrations/0056_asendiaussettings_geodissettings_code_client_and_more.py +75 -0
  123. karrio/server/providers/migrations/0057_alter_servicelevel_weight_unit_belgianpostsettings.py +51 -0
  124. karrio/server/providers/migrations/0058_alliedexpresssettings.py +38 -0
  125. karrio/server/providers/migrations/0059_ratesheet.py +81 -0
  126. karrio/server/providers/migrations/0060_belgianpostsettings_rate_sheet_and_more.py +73 -0
  127. karrio/server/providers/migrations/0061_alliedexpresssettings_service_type.py +17 -0
  128. karrio/server/providers/migrations/0062_sendlesettings_account_country_code.py +257 -0
  129. karrio/server/providers/migrations/0063_servicelevel_metadata.py +25 -0
  130. karrio/server/providers/migrations/0064_alliedexpresslocalsettings.py +43 -0
  131. karrio/server/providers/migrations/0065_servicelevel_carrier_service_code_and_more.py +66 -0
  132. karrio/server/providers/migrations/0066_rename_fedexsettings_fedexwssettings_and_more.py +28 -0
  133. karrio/server/providers/migrations/0067_fedexsettings.py +283 -0
  134. karrio/server/providers/migrations/0068_fedexsettings_track_api_key_and_more.py +38 -0
  135. karrio/server/providers/migrations/0069_alter_canadapostsettings_contract_id_and_more.py +23 -0
  136. karrio/server/providers/migrations/0070_tgesettings_alter_carrier_capabilities.py +65 -0
  137. karrio/server/providers/migrations/0071_alter_tgesettings_my_toll_token.py +18 -0
  138. karrio/server/providers/migrations/0072_rename_eshippersettings_eshipperxmlsettings_and_more.py +28 -0
  139. karrio/server/providers/migrations/0073_delete_eshipperxmlsettings.py +41 -0
  140. karrio/server/providers/migrations/0074_eshippersettings.py +38 -0
  141. karrio/server/providers/migrations/0075_haypostsettings.py +40 -0
  142. karrio/server/providers/migrations/0076_rename_customer_registration_id_uspsinternationalsettings_account_number_and_more.py +125 -0
  143. karrio/server/providers/migrations/0077_uspswtinternationalsettings_uspswtsettings_and_more.py +165 -0
  144. karrio/server/providers/migrations/0078_auto_20240813_1552.py +120 -0
  145. karrio/server/providers/migrations/0079_alter_carrier_options_alter_ratesheet_created_by.py +31 -0
  146. karrio/server/providers/migrations/0080_alter_aramexsettings_account_country_code_and_more.py +3025 -0
  147. karrio/server/providers/migrations/0081_remove_alliedexpresssettings_carrier_ptr_and_more.py +338 -0
  148. karrio/server/providers/migrations/0082_add_zone_identifiers.py +50 -0
  149. karrio/server/providers/migrations/0083_add_optimized_rate_sheet_structure.py +33 -0
  150. karrio/server/providers/migrations/0084_alter_servicelevel_currency.py +168 -0
  151. karrio/server/providers/migrations/0085_populate_dhl_parcel_de_oauth_credentials.py +82 -0
  152. karrio/server/providers/migrations/0086_rename_dhl_parcel_de_customer_number_to_billing_number.py +71 -0
  153. karrio/server/providers/migrations/__init__.py +0 -0
  154. karrio/server/providers/models/__init__.py +16 -0
  155. karrio/server/providers/models/carrier.py +387 -0
  156. karrio/server/providers/models/config.py +30 -0
  157. karrio/server/providers/models/service.py +192 -0
  158. karrio/server/providers/models/sheet.py +287 -0
  159. karrio/server/providers/models/template.py +39 -0
  160. karrio/server/providers/models/utils.py +58 -0
  161. karrio/server/providers/router.py +3 -0
  162. karrio/server/providers/serializers/__init__.py +3 -0
  163. karrio/server/providers/serializers/base.py +538 -0
  164. karrio/server/providers/signals.py +25 -0
  165. karrio/server/providers/templates/providers/oauth_callback.html +105 -0
  166. karrio/server/providers/tests/__init__.py +5 -0
  167. karrio/server/providers/tests/test_connections.py +895 -0
  168. karrio/server/providers/urls.py +11 -0
  169. karrio/server/providers/views/__init__.py +0 -0
  170. karrio/server/providers/views/carriers.py +267 -0
  171. karrio/server/providers/views/connections.py +496 -0
  172. karrio/server/samples.py +352 -0
  173. karrio/server/serializers/__init__.py +2 -0
  174. karrio/server/serializers/abstract.py +602 -0
  175. karrio/server/tracing/__init__.py +0 -0
  176. karrio/server/tracing/admin.py +63 -0
  177. karrio/server/tracing/apps.py +8 -0
  178. karrio/server/tracing/migrations/0001_initial.py +41 -0
  179. karrio/server/tracing/migrations/0002_auto_20220710_1307.py +22 -0
  180. karrio/server/tracing/migrations/0003_auto_20221105_0317.py +43 -0
  181. karrio/server/tracing/migrations/0004_tracingrecord_carrier_account_idx.py +24 -0
  182. karrio/server/tracing/migrations/0005_optimise_tracingrecord_request_log_idx.py +25 -0
  183. karrio/server/tracing/migrations/0006_alter_tracingrecord_options_and_more.py +49 -0
  184. karrio/server/tracing/migrations/0007_tracingrecord_tracing_created_at_idx.py +19 -0
  185. karrio/server/tracing/migrations/__init__.py +0 -0
  186. karrio/server/tracing/models.py +82 -0
  187. karrio/server/tracing/tests.py +3 -0
  188. karrio/server/tracing/utils.py +109 -0
  189. karrio/server/user/__init__.py +0 -0
  190. karrio/server/user/admin.py +96 -0
  191. karrio/server/user/apps.py +7 -0
  192. karrio/server/user/forms.py +35 -0
  193. karrio/server/user/migrations/0001_initial.py +41 -0
  194. karrio/server/user/migrations/0002_token.py +29 -0
  195. karrio/server/user/migrations/0003_token_test_mode.py +20 -0
  196. karrio/server/user/migrations/0004_group.py +26 -0
  197. karrio/server/user/migrations/0005_token_label.py +21 -0
  198. karrio/server/user/migrations/0006_workspaceconfig.py +63 -0
  199. karrio/server/user/migrations/0007_user_metadata.py +25 -0
  200. karrio/server/user/migrations/__init__.py +0 -0
  201. karrio/server/user/models.py +218 -0
  202. karrio/server/user/serializers.py +47 -0
  203. karrio/server/user/templates/registration/login.html +108 -0
  204. karrio/server/user/templates/registration/registration_confirm_email.html +10 -0
  205. karrio/server/user/templates/registration/registration_confirm_email.txt +3 -0
  206. karrio/server/user/tests.py +3 -0
  207. karrio/server/user/urls.py +10 -0
  208. karrio/server/user/utils.py +60 -0
  209. karrio/server/user/views.py +9 -0
  210. karrio_server_core-2025.5.dist-info/METADATA +32 -0
  211. karrio_server_core-2025.5.dist-info/RECORD +213 -0
  212. karrio_server_core-2025.5.dist-info/WHEEL +5 -0
  213. karrio_server_core-2025.5.dist-info/top_level.txt +2 -0
@@ -0,0 +1,170 @@
1
+ import hashlib
2
+ import base64
3
+ from oauth2_provider.oauth2_validators import OAuth2Validator
4
+ from django.contrib.auth import get_user_model
5
+
6
+
7
+ class CustomOAuth2Validator(OAuth2Validator):
8
+ oidc_claim_scope = None
9
+
10
+ def get_additional_claims(self):
11
+ return {
12
+ "name": lambda request: getattr(request.user, 'full_name', '') if request.user else '',
13
+ "email": lambda request: getattr(request.user, 'email', '') if request.user else '',
14
+ }
15
+
16
+ def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs):
17
+ """
18
+ Validate grant type with proper format conversion.
19
+
20
+ django-oauth-toolkit stores grant types with hyphens (e.g., 'authorization-code')
21
+ but OAuth2 spec uses underscores (e.g., 'authorization_code').
22
+ This method handles the conversion.
23
+ """
24
+ # Convert OAuth2 spec format to django-oauth-toolkit format
25
+ grant_type_mapping = {
26
+ 'authorization_code': 'authorization-code',
27
+ 'client_credentials': 'client-credentials',
28
+ 'refresh_token': 'refresh-token',
29
+ 'password': 'password',
30
+ }
31
+
32
+ # Get the stored grant type format
33
+ stored_grant_type = grant_type_mapping.get(grant_type, grant_type)
34
+
35
+ # Check if the client supports this grant type
36
+ if client and hasattr(client, 'authorization_grant_type'):
37
+ is_valid = client.authorization_grant_type == stored_grant_type
38
+ if is_valid:
39
+ return True
40
+
41
+ # Fall back to parent implementation
42
+ return super().validate_grant_type(client_id, grant_type, client, request, *args, **kwargs)
43
+
44
+ def validate_code(self, client_id, code, client, request, *args, **kwargs):
45
+ """
46
+ Override validate_code to ensure proper grant type handling for authorization code flow.
47
+ """
48
+ # First validate the code using parent implementation
49
+ is_valid = super().validate_code(client_id, code, client, request, *args, **kwargs)
50
+
51
+ if is_valid and client:
52
+ # Ensure the client supports authorization code flow
53
+ # Convert the request grant type to stored format for comparison
54
+ if request.grant_type == 'authorization_code':
55
+ return client.authorization_grant_type == 'authorization-code'
56
+
57
+ return is_valid
58
+
59
+ def validate_client_id(self, client_id, request, *args, **kwargs):
60
+ """
61
+ Validate the client_id and set the user context for different flows.
62
+ """
63
+ is_valid = super().validate_client_id(client_id, request, *args, **kwargs)
64
+
65
+ if is_valid:
66
+ try:
67
+ from oauth2_provider.models import Application
68
+ application = Application.objects.get(client_id=client_id)
69
+
70
+ # Set application on request for later use
71
+ request.oauth_application = application
72
+
73
+ # For client credentials flow, set the user from the OAuth application owner
74
+ if request.grant_type == 'client_credentials' and application.user:
75
+ request.user = application.user
76
+
77
+ except Application.DoesNotExist:
78
+ pass
79
+
80
+ return is_valid
81
+
82
+ def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs):
83
+ """
84
+ Validate redirect URI for Authorization Code Flow with enhanced security.
85
+ """
86
+ is_valid = super().validate_redirect_uri(client_id, redirect_uri, request, *args, **kwargs)
87
+
88
+ if is_valid and redirect_uri:
89
+ # Additional security: ensure HTTPS in production
90
+ from django.conf import settings
91
+ if not settings.DEBUG and not redirect_uri.startswith('https://'):
92
+ return False
93
+
94
+ return is_valid
95
+
96
+ def validate_code_challenge(self, challenge, request, *args, **kwargs):
97
+ """
98
+ Validate PKCE code challenge for enhanced security.
99
+ """
100
+ # This is called when PKCE is enabled
101
+ if challenge:
102
+ # Validate that the challenge is base64url encoded and has proper length
103
+ try:
104
+ decoded = base64.urlsafe_b64decode(challenge + '==') # Add padding
105
+ return len(decoded) >= 32 # At least 256 bits
106
+ except Exception:
107
+ return False
108
+
109
+ # If PKCE is required but no challenge provided, reject
110
+ from django.conf import settings
111
+ oauth_settings = getattr(settings, 'OAUTH2_PROVIDER', {})
112
+ if oauth_settings.get('PKCE_REQUIRED', False):
113
+ return False
114
+
115
+ return True
116
+
117
+ def validate_code_verifier(self, verifier, challenge, challenge_method, request, *args, **kwargs):
118
+ """
119
+ Validate PKCE code verifier against the challenge.
120
+ """
121
+ if challenge_method == 'S256':
122
+ # SHA256 challenge method
123
+ verifier_hash = hashlib.sha256(verifier.encode('ascii')).digest()
124
+ verifier_challenge = base64.urlsafe_b64encode(verifier_hash).decode('ascii').rstrip('=')
125
+ return verifier_challenge == challenge
126
+ elif challenge_method == 'plain':
127
+ # Plain text challenge method (less secure, but allowed)
128
+ return verifier == challenge
129
+
130
+ return False
131
+
132
+ def get_default_scopes(self, client_id, request, *args, **kwargs):
133
+ """
134
+ Return default scopes for the application.
135
+ """
136
+ try:
137
+ from oauth2_provider.models import Application
138
+ application = Application.objects.get(client_id=client_id)
139
+
140
+ # For Karrio apps, default to read scope
141
+ if hasattr(application, 'oauth_app'):
142
+ return ['read']
143
+
144
+ except Application.DoesNotExist:
145
+ pass
146
+
147
+ return super().get_default_scopes(client_id, request, *args, **kwargs)
148
+
149
+ def save_authorization_code(self, client_id, code, request, *args, **kwargs):
150
+ """
151
+ Store authorization code with additional security measures.
152
+ """
153
+ # Store the authorization code with enhanced security
154
+ super().save_authorization_code(client_id, code, request, *args, **kwargs)
155
+
156
+ # Log OAuth events for audit purposes
157
+ from karrio.server.core.logging import logger
158
+ logger.info("Authorization code granted", client_id=client_id)
159
+
160
+ def authenticate_user(self, request):
161
+ """
162
+ Authenticate user for Authorization Code Flow.
163
+ """
164
+ user = super().authenticate_user(request)
165
+
166
+ if user:
167
+ # Set additional user context
168
+ request.oauth_user = user
169
+
170
+ return user
@@ -0,0 +1,36 @@
1
+ import pydoc
2
+ import typing
3
+ from rest_framework import permissions, exceptions
4
+ from karrio.server.core.logging import logger
5
+
6
+ import karrio.server.conf as conf
7
+
8
+ PERMISSION_CHECKS = getattr(
9
+ conf.settings, "PERMISSION_CHECKS", ["karrio.server.core.permissions.check_feature_flags"]
10
+ )
11
+
12
+
13
+ class AllowEnabledAPI(permissions.BasePermission):
14
+ """
15
+ Global permission check for blocked IPs.
16
+ """
17
+
18
+ def has_permission(self, request, view):
19
+ if ("/v1/data" in request.path) and (conf.settings.DATA_IMPORT_EXPORT is False):
20
+ raise exceptions.PermissionDenied()
21
+
22
+ if ("/v1/orders" in request.path) and (conf.settings.ORDERS_MANAGEMENT is False):
23
+ raise exceptions.PermissionDenied()
24
+
25
+ return super().has_permission(request, view)
26
+
27
+
28
+ def check_permissions(context, keys: typing.List[str]):
29
+ for check in PERMISSION_CHECKS:
30
+ pydoc.locate(check)(context=context, keys=keys) # type: ignore
31
+
32
+
33
+ def check_feature_flags(keys: typing.List[str] = [], **kwargs):
34
+ flags = [flag for flag in keys if flag in conf.FEATURE_FLAGS]
35
+ if any([conf.settings.get(flag) is False for flag in flags]):
36
+ raise exceptions.PermissionDenied()
@@ -0,0 +1,11 @@
1
+ from rest_framework.renderers import BaseRenderer
2
+
3
+
4
+ class BinaryFileRenderer(BaseRenderer):
5
+ media_type = 'application/octet-stream'
6
+ format = None
7
+ charset = None
8
+ render_style = 'binary'
9
+
10
+ def render(self, data, media_type=None, renderer_context=None):
11
+ return data
@@ -0,0 +1,3 @@
1
+ from rest_framework.routers import DefaultRouter
2
+
3
+ router = DefaultRouter(trailing_slash=False)