django-nativemojo 0.1.10__py3-none-any.whl → 0.1.15__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/METADATA +136 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/RECORD +105 -65
- mojo/__init__.py +1 -1
- mojo/apps/account/management/__init__.py +5 -0
- mojo/apps/account/management/commands/__init__.py +6 -0
- mojo/apps/account/management/commands/serializer_admin.py +531 -0
- mojo/apps/account/migrations/0004_user_avatar.py +20 -0
- mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
- mojo/apps/account/models/group.py +25 -7
- mojo/apps/account/models/member.py +15 -4
- mojo/apps/account/models/user.py +197 -20
- mojo/apps/account/rest/group.py +1 -0
- mojo/apps/account/rest/user.py +6 -2
- mojo/apps/aws/rest/__init__.py +1 -0
- mojo/apps/aws/rest/s3.py +64 -0
- mojo/apps/fileman/README.md +8 -8
- mojo/apps/fileman/backends/base.py +76 -70
- mojo/apps/fileman/backends/filesystem.py +86 -86
- mojo/apps/fileman/backends/s3.py +200 -108
- mojo/apps/fileman/migrations/0001_initial.py +106 -0
- mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
- mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
- mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
- mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
- mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
- mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
- mojo/apps/fileman/migrations/0008_file_category.py +18 -0
- mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
- mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
- mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
- mojo/apps/fileman/models/__init__.py +1 -5
- mojo/apps/fileman/models/file.py +204 -58
- mojo/apps/fileman/models/manager.py +161 -31
- mojo/apps/fileman/models/rendition.py +118 -0
- mojo/apps/fileman/renderer/__init__.py +111 -0
- mojo/apps/fileman/renderer/audio.py +403 -0
- mojo/apps/fileman/renderer/base.py +205 -0
- mojo/apps/fileman/renderer/document.py +404 -0
- mojo/apps/fileman/renderer/image.py +222 -0
- mojo/apps/fileman/renderer/utils.py +297 -0
- mojo/apps/fileman/renderer/video.py +304 -0
- mojo/apps/fileman/rest/__init__.py +1 -18
- mojo/apps/fileman/rest/upload.py +22 -32
- mojo/apps/fileman/signals.py +58 -0
- mojo/apps/fileman/tasks.py +254 -0
- mojo/apps/fileman/utils/__init__.py +40 -16
- mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
- mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
- mojo/apps/incident/models/__init__.py +1 -0
- mojo/apps/incident/models/history.py +36 -0
- mojo/apps/incident/models/incident.py +1 -1
- mojo/apps/incident/reporter.py +3 -1
- mojo/apps/incident/rest/event.py +7 -1
- mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
- mojo/apps/logit/models/log.py +4 -1
- mojo/apps/metrics/utils.py +2 -2
- mojo/apps/notify/handlers/ses/message.py +1 -1
- mojo/apps/notify/providers/aws.py +2 -2
- mojo/apps/tasks/__init__.py +34 -1
- mojo/apps/tasks/manager.py +200 -45
- mojo/apps/tasks/rest/tasks.py +24 -10
- mojo/apps/tasks/runner.py +283 -18
- mojo/apps/tasks/task.py +99 -0
- mojo/apps/tasks/tq_handlers.py +118 -0
- mojo/decorators/auth.py +6 -1
- mojo/decorators/http.py +7 -2
- mojo/helpers/aws/__init__.py +41 -0
- mojo/helpers/aws/ec2.py +804 -0
- mojo/helpers/aws/iam.py +748 -0
- mojo/helpers/aws/s3.py +451 -11
- mojo/helpers/aws/ses.py +483 -0
- mojo/helpers/aws/sns.py +461 -0
- 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/dates.py +18 -0
- mojo/helpers/response.py +6 -2
- mojo/helpers/settings/__init__.py +2 -0
- mojo/helpers/{settings.py → settings/helper.py} +1 -37
- mojo/helpers/settings/parser.py +132 -0
- mojo/middleware/logging.py +1 -1
- mojo/middleware/mojo.py +5 -0
- mojo/models/rest.py +261 -46
- mojo/models/secrets.py +13 -4
- mojo/serializers/__init__.py +100 -0
- mojo/serializers/advanced/README.md +363 -0
- mojo/serializers/advanced/__init__.py +247 -0
- mojo/serializers/advanced/formats/__init__.py +28 -0
- mojo/serializers/advanced/formats/csv.py +416 -0
- mojo/serializers/advanced/formats/excel.py +516 -0
- mojo/serializers/advanced/formats/json.py +239 -0
- mojo/serializers/advanced/formats/localizers.py +509 -0
- mojo/serializers/advanced/formats/response.py +485 -0
- mojo/serializers/advanced/serializer.py +568 -0
- mojo/serializers/manager.py +501 -0
- mojo/serializers/optimized.py +618 -0
- mojo/serializers/settings_example.py +322 -0
- mojo/serializers/{models.py → simple.py} +38 -15
- testit/helpers.py +21 -4
- django_nativemojo-0.1.10.dist-info/METADATA +0 -96
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/ws4redis/README.md +0 -174
- mojo/ws4redis/__init__.py +0 -2
- mojo/ws4redis/client.py +0 -283
- mojo/ws4redis/connection.py +0 -327
- mojo/ws4redis/exceptions.py +0 -32
- mojo/ws4redis/redis.py +0 -183
- mojo/ws4redis/servers/base.py +0 -86
- mojo/ws4redis/servers/django.py +0 -171
- mojo/ws4redis/servers/uwsgi.py +0 -63
- mojo/ws4redis/settings.py +0 -45
- mojo/ws4redis/utf8validator.py +0 -128
- mojo/ws4redis/websocket.py +0 -403
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/WHEEL +0 -0
- /mojo/{ws4redis/servers → apps/aws}/__init__.py +0 -0
- /mojo/apps/{fileman/models/render.py → aws/models/__init__.py} +0 -0
- /mojo/apps/fileman/{rest/__init__ → migrations/__init__.py} +0 -0
@@ -8,101 +8,101 @@ class StorageBackend(ABC):
|
|
8
8
|
"""
|
9
9
|
Abstract base class for all storage backends
|
10
10
|
"""
|
11
|
-
|
11
|
+
|
12
12
|
def __init__(self, file_manager, **kwargs):
|
13
13
|
"""
|
14
14
|
Initialize the storage backend with a FileManager instance
|
15
|
-
|
15
|
+
|
16
16
|
Args:
|
17
17
|
file_manager: FileManager instance with configuration
|
18
18
|
**kwargs: Additional backend-specific configuration
|
19
19
|
"""
|
20
20
|
self.file_manager = file_manager
|
21
|
-
self.settings = file_manager.
|
21
|
+
self.settings = file_manager.primary_settings
|
22
22
|
self.backend_url = file_manager.backend_url
|
23
23
|
self.config = kwargs
|
24
|
-
|
24
|
+
|
25
25
|
@abstractmethod
|
26
|
-
def save(self, file_obj,
|
26
|
+
def save(self, file_obj, file_path: str, content_type: Optional[str] = None, metadata: Optional[dict] = None) -> str:
|
27
27
|
"""
|
28
28
|
Save a file to the storage backend
|
29
|
-
|
29
|
+
|
30
30
|
Args:
|
31
31
|
file_obj: File-like object to save
|
32
32
|
filename: Name to save the file as
|
33
33
|
**kwargs: Additional save options
|
34
|
-
|
34
|
+
|
35
35
|
Returns:
|
36
36
|
str: Full path to the saved file
|
37
37
|
"""
|
38
38
|
pass
|
39
|
-
|
39
|
+
|
40
40
|
@abstractmethod
|
41
41
|
def delete(self, file_path: str) -> bool:
|
42
42
|
"""
|
43
43
|
Delete a file from the storage backend
|
44
|
-
|
44
|
+
|
45
45
|
Args:
|
46
46
|
file_path: Path to the file to delete
|
47
|
-
|
47
|
+
|
48
48
|
Returns:
|
49
49
|
bool: True if deletion was successful
|
50
50
|
"""
|
51
51
|
pass
|
52
|
-
|
52
|
+
|
53
53
|
@abstractmethod
|
54
54
|
def exists(self, file_path: str) -> bool:
|
55
55
|
"""
|
56
56
|
Check if a file exists in the storage backend
|
57
|
-
|
57
|
+
|
58
58
|
Args:
|
59
59
|
file_path: Path to check
|
60
|
-
|
60
|
+
|
61
61
|
Returns:
|
62
62
|
bool: True if file exists
|
63
63
|
"""
|
64
64
|
pass
|
65
|
-
|
65
|
+
|
66
66
|
@abstractmethod
|
67
67
|
def get_file_size(self, file_path: str) -> Optional[int]:
|
68
68
|
"""
|
69
69
|
Get the size of a file in bytes
|
70
|
-
|
70
|
+
|
71
71
|
Args:
|
72
72
|
file_path: Path to the file
|
73
|
-
|
73
|
+
|
74
74
|
Returns:
|
75
75
|
Optional[int]: File size in bytes, None if file doesn't exist
|
76
76
|
"""
|
77
77
|
pass
|
78
|
-
|
78
|
+
|
79
79
|
@abstractmethod
|
80
80
|
def get_url(self, file_path: str, expires_in: Optional[int] = None) -> str:
|
81
81
|
"""
|
82
82
|
Get a URL to access the file
|
83
|
-
|
83
|
+
|
84
84
|
Args:
|
85
85
|
file_path: Path to the file
|
86
86
|
expires_in: Optional expiration time in seconds
|
87
|
-
|
87
|
+
|
88
88
|
Returns:
|
89
89
|
str: URL to access the file
|
90
90
|
"""
|
91
91
|
pass
|
92
|
-
|
92
|
+
|
93
93
|
@abstractmethod
|
94
|
-
def generate_upload_url(self, file_path: str, content_type: str,
|
95
|
-
file_size: Optional[int] = None,
|
94
|
+
def generate_upload_url(self, file_path: str, content_type: str,
|
95
|
+
file_size: Optional[int] = None,
|
96
96
|
expires_in: int = 3600) -> Dict[str, Any]:
|
97
97
|
"""
|
98
98
|
Generate a pre-signed URL for direct upload
|
99
|
-
|
99
|
+
|
100
100
|
Args:
|
101
101
|
file_path: Path where the file will be stored
|
102
102
|
content_type: MIME type of the file
|
103
103
|
file_size: Expected file size in bytes
|
104
104
|
expires_in: URL expiration time in seconds
|
105
|
-
|
105
|
+
|
106
106
|
Returns:
|
107
107
|
Dict containing:
|
108
108
|
- upload_url: Pre-signed upload URL
|
@@ -111,54 +111,54 @@ class StorageBackend(ABC):
|
|
111
111
|
- headers: Required headers (if any)
|
112
112
|
"""
|
113
113
|
pass
|
114
|
-
|
114
|
+
|
115
115
|
def supports_direct_upload(self) -> bool:
|
116
116
|
"""
|
117
117
|
Check if this backend supports direct uploads
|
118
|
-
|
118
|
+
|
119
119
|
Returns:
|
120
120
|
bool: True if direct uploads are supported
|
121
121
|
"""
|
122
122
|
return self.file_manager.supports_direct_upload
|
123
|
-
|
124
|
-
def validate_upload(self, file_path: str, upload_token: str,
|
123
|
+
|
124
|
+
def validate_upload(self, file_path: str, upload_token: Optional[str] = None,
|
125
125
|
expected_size: Optional[int] = None,
|
126
126
|
expected_checksum: Optional[str] = None) -> Tuple[bool, str]:
|
127
127
|
"""
|
128
128
|
Validate that an uploaded file matches expectations
|
129
|
-
|
129
|
+
|
130
130
|
Args:
|
131
131
|
file_path: Path to the uploaded file
|
132
132
|
upload_token: Token used for the upload
|
133
133
|
expected_size: Expected file size
|
134
134
|
expected_checksum: Expected file checksum
|
135
|
-
|
135
|
+
|
136
136
|
Returns:
|
137
137
|
Tuple[bool, str]: (is_valid, error_message)
|
138
138
|
"""
|
139
139
|
if not self.exists(file_path):
|
140
140
|
return False, "File does not exist after upload"
|
141
|
-
|
141
|
+
|
142
142
|
if expected_size:
|
143
143
|
actual_size = self.get_file_size(file_path)
|
144
144
|
if actual_size != expected_size:
|
145
145
|
return False, f"File size mismatch: expected {expected_size}, got {actual_size}"
|
146
|
-
|
146
|
+
|
147
147
|
if expected_checksum:
|
148
148
|
actual_checksum = self.get_file_checksum(file_path)
|
149
149
|
if actual_checksum != expected_checksum:
|
150
150
|
return False, f"Checksum mismatch: expected {expected_checksum}, got {actual_checksum}"
|
151
|
-
|
151
|
+
|
152
152
|
return True, "Upload validation successful"
|
153
|
-
|
153
|
+
|
154
154
|
def get_file_checksum(self, file_path: str, algorithm: str = 'md5') -> Optional[str]:
|
155
155
|
"""
|
156
156
|
Calculate checksum of a file
|
157
|
-
|
157
|
+
|
158
158
|
Args:
|
159
159
|
file_path: Path to the file
|
160
160
|
algorithm: Hash algorithm to use (md5, sha256, etc.)
|
161
|
-
|
161
|
+
|
162
162
|
Returns:
|
163
163
|
Optional[str]: File checksum, None if calculation fails
|
164
164
|
"""
|
@@ -166,45 +166,45 @@ class StorageBackend(ABC):
|
|
166
166
|
try:
|
167
167
|
import hashlib
|
168
168
|
hash_obj = hashlib.new(algorithm)
|
169
|
-
|
169
|
+
|
170
170
|
with self.open(file_path, 'rb') as f:
|
171
171
|
for chunk in iter(lambda: f.read(4096), b""):
|
172
172
|
hash_obj.update(chunk)
|
173
|
-
|
173
|
+
|
174
174
|
return hash_obj.hexdigest()
|
175
175
|
except Exception:
|
176
176
|
return None
|
177
|
-
|
177
|
+
|
178
178
|
def open(self, file_path: str, mode: str = 'rb'):
|
179
179
|
"""
|
180
180
|
Open a file from the storage backend
|
181
|
-
|
181
|
+
|
182
182
|
Args:
|
183
183
|
file_path: Path to the file
|
184
184
|
mode: File open mode
|
185
|
-
|
185
|
+
|
186
186
|
Returns:
|
187
187
|
File-like object
|
188
188
|
"""
|
189
189
|
raise NotImplementedError("Backend does not support file opening")
|
190
|
-
|
190
|
+
|
191
191
|
def generate_file_path(self, filename: str, group_id: Optional[int] = None) -> str:
|
192
192
|
"""
|
193
193
|
Generate a storage path for a file
|
194
|
-
|
194
|
+
|
195
195
|
Args:
|
196
196
|
filename: Original filename
|
197
197
|
group_id: Optional group ID for organization
|
198
|
-
|
198
|
+
|
199
199
|
Returns:
|
200
200
|
str: Generated file path
|
201
201
|
"""
|
202
202
|
# Default implementation - backends can override
|
203
203
|
parts = []
|
204
|
-
|
204
|
+
|
205
205
|
if group_id:
|
206
206
|
parts.append(f"group_{group_id}")
|
207
|
-
|
207
|
+
|
208
208
|
# Add date-based organization
|
209
209
|
now = datetime.now()
|
210
210
|
parts.extend([
|
@@ -212,58 +212,58 @@ class StorageBackend(ABC):
|
|
212
212
|
f"{now.month:02d}",
|
213
213
|
f"{now.day:02d}"
|
214
214
|
])
|
215
|
-
|
215
|
+
|
216
216
|
parts.append(filename)
|
217
217
|
return "/".join(parts)
|
218
|
-
|
218
|
+
|
219
219
|
def get_available_space(self) -> Optional[int]:
|
220
220
|
"""
|
221
221
|
Get available storage space in bytes
|
222
|
-
|
222
|
+
|
223
223
|
Returns:
|
224
224
|
Optional[int]: Available space in bytes, None if unlimited/unknown
|
225
225
|
"""
|
226
226
|
return None
|
227
|
-
|
227
|
+
|
228
228
|
def cleanup_expired_uploads(self, before_date: Optional[datetime] = None):
|
229
229
|
"""
|
230
230
|
Clean up expired upload URLs and temporary files
|
231
|
-
|
231
|
+
|
232
232
|
Args:
|
233
233
|
before_date: Clean up uploads before this date (default: now)
|
234
234
|
"""
|
235
235
|
# Default implementation does nothing - backends can override
|
236
236
|
pass
|
237
|
-
|
237
|
+
|
238
238
|
def get_file_metadata(self, file_path: str) -> Dict[str, Any]:
|
239
239
|
"""
|
240
240
|
Get metadata for a file
|
241
|
-
|
241
|
+
|
242
242
|
Args:
|
243
243
|
file_path: Path to the file
|
244
|
-
|
244
|
+
|
245
245
|
Returns:
|
246
246
|
Dict containing file metadata
|
247
247
|
"""
|
248
248
|
metadata = {}
|
249
|
-
|
249
|
+
|
250
250
|
if self.exists(file_path):
|
251
251
|
metadata['exists'] = True
|
252
252
|
metadata['size'] = self.get_file_size(file_path)
|
253
253
|
metadata['path'] = file_path
|
254
254
|
else:
|
255
255
|
metadata['exists'] = False
|
256
|
-
|
256
|
+
|
257
257
|
return metadata
|
258
|
-
|
258
|
+
|
259
259
|
def copy_file(self, source_path: str, dest_path: str) -> bool:
|
260
260
|
"""
|
261
261
|
Copy a file within the storage backend
|
262
|
-
|
262
|
+
|
263
263
|
Args:
|
264
264
|
source_path: Source file path
|
265
265
|
dest_path: Destination file path
|
266
|
-
|
266
|
+
|
267
267
|
Returns:
|
268
268
|
bool: True if copy was successful
|
269
269
|
"""
|
@@ -272,48 +272,54 @@ class StorageBackend(ABC):
|
|
272
272
|
return self.save(source, dest_path) is not None
|
273
273
|
except Exception:
|
274
274
|
return False
|
275
|
-
|
275
|
+
|
276
276
|
def move_file(self, source_path: str, dest_path: str) -> bool:
|
277
277
|
"""
|
278
278
|
Move a file within the storage backend
|
279
|
-
|
279
|
+
|
280
280
|
Args:
|
281
281
|
source_path: Source file path
|
282
282
|
dest_path: Destination file path
|
283
|
-
|
283
|
+
|
284
284
|
Returns:
|
285
285
|
bool: True if move was successful
|
286
286
|
"""
|
287
287
|
if self.copy_file(source_path, dest_path):
|
288
288
|
return self.delete(source_path)
|
289
289
|
return False
|
290
|
-
|
290
|
+
|
291
291
|
def list_files(self, path_prefix: str = "", limit: int = 1000) -> List[str]:
|
292
292
|
"""
|
293
293
|
List files with optional path prefix
|
294
|
-
|
294
|
+
|
295
295
|
Args:
|
296
296
|
path_prefix: Optional path prefix to filter by
|
297
297
|
limit: Maximum number of files to return
|
298
|
-
|
298
|
+
|
299
299
|
Returns:
|
300
300
|
List[str]: List of file paths
|
301
301
|
"""
|
302
302
|
# Default implementation returns empty list - backends should override
|
303
303
|
return []
|
304
|
-
|
304
|
+
|
305
305
|
def get_setting(self, key: str, default: Any = None) -> Any:
|
306
306
|
"""
|
307
307
|
Get a setting value from the file manager configuration
|
308
|
-
|
308
|
+
|
309
309
|
Args:
|
310
310
|
key: Setting key
|
311
311
|
default: Default value if key not found
|
312
|
-
|
312
|
+
|
313
313
|
Returns:
|
314
314
|
Setting value
|
315
315
|
"""
|
316
316
|
return self.settings.get(key, default)
|
317
|
-
|
317
|
+
|
318
|
+
def make_path_public(self):
|
319
|
+
return
|
320
|
+
|
321
|
+
def make_path_private(self):
|
322
|
+
return
|
323
|
+
|
318
324
|
def __str__(self):
|
319
|
-
return f"{self.__class__.__name__}({self.backend_url})"
|
325
|
+
return f"{self.__class__.__name__}({self.backend_url})"
|