karrio-server-core 2025.5rc12__py3-none-any.whl → 2026.1.1__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/core/authentication.py +59 -25
- karrio/server/core/config.py +31 -0
- karrio/server/core/datatypes.py +30 -4
- karrio/server/core/dataunits.py +53 -22
- karrio/server/core/exceptions.py +287 -17
- karrio/server/core/filters.py +14 -0
- karrio/server/core/gateway.py +285 -10
- karrio/server/core/logging.py +403 -0
- karrio/server/core/management/commands/runserver.py +5 -0
- karrio/server/core/middleware.py +104 -2
- karrio/server/core/migrations/0006_add_api_log_requested_at_index.py +22 -0
- karrio/server/core/models/base.py +34 -1
- karrio/server/core/oauth_validators.py +2 -3
- karrio/server/core/permissions.py +1 -2
- karrio/server/core/serializers.py +183 -10
- karrio/server/core/signals.py +22 -28
- karrio/server/core/telemetry.py +573 -0
- karrio/server/core/tests/__init__.py +27 -0
- karrio/server/core/{tests.py → tests/base.py} +6 -7
- karrio/server/core/tests/test_exception_level.py +159 -0
- karrio/server/core/tests/test_resource_token.py +593 -0
- karrio/server/core/utils.py +688 -38
- karrio/server/core/validators.py +144 -222
- karrio/server/core/views/oauth.py +13 -12
- karrio/server/core/views/references.py +2 -2
- karrio/server/iam/apps.py +1 -4
- karrio/server/iam/migrations/0002_setup_carrier_permission_groups.py +103 -0
- karrio/server/iam/migrations/0003_remove_permission_groups.py +91 -0
- karrio/server/iam/permissions.py +7 -134
- karrio/server/iam/serializers.py +17 -2
- karrio/server/iam/signals.py +2 -4
- karrio/server/providers/admin.py +1 -1
- karrio/server/providers/management/commands/migrate_rate_sheets.py +101 -0
- karrio/server/providers/migrations/0082_add_zone_identifiers.py +50 -0
- karrio/server/providers/migrations/0083_add_optimized_rate_sheet_structure.py +33 -0
- karrio/server/providers/migrations/0084_alter_servicelevel_currency.py +168 -0
- karrio/server/providers/migrations/0085_populate_dhl_parcel_de_oauth_credentials.py +82 -0
- karrio/server/providers/migrations/0086_rename_dhl_parcel_de_customer_number_to_billing_number.py +71 -0
- karrio/server/providers/migrations/0087_alter_carrier_capabilities.py +38 -0
- karrio/server/providers/migrations/0088_servicelevel_surcharges.py +24 -0
- karrio/server/providers/migrations/0089_servicelevel_cost_max_volume.py +31 -0
- karrio/server/providers/migrations/0090_ratesheet_surcharges_servicelevel_zone_surcharge_ids.py +47 -0
- karrio/server/providers/migrations/0091_migrate_legacy_zones_surcharges.py +154 -0
- karrio/server/providers/models/__init__.py +1 -2
- karrio/server/providers/models/carrier.py +103 -18
- karrio/server/providers/models/service.py +188 -1
- karrio/server/providers/models/sheet.py +371 -0
- karrio/server/providers/serializers/base.py +263 -2
- karrio/server/providers/signals.py +2 -4
- karrio/server/providers/templates/providers/oauth_callback.html +105 -0
- karrio/server/providers/tests/__init__.py +5 -0
- karrio/server/providers/tests/test_connections.py +895 -0
- karrio/server/providers/views/carriers.py +1 -3
- karrio/server/providers/views/connections.py +322 -2
- karrio/server/samples.py +1 -1
- karrio/server/serializers/abstract.py +116 -21
- karrio/server/tracing/migrations/0007_tracingrecord_tracing_created_at_idx.py +19 -0
- karrio/server/tracing/models.py +2 -0
- karrio/server/tracing/utils.py +5 -8
- karrio/server/user/migrations/0007_user_metadata.py +25 -0
- karrio/server/user/models.py +38 -23
- karrio/server/user/serializers.py +1 -0
- karrio/server/user/templates/registration/registration_confirm_email.html +1 -1
- {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/METADATA +2 -2
- {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/RECORD +67 -86
- karrio/server/providers/extension/__init__.py +0 -1
- karrio/server/providers/extension/models/__init__.py +0 -1
- karrio/server/providers/extension/models/allied_express.py +0 -22
- karrio/server/providers/extension/models/allied_express_local.py +0 -22
- karrio/server/providers/extension/models/amazon_shipping.py +0 -27
- karrio/server/providers/extension/models/aramex.py +0 -25
- karrio/server/providers/extension/models/asendia_us.py +0 -21
- karrio/server/providers/extension/models/australiapost.py +0 -20
- karrio/server/providers/extension/models/boxknight.py +0 -19
- karrio/server/providers/extension/models/bpost.py +0 -21
- karrio/server/providers/extension/models/canadapost.py +0 -21
- karrio/server/providers/extension/models/canpar.py +0 -19
- karrio/server/providers/extension/models/chronopost.py +0 -22
- karrio/server/providers/extension/models/colissimo.py +0 -22
- karrio/server/providers/extension/models/dhl_express.py +0 -23
- karrio/server/providers/extension/models/dhl_parcel_de.py +0 -25
- karrio/server/providers/extension/models/dhl_poland.py +0 -22
- karrio/server/providers/extension/models/dhl_universal.py +0 -19
- karrio/server/providers/extension/models/dicom.py +0 -20
- karrio/server/providers/extension/models/dpd.py +0 -37
- karrio/server/providers/extension/models/dpdhl.py +0 -26
- karrio/server/providers/extension/models/easypost.py +0 -20
- karrio/server/providers/extension/models/eshipper.py +0 -21
- karrio/server/providers/extension/models/fedex.py +0 -25
- karrio/server/providers/extension/models/fedex_ws.py +0 -24
- karrio/server/providers/extension/models/freightcom.py +0 -21
- karrio/server/providers/extension/models/generic.py +0 -35
- karrio/server/providers/extension/models/geodis.py +0 -22
- karrio/server/providers/extension/models/hay_post.py +0 -22
- karrio/server/providers/extension/models/laposte.py +0 -19
- karrio/server/providers/extension/models/locate2u.py +0 -22
- karrio/server/providers/extension/models/nationex.py +0 -22
- karrio/server/providers/extension/models/purolator.py +0 -21
- karrio/server/providers/extension/models/roadie.py +0 -18
- karrio/server/providers/extension/models/royalmail.py +0 -19
- karrio/server/providers/extension/models/sendle.py +0 -22
- karrio/server/providers/extension/models/tge.py +0 -63
- karrio/server/providers/extension/models/tnt.py +0 -23
- karrio/server/providers/extension/models/ups.py +0 -23
- karrio/server/providers/extension/models/usps.py +0 -23
- karrio/server/providers/extension/models/usps_international.py +0 -23
- karrio/server/providers/extension/models/usps_wt.py +0 -24
- karrio/server/providers/extension/models/usps_wt_international.py +0 -24
- karrio/server/providers/extension/models/zoom2u.py +0 -23
- karrio/server/providers/tests.py +0 -3
- {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/WHEEL +0 -0
- {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import yaml # type: ignore
|
|
2
2
|
import pydoc
|
|
3
|
-
import logging
|
|
4
3
|
import functools
|
|
5
4
|
from django.db.utils import ProgrammingError
|
|
6
5
|
from django.conf import settings
|
|
@@ -23,8 +22,8 @@ from oauth2_provider.contrib.rest_framework import (
|
|
|
23
22
|
OAuth2Authentication as BaseOAuth2Authentication,
|
|
24
23
|
)
|
|
25
24
|
from django_otp.middleware import OTPMiddleware
|
|
25
|
+
from karrio.server.core.logging import logger
|
|
26
26
|
|
|
27
|
-
logger = logging.getLogger(__name__)
|
|
28
27
|
UserModel = get_user_model()
|
|
29
28
|
AUTHENTICATION_CLASSES = getattr(settings, "AUTHENTICATION_CLASSES", [])
|
|
30
29
|
|
|
@@ -155,13 +154,17 @@ class OAuth2Authentication(BaseOAuth2Authentication):
|
|
|
155
154
|
if user is None:
|
|
156
155
|
# For client credentials flow, the user might be None from the base class
|
|
157
156
|
# but our custom validator should have set it on the request
|
|
158
|
-
if
|
|
157
|
+
if (
|
|
158
|
+
hasattr(request, "user")
|
|
159
|
+
and request.user
|
|
160
|
+
and not request.user.is_anonymous
|
|
161
|
+
):
|
|
159
162
|
user = request.user
|
|
160
|
-
elif hasattr(request,
|
|
163
|
+
elif hasattr(request, "oauth_user"):
|
|
161
164
|
user = request.oauth_user
|
|
162
165
|
# If we still don't have a user, try to get it from the OAuth application
|
|
163
|
-
elif hasattr(token,
|
|
164
|
-
user = getattr(token.application,
|
|
166
|
+
elif hasattr(token, "application") and token.application:
|
|
167
|
+
user = getattr(token.application, "user", None)
|
|
165
168
|
|
|
166
169
|
# Set request context
|
|
167
170
|
request.user = user or request.user
|
|
@@ -170,13 +173,17 @@ class OAuth2Authentication(BaseOAuth2Authentication):
|
|
|
170
173
|
|
|
171
174
|
# Enhanced organization context for OAuth apps
|
|
172
175
|
default_org = None
|
|
173
|
-
if hasattr(token,
|
|
176
|
+
if hasattr(token, "application") and hasattr(
|
|
177
|
+
token.application, "oauth_app"
|
|
178
|
+
):
|
|
174
179
|
# If this is an OAuth app token, use the app owner's organization
|
|
175
180
|
oauth_app = token.application.oauth_app
|
|
176
|
-
if hasattr(oauth_app,
|
|
181
|
+
if hasattr(oauth_app, "created_by") and oauth_app.created_by:
|
|
177
182
|
app_owner = oauth_app.created_by
|
|
178
|
-
if hasattr(app_owner,
|
|
179
|
-
default_org = app_owner.organizations.filter(
|
|
183
|
+
if hasattr(app_owner, "organizations"):
|
|
184
|
+
default_org = app_owner.organizations.filter(
|
|
185
|
+
is_active=True
|
|
186
|
+
).first()
|
|
180
187
|
|
|
181
188
|
request.org = SimpleLazyObject(
|
|
182
189
|
functools.partial(
|
|
@@ -199,7 +206,11 @@ class AccessMixin(mixins.AccessMixin):
|
|
|
199
206
|
"""Verify that the current user is authenticated."""
|
|
200
207
|
|
|
201
208
|
def dispatch(self, request, *args, **kwargs):
|
|
202
|
-
if
|
|
209
|
+
if (
|
|
210
|
+
not hasattr(request, "user")
|
|
211
|
+
or request.user is None
|
|
212
|
+
or not request.user.is_authenticated
|
|
213
|
+
):
|
|
203
214
|
authenticate_user(request)
|
|
204
215
|
|
|
205
216
|
request.user = SimpleLazyObject(
|
|
@@ -240,19 +251,40 @@ class AuthenticationMiddleware(BaseAuthenticationMiddleware):
|
|
|
240
251
|
def authenticate_user(request):
|
|
241
252
|
def authenticate(request, authenticator):
|
|
242
253
|
# Check if user exists and is not authenticated
|
|
243
|
-
if
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
254
|
+
if (
|
|
255
|
+
not hasattr(request, "user")
|
|
256
|
+
or request.user is None
|
|
257
|
+
or not getattr(request.user, "is_authenticated", False)
|
|
258
|
+
):
|
|
259
|
+
logger.debug(f"Trying authenticator: {authenticator}")
|
|
260
|
+
try:
|
|
261
|
+
auth_instance = pydoc.locate(authenticator)()
|
|
262
|
+
auth = auth_instance.authenticate(request)
|
|
263
|
+
|
|
264
|
+
if auth is not None:
|
|
265
|
+
user, token = auth
|
|
266
|
+
request.user = user
|
|
267
|
+
request.token = token
|
|
268
|
+
except AttributeError as e:
|
|
269
|
+
# Skip SessionAuthentication if it fails with _request attribute error
|
|
270
|
+
if "'WSGIRequest' object has no attribute '_request'" in str(e):
|
|
271
|
+
logger.debug(
|
|
272
|
+
f"Skipping {authenticator} - incompatible with middleware context"
|
|
273
|
+
)
|
|
274
|
+
else:
|
|
275
|
+
raise
|
|
276
|
+
except exceptions.AuthenticationFailed:
|
|
277
|
+
# Silently skip authentication failures - let the next authenticator try
|
|
278
|
+
logger.debug(f"Authentication failed with {authenticator}, trying next")
|
|
279
|
+
pass
|
|
250
280
|
|
|
251
281
|
return request
|
|
252
282
|
|
|
253
283
|
try:
|
|
284
|
+
logger.debug(f"Auth classes to try: {AUTHENTICATION_CLASSES}")
|
|
254
285
|
return functools.reduce(authenticate, AUTHENTICATION_CLASSES, request)
|
|
255
|
-
except Exception:
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.debug(f"Authentication error: {e}")
|
|
256
288
|
return request
|
|
257
289
|
|
|
258
290
|
|
|
@@ -266,13 +298,15 @@ def get_request_org(request, user, org_id: str = None, default_org=None):
|
|
|
266
298
|
|
|
267
299
|
if default_org is not None:
|
|
268
300
|
org = default_org
|
|
269
|
-
elif user and hasattr(user,
|
|
301
|
+
elif user and hasattr(user, "id") and user.id:
|
|
270
302
|
orgs = Organization.objects.filter(users__id=user.id)
|
|
271
|
-
org =
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
303
|
+
org = None
|
|
304
|
+
|
|
305
|
+
if org_id is not None:
|
|
306
|
+
org = orgs.filter(id=org_id).first()
|
|
307
|
+
|
|
308
|
+
if org is None:
|
|
309
|
+
org = orgs.filter(is_active=True).first()
|
|
276
310
|
else:
|
|
277
311
|
org = None
|
|
278
312
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Karrio server system configuration adapter."""
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
import karrio.lib as lib
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ConstanceConfig(lib.AbstractSystemConfig):
|
|
8
|
+
"""Django constance configuration adapter.
|
|
9
|
+
|
|
10
|
+
Provides access to runtime configuration values stored
|
|
11
|
+
in Django constance (database-backed settings).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def get(self, key: str, default: typing.Any = None) -> typing.Any:
|
|
15
|
+
from constance import config as constance_config
|
|
16
|
+
|
|
17
|
+
return getattr(constance_config, key, default)
|
|
18
|
+
|
|
19
|
+
def __getitem__(self, key: str) -> typing.Any:
|
|
20
|
+
from constance import config as constance_config
|
|
21
|
+
|
|
22
|
+
return getattr(constance_config, key)
|
|
23
|
+
|
|
24
|
+
def __contains__(self, key: str) -> bool:
|
|
25
|
+
from constance import config as constance_config
|
|
26
|
+
|
|
27
|
+
return hasattr(constance_config, key)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Singleton instance for use across the server
|
|
31
|
+
config = ConstanceConfig()
|
karrio/server/core/datatypes.py
CHANGED
|
@@ -23,13 +23,24 @@ from karrio.core.models import (
|
|
|
23
23
|
PickupCancelRequest as BasePickupCancelRequest,
|
|
24
24
|
ConfirmationDetails as Confirmation,
|
|
25
25
|
TrackingEvent,
|
|
26
|
-
TrackingDetails,
|
|
27
26
|
TrackingInfo,
|
|
27
|
+
ManifestDocument,
|
|
28
|
+
TrackingDetails,
|
|
28
29
|
DocumentFile,
|
|
29
30
|
DocumentUploadRequest,
|
|
30
31
|
ManifestRequest,
|
|
31
32
|
ManifestDetails,
|
|
32
|
-
|
|
33
|
+
RequestPayload,
|
|
34
|
+
OAuthAuthorizePayload,
|
|
35
|
+
OAuthAuthorizeRequest,
|
|
36
|
+
WebhookEventDetails,
|
|
37
|
+
WebhookRegistrationDetails,
|
|
38
|
+
WebhookDeregistrationRequest,
|
|
39
|
+
WebhookRegistrationRequest,
|
|
40
|
+
DutiesCalculationRequest,
|
|
41
|
+
DutiesCalculationDetails,
|
|
42
|
+
InsuranceRequest,
|
|
43
|
+
InsuranceDetails,
|
|
33
44
|
)
|
|
34
45
|
|
|
35
46
|
|
|
@@ -106,7 +117,9 @@ class Address(BaseAddress):
|
|
|
106
117
|
residential: bool = False
|
|
107
118
|
|
|
108
119
|
address_line1: str = ""
|
|
109
|
-
address_line2: str =
|
|
120
|
+
address_line2: str = None
|
|
121
|
+
street_number: str = None
|
|
122
|
+
suite: str = None
|
|
110
123
|
|
|
111
124
|
federal_tax_id: str = None
|
|
112
125
|
state_tax_id: str = None
|
|
@@ -362,8 +375,21 @@ class ManifestResponse:
|
|
|
362
375
|
manifest: Manifest = jstruct.JStruct[Manifest]
|
|
363
376
|
|
|
364
377
|
|
|
378
|
+
@attr.s(auto_attribs=True)
|
|
379
|
+
class DutiesResponse:
|
|
380
|
+
messages: typing.List[Message] = jstruct.JList[Message]
|
|
381
|
+
duties: DutiesCalculationDetails = jstruct.JStruct[DutiesCalculationDetails]
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
@attr.s(auto_attribs=True)
|
|
385
|
+
class InsuranceResponse:
|
|
386
|
+
messages: typing.List[Message] = jstruct.JList[Message]
|
|
387
|
+
insurance: InsuranceDetails = jstruct.JStruct[InsuranceDetails]
|
|
388
|
+
|
|
389
|
+
|
|
365
390
|
@attr.s(auto_attribs=True)
|
|
366
391
|
class Error:
|
|
367
|
-
message: str = None
|
|
368
392
|
code: str = None
|
|
393
|
+
message: str = None
|
|
394
|
+
level: str = None
|
|
369
395
|
details: typing.Dict = None
|
karrio/server/core/dataunits.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import typing
|
|
2
1
|
from constance import config
|
|
3
2
|
from django.urls import reverse
|
|
4
3
|
from rest_framework.request import Request
|
|
@@ -7,6 +6,7 @@ import karrio.lib as lib
|
|
|
7
6
|
import karrio.references as ref
|
|
8
7
|
import karrio.core.units as units
|
|
9
8
|
import karrio.server.conf as conf
|
|
9
|
+
import karrio.server.core.utils as utils
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
PACKAGE_MAPPERS = ref.collect_providers_data()
|
|
@@ -33,16 +33,25 @@ NON_HUBS_CARRIERS = [
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def contextual_metadata(request: Request):
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)
|
|
36
|
+
# Detect HTTPS from headers (for proxied environments like Caddy/ALB)
|
|
37
|
+
is_https = False
|
|
38
|
+
if hasattr(request, 'META'):
|
|
39
|
+
# Check X-Forwarded-Proto header (set by load balancers/proxies)
|
|
40
|
+
forwarded_proto = request.META.get('HTTP_X_FORWARDED_PROTO', '').lower()
|
|
41
|
+
# Check if request is secure (Django's built-in HTTPS detection)
|
|
42
|
+
is_secure = getattr(request, 'is_secure', lambda: False)()
|
|
43
|
+
is_https = forwarded_proto == 'https' or is_secure
|
|
44
|
+
|
|
45
|
+
if hasattr(request, "build_absolute_uri"):
|
|
46
|
+
_host: str = request.build_absolute_uri(
|
|
47
|
+
reverse("karrio.server.core:metadata", kwargs={})
|
|
48
|
+
)
|
|
49
|
+
# Override protocol if we detected HTTPS but build_absolute_uri returned HTTP
|
|
50
|
+
if is_https and _host.startswith('http://'):
|
|
51
|
+
_host = _host.replace('http://', 'https://', 1)
|
|
52
|
+
else:
|
|
53
|
+
_host = "/"
|
|
54
|
+
|
|
46
55
|
host = _host[:-1] if _host[-1] == "/" else _host
|
|
47
56
|
name = lib.identity(
|
|
48
57
|
getattr(conf.settings.tenant, "name", conf.settings.APP_NAME)
|
|
@@ -55,6 +64,30 @@ def contextual_metadata(request: Request):
|
|
|
55
64
|
else getattr(config, "APP_WEBSITE", None) or conf.settings.APP_WEBSITE
|
|
56
65
|
)
|
|
57
66
|
|
|
67
|
+
# Batch fetch all feature flags
|
|
68
|
+
flag_names = [flag for flag, _ in conf.settings.FEATURE_FLAGS]
|
|
69
|
+
|
|
70
|
+
if conf.settings.MULTI_TENANTS:
|
|
71
|
+
# In multi-tenancy mode, feature flags come from tenant.feature_flags (JSON field)
|
|
72
|
+
# No N+1 issue since it's a single field access on an already-loaded tenant object
|
|
73
|
+
tenant = conf.settings.tenant
|
|
74
|
+
tenant_flags = getattr(tenant, "feature_flags", {}) if tenant else {}
|
|
75
|
+
feature_flags = {
|
|
76
|
+
flag: tenant_flags.get(flag, getattr(conf.settings, flag, None))
|
|
77
|
+
for flag in flag_names
|
|
78
|
+
}
|
|
79
|
+
else:
|
|
80
|
+
# In single-tenant mode, batch fetch from Constance to avoid N+1 queries
|
|
81
|
+
constance_values = utils.batch_get_constance_values(flag_names)
|
|
82
|
+
feature_flags = {
|
|
83
|
+
flag: (
|
|
84
|
+
constance_values.get(flag)
|
|
85
|
+
if flag in constance_values
|
|
86
|
+
else getattr(conf.settings, flag, None)
|
|
87
|
+
)
|
|
88
|
+
for flag in flag_names
|
|
89
|
+
}
|
|
90
|
+
|
|
58
91
|
return {
|
|
59
92
|
"VERSION": conf.settings.VERSION,
|
|
60
93
|
"APP_NAME": name,
|
|
@@ -63,10 +96,7 @@ def contextual_metadata(request: Request):
|
|
|
63
96
|
"ADMIN": f"{host}/admin",
|
|
64
97
|
"GRAPHQL": f"{host}/graphql",
|
|
65
98
|
"OPENAPI": f"{host}/openapi",
|
|
66
|
-
**
|
|
67
|
-
flag: getattr(conf.settings, flag, None)
|
|
68
|
-
for flag, _ in conf.settings.FEATURE_FLAGS
|
|
69
|
-
},
|
|
99
|
+
**feature_flags,
|
|
70
100
|
}
|
|
71
101
|
|
|
72
102
|
|
|
@@ -93,30 +123,31 @@ def contextual_reference(request: Request = None, reduced: bool = True):
|
|
|
93
123
|
}
|
|
94
124
|
|
|
95
125
|
def _get_generic_carriers():
|
|
126
|
+
# Get all carriers, then filter by extension instead of hardcoded slug
|
|
96
127
|
system_custom_carriers = [
|
|
97
|
-
c for c in gateway.Carriers.list(system_only=True
|
|
128
|
+
c for c in gateway.Carriers.list(system_only=True)
|
|
129
|
+
if c.ext == "generic"
|
|
98
130
|
]
|
|
99
131
|
custom_carriers = [
|
|
100
132
|
c
|
|
101
133
|
for c in (
|
|
102
|
-
gateway.Carriers.list(context=request
|
|
103
|
-
is_system=True
|
|
104
|
-
)
|
|
134
|
+
gateway.Carriers.list(context=request).exclude(is_system=True)
|
|
105
135
|
if is_authenticated
|
|
106
136
|
else []
|
|
107
137
|
)
|
|
138
|
+
if c.ext == "generic"
|
|
108
139
|
]
|
|
109
140
|
|
|
110
141
|
extra_carriers = {
|
|
111
|
-
|
|
142
|
+
c.carrier_code: c.display_name
|
|
112
143
|
for c in custom_carriers
|
|
113
144
|
}
|
|
114
145
|
system_carriers = {
|
|
115
|
-
|
|
146
|
+
c.carrier_code: c.display_name
|
|
116
147
|
for c in system_custom_carriers
|
|
117
148
|
}
|
|
118
149
|
extra_services = {
|
|
119
|
-
|
|
150
|
+
c.carrier_code: {
|
|
120
151
|
s.service_code: s.service_code
|
|
121
152
|
for s in c.services
|
|
122
153
|
or [
|