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,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
+ )