django-nativemojo 0.1.15__py3-none-any.whl → 0.1.17__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.
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/METADATA +3 -2
- django_nativemojo-0.1.17.dist-info/RECORD +302 -0
- mojo/__init__.py +1 -1
- mojo/apps/account/management/commands/serializer_admin.py +121 -1
- mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
- mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
- mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
- mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
- mojo/apps/account/migrations/0010_group_avatar.py +20 -0
- mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
- mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
- mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
- mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
- mojo/apps/account/models/__init__.py +2 -0
- mojo/apps/account/models/device.py +279 -0
- mojo/apps/account/models/group.py +294 -8
- mojo/apps/account/models/member.py +14 -1
- mojo/apps/account/models/push/__init__.py +4 -0
- mojo/apps/account/models/push/config.py +112 -0
- mojo/apps/account/models/push/delivery.py +93 -0
- mojo/apps/account/models/push/device.py +66 -0
- mojo/apps/account/models/push/template.py +99 -0
- mojo/apps/account/models/user.py +190 -17
- mojo/apps/account/rest/__init__.py +2 -0
- mojo/apps/account/rest/device.py +39 -0
- mojo/apps/account/rest/group.py +8 -0
- mojo/apps/account/rest/push.py +187 -0
- mojo/apps/account/rest/user.py +95 -5
- mojo/apps/account/services/__init__.py +1 -0
- mojo/apps/account/services/push.py +363 -0
- mojo/apps/aws/migrations/0001_initial.py +206 -0
- mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
- mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
- mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
- mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
- mojo/apps/aws/models/__init__.py +19 -0
- mojo/apps/aws/models/email_attachment.py +99 -0
- mojo/apps/aws/models/email_domain.py +218 -0
- mojo/apps/aws/models/email_template.py +132 -0
- mojo/apps/aws/models/incoming_email.py +197 -0
- mojo/apps/aws/models/mailbox.py +288 -0
- mojo/apps/aws/models/sent_message.py +175 -0
- mojo/apps/aws/rest/__init__.py +6 -0
- mojo/apps/aws/rest/email.py +33 -0
- mojo/apps/aws/rest/email_ops.py +183 -0
- mojo/apps/aws/rest/messages.py +32 -0
- mojo/apps/aws/rest/send.py +101 -0
- mojo/apps/aws/rest/sns.py +403 -0
- mojo/apps/aws/rest/templates.py +19 -0
- mojo/apps/aws/services/__init__.py +32 -0
- mojo/apps/aws/services/email.py +390 -0
- mojo/apps/aws/services/email_ops.py +548 -0
- mojo/apps/docit/__init__.py +6 -0
- mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
- mojo/apps/docit/markdown_plugins/toc.py +12 -0
- mojo/apps/docit/migrations/0001_initial.py +113 -0
- mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
- mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
- mojo/apps/docit/models/__init__.py +17 -0
- mojo/apps/docit/models/asset.py +231 -0
- mojo/apps/docit/models/book.py +227 -0
- mojo/apps/docit/models/page.py +319 -0
- mojo/apps/docit/models/page_revision.py +203 -0
- mojo/apps/docit/rest/__init__.py +10 -0
- mojo/apps/docit/rest/asset.py +17 -0
- mojo/apps/docit/rest/book.py +22 -0
- mojo/apps/docit/rest/page.py +22 -0
- mojo/apps/docit/rest/page_revision.py +17 -0
- mojo/apps/docit/services/__init__.py +11 -0
- mojo/apps/docit/services/docit.py +315 -0
- mojo/apps/docit/services/markdown.py +44 -0
- mojo/apps/fileman/backends/s3.py +209 -0
- mojo/apps/fileman/models/file.py +45 -9
- mojo/apps/fileman/models/manager.py +269 -3
- mojo/apps/incident/migrations/0007_event_uid.py +18 -0
- mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
- mojo/apps/incident/migrations/0009_incident_status.py +18 -0
- mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
- mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
- mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
- mojo/apps/incident/models/__init__.py +1 -0
- mojo/apps/incident/models/event.py +35 -0
- mojo/apps/incident/models/incident.py +2 -0
- mojo/apps/incident/models/ticket.py +62 -0
- mojo/apps/incident/reporter.py +21 -3
- mojo/apps/incident/rest/__init__.py +1 -0
- mojo/apps/incident/rest/ticket.py +43 -0
- mojo/apps/jobs/__init__.py +489 -0
- mojo/apps/jobs/adapters.py +24 -0
- mojo/apps/jobs/cli.py +616 -0
- mojo/apps/jobs/daemon.py +370 -0
- mojo/apps/jobs/examples/sample_jobs.py +376 -0
- mojo/apps/jobs/examples/webhook_examples.py +203 -0
- mojo/apps/jobs/handlers/__init__.py +5 -0
- mojo/apps/jobs/handlers/webhook.py +317 -0
- mojo/apps/jobs/job_engine.py +734 -0
- mojo/apps/jobs/keys.py +203 -0
- mojo/apps/jobs/local_queue.py +363 -0
- mojo/apps/jobs/management/__init__.py +3 -0
- mojo/apps/jobs/management/commands/__init__.py +3 -0
- mojo/apps/jobs/manager.py +1327 -0
- mojo/apps/jobs/migrations/0001_initial.py +97 -0
- mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
- mojo/apps/jobs/models/__init__.py +6 -0
- mojo/apps/jobs/models/job.py +441 -0
- mojo/apps/jobs/rest/__init__.py +2 -0
- mojo/apps/jobs/rest/control.py +466 -0
- mojo/apps/jobs/rest/jobs.py +421 -0
- mojo/apps/jobs/scheduler.py +571 -0
- mojo/apps/jobs/services/__init__.py +6 -0
- mojo/apps/jobs/services/job_actions.py +465 -0
- mojo/apps/jobs/settings.py +209 -0
- mojo/apps/logit/models/log.py +3 -0
- mojo/apps/metrics/__init__.py +8 -1
- mojo/apps/metrics/redis_metrics.py +198 -0
- mojo/apps/metrics/rest/__init__.py +3 -0
- mojo/apps/metrics/rest/categories.py +266 -0
- mojo/apps/metrics/rest/helpers.py +48 -0
- mojo/apps/metrics/rest/permissions.py +99 -0
- mojo/apps/metrics/rest/values.py +277 -0
- mojo/apps/metrics/utils.py +17 -0
- mojo/decorators/http.py +40 -1
- mojo/helpers/aws/__init__.py +11 -7
- mojo/helpers/aws/inbound_email.py +309 -0
- mojo/helpers/aws/kms.py +413 -0
- mojo/helpers/aws/ses_domain.py +959 -0
- mojo/helpers/crypto/__init__.py +1 -1
- mojo/helpers/crypto/utils.py +15 -0
- mojo/helpers/location/__init__.py +2 -0
- mojo/helpers/location/countries.py +262 -0
- mojo/helpers/location/geolocation.py +196 -0
- mojo/helpers/logit.py +37 -0
- mojo/helpers/redis/__init__.py +2 -0
- mojo/helpers/redis/adapter.py +606 -0
- mojo/helpers/redis/client.py +48 -0
- mojo/helpers/redis/pool.py +225 -0
- mojo/helpers/request.py +8 -0
- mojo/helpers/response.py +8 -0
- mojo/middleware/auth.py +1 -1
- mojo/middleware/cors.py +40 -0
- mojo/middleware/logging.py +131 -12
- mojo/middleware/mojo.py +5 -0
- mojo/models/rest.py +271 -57
- mojo/models/secrets.py +86 -0
- mojo/serializers/__init__.py +16 -10
- mojo/serializers/core/__init__.py +90 -0
- mojo/serializers/core/cache/__init__.py +121 -0
- mojo/serializers/core/cache/backends.py +518 -0
- mojo/serializers/core/cache/base.py +102 -0
- mojo/serializers/core/cache/disabled.py +181 -0
- mojo/serializers/core/cache/memory.py +287 -0
- mojo/serializers/core/cache/redis.py +533 -0
- mojo/serializers/core/cache/utils.py +454 -0
- mojo/serializers/{manager.py → core/manager.py} +53 -4
- mojo/serializers/core/serializer.py +475 -0
- mojo/serializers/{advanced/formats → formats}/csv.py +116 -139
- mojo/serializers/suggested_improvements.md +388 -0
- testit/client.py +1 -1
- testit/helpers.py +14 -0
- testit/runner.py +23 -6
- django_nativemojo-0.1.15.dist-info/RECORD +0 -234
- mojo/apps/notify/README.md +0 -91
- mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
- mojo/apps/notify/admin.py +0 -52
- mojo/apps/notify/handlers/example_handlers.py +0 -516
- mojo/apps/notify/handlers/ses/__init__.py +0 -25
- mojo/apps/notify/handlers/ses/complaint.py +0 -25
- mojo/apps/notify/handlers/ses/message.py +0 -86
- mojo/apps/notify/management/commands/__init__.py +0 -1
- mojo/apps/notify/management/commands/process_notifications.py +0 -370
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +0 -12
- mojo/apps/notify/models/account.py +0 -128
- mojo/apps/notify/models/attachment.py +0 -24
- mojo/apps/notify/models/bounce.py +0 -68
- mojo/apps/notify/models/complaint.py +0 -40
- mojo/apps/notify/models/inbox.py +0 -113
- mojo/apps/notify/models/inbox_message.py +0 -173
- mojo/apps/notify/models/outbox.py +0 -129
- mojo/apps/notify/models/outbox_message.py +0 -288
- mojo/apps/notify/models/template.py +0 -30
- mojo/apps/notify/providers/aws.py +0 -73
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +0 -2
- mojo/apps/notify/utils/notifications.py +0 -404
- mojo/apps/notify/utils/parsing.py +0 -202
- mojo/apps/notify/utils/render.py +0 -144
- mojo/apps/tasks/README.md +0 -118
- mojo/apps/tasks/__init__.py +0 -44
- mojo/apps/tasks/manager.py +0 -644
- mojo/apps/tasks/rest/__init__.py +0 -2
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +0 -76
- mojo/apps/tasks/runner.py +0 -439
- mojo/apps/tasks/task.py +0 -99
- mojo/apps/tasks/tq_handlers.py +0 -132
- mojo/helpers/crypto/__pycache__/hash.cpython-310.pyc +0 -0
- mojo/helpers/crypto/__pycache__/sign.cpython-310.pyc +0 -0
- mojo/helpers/crypto/__pycache__/utils.cpython-310.pyc +0 -0
- mojo/helpers/redis.py +0 -10
- mojo/models/meta.py +0 -262
- mojo/serializers/advanced/README.md +0 -363
- mojo/serializers/advanced/__init__.py +0 -247
- mojo/serializers/advanced/formats/__init__.py +0 -28
- mojo/serializers/advanced/formats/excel.py +0 -516
- mojo/serializers/advanced/formats/json.py +0 -239
- mojo/serializers/advanced/formats/response.py +0 -485
- mojo/serializers/advanced/serializer.py +0 -568
- mojo/serializers/optimized.py +0 -618
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/WHEEL +0 -0
- /mojo/apps/{notify → aws/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/handlers → docit/markdown_plugins}/__init__.py +0 -0
- /mojo/apps/{notify/management → docit/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/providers → jobs/examples}/__init__.py +0 -0
- /mojo/apps/{notify/rest → jobs/migrations}/__init__.py +0 -0
- /mojo/{serializers → rest}/openapi.py +0 -0
- /mojo/serializers/{settings_example.py → examples/settings.py} +0 -0
- /mojo/{apps/notify/handlers/ses/bounce.py → serializers/formats/__init__.py} +0 -0
- /mojo/serializers/{advanced/formats → formats}/localizers.py +0 -0
mojo/apps/tasks/tq_handlers.py
DELETED
@@ -1,132 +0,0 @@
|
|
1
|
-
from mojo.helpers import logit
|
2
|
-
import time
|
3
|
-
|
4
|
-
logger = logit.get_logger("ti_example", "ti_example.log")
|
5
|
-
|
6
|
-
def run_example_task(task):
|
7
|
-
logger.info("Running example task with data", task)
|
8
|
-
time.sleep(task.data.get("duration", 5))
|
9
|
-
|
10
|
-
|
11
|
-
def run_error_task(task):
|
12
|
-
logger.info("Running error task with data", task)
|
13
|
-
time.sleep(2)
|
14
|
-
raise Exception("Example error")
|
15
|
-
|
16
|
-
|
17
|
-
def run_quick_task(task):
|
18
|
-
"""Quick task for testing - completes immediately"""
|
19
|
-
logger.info("Running quick task with data", task)
|
20
|
-
return {"status": "completed", "data": task.data}
|
21
|
-
|
22
|
-
|
23
|
-
def run_slow_task(task):
|
24
|
-
"""Slow task for testing - takes 10 seconds"""
|
25
|
-
logger.info("Running slow task with data", task)
|
26
|
-
time.sleep(10)
|
27
|
-
return {"status": "completed", "duration": 10}
|
28
|
-
|
29
|
-
|
30
|
-
def run_args_kwargs_task(*args, **kwargs):
|
31
|
-
"""Task that receives args and kwargs directly"""
|
32
|
-
logger.info(f"Running args/kwargs task with args: {args}, kwargs: {kwargs}")
|
33
|
-
return {"args": args, "kwargs": kwargs}
|
34
|
-
|
35
|
-
|
36
|
-
def run_data_processing_task(task):
|
37
|
-
"""Task that processes data and returns results"""
|
38
|
-
logger.info("Running data processing task")
|
39
|
-
data = task.data
|
40
|
-
if not isinstance(data, dict):
|
41
|
-
raise ValueError("Data must be a dictionary")
|
42
|
-
|
43
|
-
result = {
|
44
|
-
"processed": True,
|
45
|
-
"input_keys": list(data.keys()),
|
46
|
-
"total_items": len(data)
|
47
|
-
}
|
48
|
-
return result
|
49
|
-
|
50
|
-
|
51
|
-
def run_counter_task(task):
|
52
|
-
"""Task that increments a counter - for testing state changes"""
|
53
|
-
logger.info("Running counter task")
|
54
|
-
count = task.data.get("count", 0)
|
55
|
-
new_count = count + 1
|
56
|
-
logger.info(f"Counter incremented from {count} to {new_count}")
|
57
|
-
return {"count": new_count}
|
58
|
-
|
59
|
-
|
60
|
-
def run_timeout_task(task):
|
61
|
-
"""Task that times out - for testing timeout scenarios"""
|
62
|
-
duration = task.data.get("duration", 60)
|
63
|
-
logger.info(f"Running timeout task for {duration} seconds")
|
64
|
-
time.sleep(duration)
|
65
|
-
return {"completed": True}
|
66
|
-
|
67
|
-
|
68
|
-
def run_memory_task(task):
|
69
|
-
"""Task that uses memory - for testing resource usage"""
|
70
|
-
logger.info("Running memory task")
|
71
|
-
size = task.data.get("size", 1000000) # 1MB default
|
72
|
-
data = bytearray(size)
|
73
|
-
logger.info(f"Allocated {size} bytes")
|
74
|
-
return {"allocated_bytes": size}
|
75
|
-
|
76
|
-
|
77
|
-
def run_conditional_error_task(task):
|
78
|
-
"""Task that conditionally raises an error based on input"""
|
79
|
-
logger.info("Running conditional error task")
|
80
|
-
should_error = task.data.get("should_error", False)
|
81
|
-
error_message = task.data.get("error_message", "Conditional error occurred")
|
82
|
-
|
83
|
-
if should_error:
|
84
|
-
raise Exception(error_message)
|
85
|
-
|
86
|
-
return {"status": "success", "should_error": should_error}
|
87
|
-
|
88
|
-
|
89
|
-
def run_nested_data_task(task):
|
90
|
-
"""Task that works with nested data structures"""
|
91
|
-
logger.info("Running nested data task")
|
92
|
-
data = task.data
|
93
|
-
|
94
|
-
if "nested" not in data:
|
95
|
-
raise ValueError("Missing 'nested' key in data")
|
96
|
-
|
97
|
-
nested = data["nested"]
|
98
|
-
result = {
|
99
|
-
"original": nested,
|
100
|
-
"keys": list(nested.keys()) if isinstance(nested, dict) else None,
|
101
|
-
"length": len(nested) if hasattr(nested, '__len__') else None
|
102
|
-
}
|
103
|
-
|
104
|
-
return result
|
105
|
-
|
106
|
-
|
107
|
-
# Test async task handlers
|
108
|
-
def async_quick_task(message="Hello"):
|
109
|
-
"""Async task handler for testing decorator"""
|
110
|
-
logger.info(f"Async quick task: {message}")
|
111
|
-
return f"Processed: {message}"
|
112
|
-
|
113
|
-
|
114
|
-
def async_slow_task(duration=5):
|
115
|
-
"""Async slow task handler for testing decorator"""
|
116
|
-
logger.info(f"Async slow task sleeping for {duration} seconds")
|
117
|
-
time.sleep(duration)
|
118
|
-
return f"Completed after {duration} seconds"
|
119
|
-
|
120
|
-
|
121
|
-
def async_error_task(should_error=True, message="Async error"):
|
122
|
-
"""Async error task handler for testing decorator"""
|
123
|
-
logger.info(f"Async error task - should_error: {should_error}")
|
124
|
-
if should_error:
|
125
|
-
raise Exception(message)
|
126
|
-
return "No error raised"
|
127
|
-
|
128
|
-
|
129
|
-
def async_args_task(*args, **kwargs):
|
130
|
-
"""Async task that tests args and kwargs handling"""
|
131
|
-
logger.info(f"Async args task - args: {args}, kwargs: {kwargs}")
|
132
|
-
return {"received_args": args, "received_kwargs": kwargs}
|
Binary file
|
Binary file
|
Binary file
|
mojo/helpers/redis.py
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
from redis import ConnectionPool, StrictRedis
|
2
|
-
from mojo.helpers.settings import settings
|
3
|
-
REDIS_POOL = None
|
4
|
-
|
5
|
-
|
6
|
-
def get_connection():
|
7
|
-
global REDIS_POOL
|
8
|
-
if REDIS_POOL is None:
|
9
|
-
REDIS_POOL = ConnectionPool(**settings.REDIS_DB)
|
10
|
-
return StrictRedis(connection_pool=REDIS_POOL)
|
mojo/models/meta.py
DELETED
@@ -1,262 +0,0 @@
|
|
1
|
-
from objict import objict
|
2
|
-
from django.db import models as dm
|
3
|
-
import string
|
4
|
-
from rest.encryption import ENCRYPTER, DECRYPTER
|
5
|
-
from datetime import datetime, date
|
6
|
-
|
7
|
-
class MetaDataBase(dm.Model):
|
8
|
-
class Meta:
|
9
|
-
abstract = True
|
10
|
-
|
11
|
-
category = dm.CharField(db_index=True, max_length=32, default=None, null=True, blank=True)
|
12
|
-
key = dm.CharField(db_index=True, max_length=80)
|
13
|
-
value_format = dm.CharField(max_length=16)
|
14
|
-
value = dm.TextField()
|
15
|
-
int_value = dm.IntegerField(default=None, null=True, blank=True)
|
16
|
-
float_value = dm.FloatField(default=None, null=True, blank=True)
|
17
|
-
|
18
|
-
def set_value(self, value):
|
19
|
-
self.value = str(value)
|
20
|
-
value_type = type(value)
|
21
|
-
|
22
|
-
if value_type is int or self.value in ["0", "1"]:
|
23
|
-
if value_type is int and value > 2147483647:
|
24
|
-
self.value_format = "S"
|
25
|
-
return
|
26
|
-
self.value_format = "I"
|
27
|
-
self.int_value = value
|
28
|
-
elif value_type is float:
|
29
|
-
self.value_format = "F"
|
30
|
-
self.float_value = value
|
31
|
-
elif isinstance(value, list):
|
32
|
-
self.value_format = "L"
|
33
|
-
elif isinstance(value, dict):
|
34
|
-
self.value_format = "O"
|
35
|
-
elif isinstance(value, str) and len(value) < 9 and value.isdigit():
|
36
|
-
self.value_format = "I"
|
37
|
-
self.int_value = int(value)
|
38
|
-
elif value in ["True", "true", "False", "false"]:
|
39
|
-
self.value_format = "I"
|
40
|
-
self.int_value = 1 if value.lower() == "true" else 0
|
41
|
-
elif isinstance(value, bool):
|
42
|
-
self.value_format = "I"
|
43
|
-
self.int_value = 1 if value else 0
|
44
|
-
else:
|
45
|
-
self.value_format = "S"
|
46
|
-
|
47
|
-
def get_strict_type(self, field_type):
|
48
|
-
try:
|
49
|
-
return field_type(self.value)
|
50
|
-
except (ValueError, TypeError):
|
51
|
-
if field_type is bool:
|
52
|
-
return self.int_value != 0 if self.value_format == 'I' else self.value.lower() in ['true', '1', 'y', 'yes']
|
53
|
-
elif field_type in [date, datetime]:
|
54
|
-
return rh.parseDate(self.value)
|
55
|
-
return self.value
|
56
|
-
|
57
|
-
def get_value(self, field_type=None):
|
58
|
-
if field_type:
|
59
|
-
return self.get_strict_type(field_type)
|
60
|
-
if self.value_format == 'I':
|
61
|
-
return self.int_value
|
62
|
-
elif self.value_format == 'F':
|
63
|
-
return self.float_value
|
64
|
-
elif self.value_format in ["L", "O"] and self.value:
|
65
|
-
try:
|
66
|
-
return eval(self.value)
|
67
|
-
except Exception:
|
68
|
-
pass
|
69
|
-
return self.value
|
70
|
-
|
71
|
-
def __str__(self):
|
72
|
-
return f"{self.category}.{self.key}={self.value}" if self.category else f"{self.key}={self.value}"
|
73
|
-
|
74
|
-
class MetaDataModel:
|
75
|
-
def set_metadata(self, request, values=None):
|
76
|
-
if not self.id:
|
77
|
-
self.save()
|
78
|
-
|
79
|
-
values = values or request
|
80
|
-
if isinstance(values, list):
|
81
|
-
values = objict({k: v for item in values if isinstance(item, dict) for k, v in item.items()})
|
82
|
-
|
83
|
-
if not isinstance(values, dict):
|
84
|
-
raise ValueError(f"invalid metadata: {values}")
|
85
|
-
|
86
|
-
for key, value in values.items():
|
87
|
-
cat, key = key.split('.', 1) if '.' in key else (None, key)
|
88
|
-
self.set_property(key, value, cat, request=request)
|
89
|
-
|
90
|
-
def metadata(self):
|
91
|
-
return self.get_properties()
|
92
|
-
|
93
|
-
def remove_properties(self, category=None):
|
94
|
-
self.properties.filter(category=category).delete()
|
95
|
-
|
96
|
-
def get_properties(self, category=None):
|
97
|
-
result = {}
|
98
|
-
for prop in self.properties.all():
|
99
|
-
category_ = prop.category
|
100
|
-
key = prop.key
|
101
|
-
|
102
|
-
if not category_:
|
103
|
-
self._add_property_to_result(result, prop)
|
104
|
-
continue
|
105
|
-
|
106
|
-
props = self.get_field_props(category_)
|
107
|
-
if props.hidden:
|
108
|
-
continue
|
109
|
-
|
110
|
-
if category_ not in result:
|
111
|
-
result[category_] = {}
|
112
|
-
|
113
|
-
if category_ == "secrets":
|
114
|
-
masked_value = "*" * prop.int_value if prop.int_value else "******"
|
115
|
-
result[category_][key] = masked_value
|
116
|
-
else:
|
117
|
-
self._add_property_to_result(result[category_], prop)
|
118
|
-
|
119
|
-
return result.get(category, {}) if category else result
|
120
|
-
|
121
|
-
def _add_property_to_result(self, result_dict, prop):
|
122
|
-
props = self.get_field_props(prop.key)
|
123
|
-
if not props.hidden:
|
124
|
-
result_dict[prop.key] = prop.get_value()
|
125
|
-
|
126
|
-
def get_field_props(self, key):
|
127
|
-
self._init_field_props()
|
128
|
-
category, key = key.split('.', 1) if '.' in key else (None, key)
|
129
|
-
props = objict()
|
130
|
-
|
131
|
-
if self.__field_props:
|
132
|
-
cat_props = self.__field_props.get(category, {})
|
133
|
-
self._update_props_with_category(props, cat_props)
|
134
|
-
|
135
|
-
field_props = self.__field_props.get(key, {})
|
136
|
-
self._update_props_with_field(props, field_props)
|
137
|
-
|
138
|
-
return props
|
139
|
-
|
140
|
-
def _update_props_with_category(self, props, cat_props):
|
141
|
-
if cat_props:
|
142
|
-
props.notify = cat_props.get("notify")
|
143
|
-
props.requires = cat_props.get("requires")
|
144
|
-
props.hidden = cat_props.get("hidden", False)
|
145
|
-
on_change_name = cat_props.get("on_change")
|
146
|
-
if on_change_name:
|
147
|
-
props.on_change = getattr(self, on_change_name, None)
|
148
|
-
|
149
|
-
def _update_props_with_field(self, props, field_props):
|
150
|
-
props.notify = field_props.get("notify", props.notify)
|
151
|
-
props.requires = field_props.get("requires", props.requires)
|
152
|
-
props.hidden = field_props.get("hidden", props.hidden)
|
153
|
-
on_change_name = field_props.get("on_change")
|
154
|
-
if on_change_name:
|
155
|
-
props.on_change = getattr(self, on_change_name, None)
|
156
|
-
|
157
|
-
def check_field_perms(self, full_key, props, request=None):
|
158
|
-
if not props.requires:
|
159
|
-
return True
|
160
|
-
if not request or not request.member:
|
161
|
-
return False
|
162
|
-
if request.member.hasPermission(props.requires) or request.user.is_superuser:
|
163
|
-
return True
|
164
|
-
|
165
|
-
if props.notify and request.member:
|
166
|
-
subject = f"permission denied changing protected '{full_key}' field"
|
167
|
-
msg = f"permission denied changing protected field '{full_key}'\nby user: {request.user.username}\nfor: {self}"
|
168
|
-
request.member.notifyWithPermission(props.notify, subject, msg, email_only=True)
|
169
|
-
raise re.PermissionDeniedException(subject, 481)
|
170
|
-
|
171
|
-
def set_properties(self, data, category=None, request=None, using=None):
|
172
|
-
for k, v in data.items():
|
173
|
-
self.set_property(k, v, category, request=request, using=using)
|
174
|
-
|
175
|
-
def set_property(self, key, value, category=None, request=None, using=None, ascii_only=False, encrypted=False):
|
176
|
-
if ascii_only and isinstance(value, str):
|
177
|
-
value = ''.join(filter(lambda x: x in string.printable, value))
|
178
|
-
|
179
|
-
if using is None:
|
180
|
-
using = getattr(self.RestMeta, "DATABASE", None)
|
181
|
-
|
182
|
-
if request is None:
|
183
|
-
request = rh.getActiveRequest()
|
184
|
-
|
185
|
-
self._init_field_props()
|
186
|
-
|
187
|
-
if '.' in key:
|
188
|
-
category, key = key.split('.', 1)
|
189
|
-
|
190
|
-
full_key = f"{category}.{key}" if category else key
|
191
|
-
field_props = self.get_field_props(full_key)
|
192
|
-
|
193
|
-
if not self.check_field_perms(full_key, field_props, request):
|
194
|
-
return False
|
195
|
-
|
196
|
-
prop = self.properties.filter(category=category, key=key).last()
|
197
|
-
if not prop and (value is None or value == ""):
|
198
|
-
return False
|
199
|
-
|
200
|
-
has_changed, old_value = self._update_or_create_property(prop, category, key, value, encrypted, using)
|
201
|
-
|
202
|
-
if has_changed and field_props.on_change:
|
203
|
-
field_props.on_change(key, value, old_value, category)
|
204
|
-
|
205
|
-
self._notify_change_if_required(field_props, full_key, value, request)
|
206
|
-
|
207
|
-
if hasattr(self, "_recordRestChange"):
|
208
|
-
self._recordRestChange(f"metadata.{full_key}", old_value)
|
209
|
-
|
210
|
-
return has_changed
|
211
|
-
|
212
|
-
def _update_or_create_property(self, prop, category, key, value, encrypted, using):
|
213
|
-
has_changed = False
|
214
|
-
old_value = None
|
215
|
-
|
216
|
-
value_len = len(value) if encrypted else 0
|
217
|
-
if encrypted:
|
218
|
-
value = ENCRYPTER.encrypt(value)
|
219
|
-
|
220
|
-
if prop:
|
221
|
-
old_value = prop.get_value()
|
222
|
-
if value is None or value == "":
|
223
|
-
self.properties.filter(category=category, key=key).delete()
|
224
|
-
has_changed = True
|
225
|
-
else:
|
226
|
-
has_changed = str(value) != prop.value
|
227
|
-
if has_changed:
|
228
|
-
prop.set_value(value)
|
229
|
-
if encrypted:
|
230
|
-
prop.int_value = value_len
|
231
|
-
prop.save(using=using)
|
232
|
-
else:
|
233
|
-
has_changed = True
|
234
|
-
PropClass = self.get_fk_model("properties")
|
235
|
-
prop = PropClass(parent=self, key=key, category=category)
|
236
|
-
prop.set_value(value)
|
237
|
-
prop.save(using=using)
|
238
|
-
|
239
|
-
return has_changed, old_value
|
240
|
-
|
241
|
-
def _notify_change_if_required(self, field_props, full_key, value, request):
|
242
|
-
if field_props.notify and request and request.member:
|
243
|
-
username = request.member.username if request and request.member else "root"
|
244
|
-
truncated_value = "***" if value and len(str(value)) > 5 else value
|
245
|
-
msg = (f"protected field '{full_key}' changed to '{truncated_value}'\n"
|
246
|
-
f"by user: {username}\nfor: {self}")
|
247
|
-
request.member.notifyWithPermission(field_props.notify, f"protected '{full_key}' field changed", msg, email_only=True)
|
248
|
-
|
249
|
-
def get_property(self, key, default=None, category=None, field_type=None, decrypted=False):
|
250
|
-
category, key = key.split('.', 1) if '.' in key else (category, key)
|
251
|
-
|
252
|
-
try:
|
253
|
-
prop_value = self.properties.get(category=category, key=key).get_value(field_type)
|
254
|
-
return DECRYPTER.decrypt(prop_value) if decrypted and prop_value else prop_value
|
255
|
-
except Exception:
|
256
|
-
return default
|
257
|
-
|
258
|
-
def set_secret_property(self, key, value):
|
259
|
-
return self.set_property(key, value, category="secrets", encrypted=True)
|
260
|
-
|
261
|
-
def get_secret_property(self, key, default=None):
|
262
|
-
return self.get_property(key, default, "secrets", decrypted=True)
|