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.
- 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 +347 -0
- karrio/server/core/config.py +31 -0
- karrio/server/core/context_processors.py +12 -0
- karrio/server/core/datatypes.py +394 -0
- karrio/server/core/dataunits.py +187 -0
- karrio/server/core/exceptions.py +404 -0
- karrio/server/core/fields.py +12 -0
- karrio/server/core/filters.py +837 -0
- karrio/server/core/gateway.py +1011 -0
- karrio/server/core/logging.py +403 -0
- karrio/server/core/management/commands/cli.py +19 -0
- karrio/server/core/management/commands/create_oauth_client.py +41 -0
- karrio/server/core/management/commands/runserver.py +5 -0
- karrio/server/core/middleware.py +197 -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/0006_add_api_log_requested_at_index.py +22 -0
- karrio/server/core/migrations/__init__.py +0 -0
- karrio/server/core/models/__init__.py +48 -0
- karrio/server/core/models/base.py +103 -0
- karrio/server/core/models/entity.py +24 -0
- karrio/server/core/models/metafield.py +144 -0
- karrio/server/core/models/third_party.py +21 -0
- karrio/server/core/oauth_validators.py +170 -0
- karrio/server/core/permissions.py +36 -0
- karrio/server/core/renderers.py +11 -0
- karrio/server/core/router.py +3 -0
- karrio/server/core/serializers.py +1971 -0
- karrio/server/core/signals.py +55 -0
- karrio/server/core/telemetry.py +573 -0
- karrio/server/core/tests.py +99 -0
- karrio/server/core/tests_resource_token.py +411 -0
- karrio/server/core/urls.py +12 -0
- karrio/server/core/utils.py +1025 -0
- karrio/server/core/validators.py +264 -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 +75 -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 +155 -0
- karrio/server/iam/serializers.py +54 -0
- karrio/server/iam/signals.py +18 -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/management/commands/migrate_rate_sheets.py +101 -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/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/__init__.py +0 -0
- karrio/server/providers/models/__init__.py +16 -0
- karrio/server/providers/models/carrier.py +387 -0
- karrio/server/providers/models/config.py +30 -0
- karrio/server/providers/models/service.py +192 -0
- karrio/server/providers/models/sheet.py +287 -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 +538 -0
- karrio/server/providers/signals.py +25 -0
- 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/urls.py +11 -0
- karrio/server/providers/views/__init__.py +0 -0
- karrio/server/providers/views/carriers.py +267 -0
- karrio/server/providers/views/connections.py +496 -0
- karrio/server/samples.py +352 -0
- karrio/server/serializers/__init__.py +2 -0
- karrio/server/serializers/abstract.py +602 -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/0007_tracingrecord_tracing_created_at_idx.py +19 -0
- karrio/server/tracing/migrations/__init__.py +0 -0
- karrio/server/tracing/models.py +82 -0
- karrio/server/tracing/tests.py +3 -0
- karrio/server/tracing/utils.py +109 -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/0007_user_metadata.py +25 -0
- karrio/server/user/migrations/__init__.py +0 -0
- karrio/server/user/models.py +218 -0
- karrio/server/user/serializers.py +47 -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.5.dist-info/METADATA +32 -0
- karrio_server_core-2025.5.dist-info/RECORD +213 -0
- karrio_server_core-2025.5.dist-info/WHEEL +5 -0
- karrio_server_core-2025.5.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Generated by Django 3.2.13 on 2022-07-18 12:05
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
import karrio.server.core.utils as utils
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def forwards_func(apps, schema_editor):
|
|
9
|
+
db_alias = schema_editor.connection.alias
|
|
10
|
+
APILog = apps.get_model("core", "APILog")
|
|
11
|
+
APILogIndex = apps.get_model("core", "APILogIndex")
|
|
12
|
+
logs = APILog.objects.using(db_alias).filter(response__isnull=False).iterator()
|
|
13
|
+
|
|
14
|
+
for log in logs:
|
|
15
|
+
response = utils.failsafe(
|
|
16
|
+
lambda: utils.DP.to_dict(utils.DP.to_dict(log.response))
|
|
17
|
+
)
|
|
18
|
+
entity_id = utils.failsafe(lambda: response["id"])
|
|
19
|
+
|
|
20
|
+
if entity_id is not None:
|
|
21
|
+
_index = APILogIndex(
|
|
22
|
+
apirequestlog_ptr=log,
|
|
23
|
+
entity_id=entity_id,
|
|
24
|
+
)
|
|
25
|
+
_index.save_base(raw=True)
|
|
26
|
+
|
|
27
|
+
if isinstance(response, dict):
|
|
28
|
+
log.response = utils.DP.jsonify(response)
|
|
29
|
+
log.save()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def reverse_func(apps, schema_editor):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Migration(migrations.Migration):
|
|
37
|
+
dependencies = [
|
|
38
|
+
("rest_framework_tracking", "0011_auto_20201117_2016"),
|
|
39
|
+
("core", "0001_initial"),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
operations = [
|
|
43
|
+
migrations.CreateModel(
|
|
44
|
+
name="APILogIndex",
|
|
45
|
+
fields=[
|
|
46
|
+
(
|
|
47
|
+
"apirequestlog_ptr",
|
|
48
|
+
models.OneToOneField(
|
|
49
|
+
auto_created=True,
|
|
50
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
51
|
+
parent_link=True,
|
|
52
|
+
primary_key=True,
|
|
53
|
+
serialize=False,
|
|
54
|
+
to="rest_framework_tracking.apirequestlog",
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
"entity_id",
|
|
59
|
+
models.CharField(db_index=True, max_length=50, null=True),
|
|
60
|
+
),
|
|
61
|
+
],
|
|
62
|
+
options={
|
|
63
|
+
"verbose_name": "API Request Log",
|
|
64
|
+
"abstract": False,
|
|
65
|
+
},
|
|
66
|
+
bases=("core.apilog",),
|
|
67
|
+
),
|
|
68
|
+
migrations.RunPython(forwards_func, reverse_func),
|
|
69
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Generated by Django 4.1.4 on 2022-12-17 11:46
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import karrio.server.core.utils as utils
|
|
5
|
+
import karrio.lib as lib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def forwards_func(apps, schema_editor):
|
|
9
|
+
db_alias = schema_editor.connection.alias
|
|
10
|
+
APILog = apps.get_model("core", "APILog")
|
|
11
|
+
APILogIndex = apps.get_model("core", "APILogIndex")
|
|
12
|
+
logs = (
|
|
13
|
+
APILog.objects.using(db_alias)
|
|
14
|
+
.filter(models.Q(response__icontains="test_mode"))
|
|
15
|
+
.iterator()
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
for log in logs:
|
|
19
|
+
response = utils.failsafe(lambda: lib.to_dict(lib.to_dict(log.response)))
|
|
20
|
+
entity_id = utils.failsafe(lambda: response["id"])
|
|
21
|
+
test_mode = utils.failsafe(lambda: response["test_mode"])
|
|
22
|
+
|
|
23
|
+
if test_mode is None and '"test_mode": true' in log.response:
|
|
24
|
+
test_mode = True
|
|
25
|
+
if test_mode is None and '"test_mode": false' in log.response:
|
|
26
|
+
test_mode = False
|
|
27
|
+
|
|
28
|
+
if test_mode is not None and hasattr(log, "apilogindex"):
|
|
29
|
+
log.apilogindex.test_mode = test_mode
|
|
30
|
+
log.apilogindex.save()
|
|
31
|
+
|
|
32
|
+
if hasattr(log, "apilogindex") is False:
|
|
33
|
+
_index = APILogIndex(
|
|
34
|
+
apirequestlog_ptr=log,
|
|
35
|
+
entity_id=entity_id,
|
|
36
|
+
test_mode=test_mode,
|
|
37
|
+
)
|
|
38
|
+
_index.save_base(raw=True)
|
|
39
|
+
|
|
40
|
+
log.response = utils.DP.jsonify(response)
|
|
41
|
+
log.save()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def reverse_func(apps, schema_editor):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Migration(migrations.Migration):
|
|
49
|
+
dependencies = [
|
|
50
|
+
("core", "0002_apilogindex"),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
operations = [
|
|
54
|
+
migrations.AddField(
|
|
55
|
+
model_name="apilogindex",
|
|
56
|
+
name="test_mode",
|
|
57
|
+
field=models.BooleanField(
|
|
58
|
+
default=True, help_text="execution context", null=True
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
migrations.RunPython(forwards_func, reverse_func),
|
|
62
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Generated by Django 4.2.8 on 2024-01-13 22:41
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
import django.db.models.deletion
|
|
6
|
+
import functools
|
|
7
|
+
import karrio.server.core.models.base
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Migration(migrations.Migration):
|
|
11
|
+
dependencies = [
|
|
12
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
13
|
+
("core", "0003_apilogindex_test_mode"),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.CreateModel(
|
|
18
|
+
name="Metafield",
|
|
19
|
+
fields=[
|
|
20
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
21
|
+
("updated_at", models.DateTimeField(auto_now=True)),
|
|
22
|
+
(
|
|
23
|
+
"id",
|
|
24
|
+
models.CharField(
|
|
25
|
+
default=functools.partial(
|
|
26
|
+
karrio.server.core.models.base.uuid,
|
|
27
|
+
*(),
|
|
28
|
+
**{"prefix": "metaf_"}
|
|
29
|
+
),
|
|
30
|
+
editable=False,
|
|
31
|
+
max_length=50,
|
|
32
|
+
primary_key=True,
|
|
33
|
+
serialize=False,
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
(
|
|
37
|
+
"key",
|
|
38
|
+
models.CharField(db_index=True, max_length=50, verbose_name="name"),
|
|
39
|
+
),
|
|
40
|
+
("value", models.CharField(blank=True, max_length=250, null=True)),
|
|
41
|
+
(
|
|
42
|
+
"type",
|
|
43
|
+
models.CharField(
|
|
44
|
+
choices=[
|
|
45
|
+
("text", "text"),
|
|
46
|
+
("number", "number"),
|
|
47
|
+
("boolean", "boolean"),
|
|
48
|
+
],
|
|
49
|
+
db_index=True,
|
|
50
|
+
default="text",
|
|
51
|
+
max_length=50,
|
|
52
|
+
verbose_name="type",
|
|
53
|
+
),
|
|
54
|
+
),
|
|
55
|
+
("is_required", models.BooleanField(default=False)),
|
|
56
|
+
(
|
|
57
|
+
"created_by",
|
|
58
|
+
models.ForeignKey(
|
|
59
|
+
blank=True,
|
|
60
|
+
editable=False,
|
|
61
|
+
null=True,
|
|
62
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
63
|
+
to=settings.AUTH_USER_MODEL,
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
],
|
|
67
|
+
options={
|
|
68
|
+
"verbose_name": "Metafield",
|
|
69
|
+
"verbose_name_plural": "Metafields",
|
|
70
|
+
"db_table": "metafield",
|
|
71
|
+
},
|
|
72
|
+
bases=(karrio.server.core.models.base.ControlledAccessModel, models.Model),
|
|
73
|
+
),
|
|
74
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Generated by Django 5.2.3 on 2025-06-19 05:27
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('core', '0004_metafield'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name='metafield',
|
|
15
|
+
name='type',
|
|
16
|
+
field=models.CharField(choices=[('text', 'text'), ('number', 'number'), ('boolean', 'boolean'), ('json', 'json'), ('date', 'date'), ('date_time', 'date_time'), ('password', 'password')], db_index=True, default='text', max_length=50, verbose_name='type'),
|
|
17
|
+
),
|
|
18
|
+
migrations.AlterField(
|
|
19
|
+
model_name='metafield',
|
|
20
|
+
name='value',
|
|
21
|
+
field=models.JSONField(blank=True, null=True),
|
|
22
|
+
),
|
|
23
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Generated migration to add index to APIRequestLog table for archiving performance
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('core', '0005_alter_metafield_type_alter_metafield_value'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.RunSQL(
|
|
14
|
+
# Create index on requested_at for archiving queries
|
|
15
|
+
sql=[
|
|
16
|
+
"CREATE INDEX IF NOT EXISTS api_log_requested_at_idx ON rest_framework_tracking_apirequestlog (requested_at);",
|
|
17
|
+
],
|
|
18
|
+
reverse_sql=[
|
|
19
|
+
"DROP INDEX IF EXISTS api_log_requested_at_idx;",
|
|
20
|
+
],
|
|
21
|
+
),
|
|
22
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import yaml
|
|
3
|
+
import typing
|
|
4
|
+
import functools
|
|
5
|
+
|
|
6
|
+
import karrio.lib as lib
|
|
7
|
+
from karrio.server.core.models.base import (
|
|
8
|
+
ControlledAccessModel,
|
|
9
|
+
get_access_filter,
|
|
10
|
+
register_model,
|
|
11
|
+
uuid,
|
|
12
|
+
MetafieldType,
|
|
13
|
+
METAFIELD_TYPE,
|
|
14
|
+
)
|
|
15
|
+
from karrio.server.core.models.third_party import (
|
|
16
|
+
APILog,
|
|
17
|
+
APILogIndex,
|
|
18
|
+
)
|
|
19
|
+
from karrio.server.core.models.metafield import (
|
|
20
|
+
Metafield,
|
|
21
|
+
)
|
|
22
|
+
from karrio.server.core.models.entity import Entity, OwnedEntity
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _identity(value: typing.Any):
|
|
26
|
+
return value
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def field_default(value: typing.Any) -> typing.Callable:
|
|
30
|
+
return functools.partial(_identity, value=value)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def metafields_to_dict(metafields: typing.List[Metafield]) -> dict:
|
|
34
|
+
_values = {}
|
|
35
|
+
|
|
36
|
+
for _ in metafields:
|
|
37
|
+
# Skip metafields with None or empty values
|
|
38
|
+
if _.value is None or _.value == "":
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
if _.type == "number":
|
|
42
|
+
_values.update({_.key: lib.failsafe(lambda: ast.literal_eval(_.value))})
|
|
43
|
+
elif _.type == "boolean":
|
|
44
|
+
_values.update({_.key: lib.failsafe(lambda: bool(yaml.safe_load(_.value)))})
|
|
45
|
+
else:
|
|
46
|
+
_values.update({_.key: _.value})
|
|
47
|
+
|
|
48
|
+
return _values
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import pydoc
|
|
2
|
+
import typing
|
|
3
|
+
import functools
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
from django.db import models
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
|
|
8
|
+
import karrio.lib as lib
|
|
9
|
+
|
|
10
|
+
T = typing.TypeVar("T")
|
|
11
|
+
MODEL_TRANSFORMERS = getattr(settings, "MODEL_TRANSFORMERS", [])
|
|
12
|
+
ACCESS_METHOD = getattr(
|
|
13
|
+
settings,
|
|
14
|
+
"KARRIO_ENTITY_ACCESS_METHOD",
|
|
15
|
+
"karrio.server.core.middleware.WideAccess",
|
|
16
|
+
)
|
|
17
|
+
get_access_filter = pydoc.locate(ACCESS_METHOD)()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def uuid(prefix: str = None):
|
|
21
|
+
return f'{prefix or ""}{uuid4().hex}'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ControlledAccessModel:
|
|
25
|
+
@classmethod
|
|
26
|
+
def access_by(cls: models.Model, context, manager: str = "objects"):
|
|
27
|
+
test_mode = (
|
|
28
|
+
context.get("test_mode")
|
|
29
|
+
if isinstance(context, dict)
|
|
30
|
+
else getattr(context, "test_mode", None)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if hasattr(cls, "created_by"):
|
|
34
|
+
key = "created_by"
|
|
35
|
+
elif hasattr(cls, "actor"):
|
|
36
|
+
key = "actor"
|
|
37
|
+
else:
|
|
38
|
+
key = "user"
|
|
39
|
+
|
|
40
|
+
query = get_access_filter(context, key)
|
|
41
|
+
|
|
42
|
+
if hasattr(cls, "test_mode") and test_mode is not None:
|
|
43
|
+
query = query & models.Q(
|
|
44
|
+
models.Q(test_mode=test_mode) | models.Q(test_mode__isnull=True)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
queryset = getattr(cls, manager, cls.objects)
|
|
48
|
+
|
|
49
|
+
if hasattr(cls, "resolve_context_data"):
|
|
50
|
+
queryset = cls.resolve_context_data(queryset, context)
|
|
51
|
+
|
|
52
|
+
return queryset.filter(query)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def resolve_context_data(cls, queryset, context):
|
|
56
|
+
# 1. Self-optimization (e.g., Carrier resolving its own configs)
|
|
57
|
+
if hasattr(queryset, 'resolve_config_for'):
|
|
58
|
+
queryset = queryset.resolve_config_for(context)
|
|
59
|
+
|
|
60
|
+
# 2. Relation optimization
|
|
61
|
+
relations = getattr(cls, "CONTEXT_RELATIONS", [])
|
|
62
|
+
if relations:
|
|
63
|
+
from django.db.models import Prefetch
|
|
64
|
+
prefetches = []
|
|
65
|
+
|
|
66
|
+
for field_name in relations:
|
|
67
|
+
field = cls._meta.get_field(field_name)
|
|
68
|
+
related_model = field.related_model
|
|
69
|
+
|
|
70
|
+
# Check if related model is capable of context resolution
|
|
71
|
+
if hasattr(related_model.objects, 'resolve_config_for'):
|
|
72
|
+
qs = related_model.objects.resolve_config_for(context)
|
|
73
|
+
prefetches.append(Prefetch(field_name, queryset=qs))
|
|
74
|
+
elif hasattr(related_model, 'access_by'):
|
|
75
|
+
# Recurse into related model's access_by (which calls its resolve_context_data)
|
|
76
|
+
qs = related_model.access_by(context)
|
|
77
|
+
prefetches.append(Prefetch(field_name, queryset=qs))
|
|
78
|
+
|
|
79
|
+
if prefetches:
|
|
80
|
+
queryset = queryset.prefetch_related(*prefetches)
|
|
81
|
+
|
|
82
|
+
return queryset
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def register_model(model: T) -> T:
|
|
86
|
+
transform = lambda model, transformer: (
|
|
87
|
+
model if transformer is None else pydoc.locate(transformer)(model)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return functools.reduce(transform, MODEL_TRANSFORMERS, model)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class MetafieldType(lib.StrEnum):
|
|
94
|
+
text = "text"
|
|
95
|
+
number = "number"
|
|
96
|
+
boolean = "boolean"
|
|
97
|
+
json = "json"
|
|
98
|
+
date = "date"
|
|
99
|
+
date_time = "date_time"
|
|
100
|
+
password = "password"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
METAFIELD_TYPE = [(c.name, c.name) for c in list(MetafieldType)]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
from karrio.server.core.models.base import uuid, ControlledAccessModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Entity(models.Model):
|
|
7
|
+
class Meta:
|
|
8
|
+
abstract = True
|
|
9
|
+
|
|
10
|
+
id = models.CharField(max_length=50, primary_key=True, default=uuid, editable=False)
|
|
11
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
12
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
13
|
+
|
|
14
|
+
def __str__(self):
|
|
15
|
+
return (
|
|
16
|
+
str(self.id) if self.id is not None else f"{self.__class__.__name__}(None)"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class OwnedEntity(ControlledAccessModel, Entity):
|
|
21
|
+
class Meta:
|
|
22
|
+
abstract = True
|
|
23
|
+
|
|
24
|
+
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
import functools
|
|
4
|
+
from datetime import datetime, date
|
|
5
|
+
import django.conf as conf
|
|
6
|
+
import django.db.models as models
|
|
7
|
+
import django.utils.translation as translation
|
|
8
|
+
from django.core.exceptions import ValidationError
|
|
9
|
+
|
|
10
|
+
import karrio.server.core.models.base as core
|
|
11
|
+
import karrio.server.core.models.entity as entity
|
|
12
|
+
|
|
13
|
+
_ = translation.gettext_lazy
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@core.register_model
|
|
17
|
+
class Metafield(entity.OwnedEntity):
|
|
18
|
+
class Meta:
|
|
19
|
+
db_table = "metafield"
|
|
20
|
+
verbose_name = "Metafield"
|
|
21
|
+
verbose_name_plural = "Metafields"
|
|
22
|
+
|
|
23
|
+
id = models.CharField(
|
|
24
|
+
max_length=50,
|
|
25
|
+
editable=False,
|
|
26
|
+
primary_key=True,
|
|
27
|
+
default=functools.partial(core.uuid, prefix="metaf_"),
|
|
28
|
+
)
|
|
29
|
+
key = models.CharField(_("name"), max_length=50, db_index=True)
|
|
30
|
+
value = models.JSONField(null=True, blank=True)
|
|
31
|
+
type = models.CharField(
|
|
32
|
+
_("type"),
|
|
33
|
+
max_length=50,
|
|
34
|
+
choices=core.METAFIELD_TYPE,
|
|
35
|
+
default=core.METAFIELD_TYPE[0][0],
|
|
36
|
+
db_index=True,
|
|
37
|
+
)
|
|
38
|
+
is_required = models.BooleanField(null=False, default=False)
|
|
39
|
+
|
|
40
|
+
# Related fields
|
|
41
|
+
created_by = models.ForeignKey(
|
|
42
|
+
conf.settings.AUTH_USER_MODEL,
|
|
43
|
+
blank=True,
|
|
44
|
+
null=True,
|
|
45
|
+
on_delete=models.CASCADE,
|
|
46
|
+
editable=False,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def object_type(self):
|
|
51
|
+
return "metafield"
|
|
52
|
+
|
|
53
|
+
def clean(self):
|
|
54
|
+
"""Validate the value based on the metafield type."""
|
|
55
|
+
super().clean()
|
|
56
|
+
if self.value is not None:
|
|
57
|
+
self._validate_value()
|
|
58
|
+
|
|
59
|
+
def _validate_value(self):
|
|
60
|
+
"""Validate value based on metafield type."""
|
|
61
|
+
if self.type == core.MetafieldType.text:
|
|
62
|
+
if not isinstance(self.value, str):
|
|
63
|
+
raise ValidationError(f"Value must be a string for type 'text'")
|
|
64
|
+
|
|
65
|
+
elif self.type == core.MetafieldType.number:
|
|
66
|
+
if not isinstance(self.value, (int, float)):
|
|
67
|
+
raise ValidationError(f"Value must be a number for type 'number'")
|
|
68
|
+
|
|
69
|
+
elif self.type == core.MetafieldType.boolean:
|
|
70
|
+
if not isinstance(self.value, bool):
|
|
71
|
+
raise ValidationError(f"Value must be a boolean for type 'boolean'")
|
|
72
|
+
|
|
73
|
+
elif self.type == core.MetafieldType.json:
|
|
74
|
+
# JSON can be any valid JSON value (dict, list, string, number, boolean, null)
|
|
75
|
+
try:
|
|
76
|
+
if isinstance(self.value, str):
|
|
77
|
+
json.loads(self.value)
|
|
78
|
+
except (json.JSONDecodeError, TypeError):
|
|
79
|
+
raise ValidationError(f"Value must be valid JSON for type 'json'")
|
|
80
|
+
|
|
81
|
+
elif self.type == core.MetafieldType.date:
|
|
82
|
+
if isinstance(self.value, str):
|
|
83
|
+
try:
|
|
84
|
+
datetime.strptime(self.value, '%Y-%m-%d')
|
|
85
|
+
except ValueError:
|
|
86
|
+
raise ValidationError(f"Value must be a valid date (YYYY-MM-DD) for type 'date'")
|
|
87
|
+
else:
|
|
88
|
+
raise ValidationError(f"Value must be a date string (YYYY-MM-DD) for type 'date'")
|
|
89
|
+
|
|
90
|
+
elif self.type == core.MetafieldType.date_time:
|
|
91
|
+
if isinstance(self.value, str):
|
|
92
|
+
try:
|
|
93
|
+
datetime.fromisoformat(self.value.replace('Z', '+00:00'))
|
|
94
|
+
except ValueError:
|
|
95
|
+
raise ValidationError(f"Value must be a valid ISO datetime for type 'date_time'")
|
|
96
|
+
else:
|
|
97
|
+
raise ValidationError(f"Value must be a datetime string for type 'date_time'")
|
|
98
|
+
|
|
99
|
+
elif self.type == core.MetafieldType.password:
|
|
100
|
+
if not isinstance(self.value, str):
|
|
101
|
+
raise ValidationError(f"Value must be a string for type 'password'")
|
|
102
|
+
|
|
103
|
+
def get_parsed_value(self):
|
|
104
|
+
"""Return the value parsed according to its type."""
|
|
105
|
+
if self.value is None:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
if self.type == core.MetafieldType.text:
|
|
109
|
+
return str(self.value)
|
|
110
|
+
|
|
111
|
+
elif self.type == core.MetafieldType.number:
|
|
112
|
+
return self.value
|
|
113
|
+
|
|
114
|
+
elif self.type == core.MetafieldType.boolean:
|
|
115
|
+
return bool(self.value)
|
|
116
|
+
|
|
117
|
+
elif self.type == core.MetafieldType.json:
|
|
118
|
+
if isinstance(self.value, str):
|
|
119
|
+
try:
|
|
120
|
+
return json.loads(self.value)
|
|
121
|
+
except json.JSONDecodeError:
|
|
122
|
+
return self.value
|
|
123
|
+
return self.value
|
|
124
|
+
|
|
125
|
+
elif self.type == core.MetafieldType.date:
|
|
126
|
+
if isinstance(self.value, str):
|
|
127
|
+
try:
|
|
128
|
+
return datetime.strptime(self.value, '%Y-%m-%d').date()
|
|
129
|
+
except ValueError:
|
|
130
|
+
return self.value
|
|
131
|
+
return self.value
|
|
132
|
+
|
|
133
|
+
elif self.type == core.MetafieldType.date_time:
|
|
134
|
+
if isinstance(self.value, str):
|
|
135
|
+
try:
|
|
136
|
+
return datetime.fromisoformat(self.value.replace('Z', '+00:00'))
|
|
137
|
+
except ValueError:
|
|
138
|
+
return self.value
|
|
139
|
+
return self.value
|
|
140
|
+
|
|
141
|
+
elif self.type == core.MetafieldType.password:
|
|
142
|
+
return str(self.value)
|
|
143
|
+
|
|
144
|
+
return self.value
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from rest_framework_tracking.models import APIRequestLog
|
|
3
|
+
|
|
4
|
+
from karrio.server.core.models.base import ControlledAccessModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class APILog(APIRequestLog, ControlledAccessModel):
|
|
8
|
+
class Meta:
|
|
9
|
+
ordering = ["-requested_at"]
|
|
10
|
+
proxy = True
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def object_type(self):
|
|
14
|
+
return "log"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class APILogIndex(APILog):
|
|
18
|
+
entity_id = models.CharField(max_length=50, null=True, db_index=True)
|
|
19
|
+
test_mode = models.BooleanField(
|
|
20
|
+
default=True, null=True, help_text="execution context"
|
|
21
|
+
)
|