django-nativemojo 0.1.10__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 (194) hide show
  1. django_nativemojo-0.1.10.dist-info/LICENSE +19 -0
  2. django_nativemojo-0.1.10.dist-info/METADATA +96 -0
  3. django_nativemojo-0.1.10.dist-info/NOTICE +8 -0
  4. django_nativemojo-0.1.10.dist-info/RECORD +194 -0
  5. django_nativemojo-0.1.10.dist-info/WHEEL +4 -0
  6. mojo/__init__.py +3 -0
  7. mojo/apps/account/__init__.py +1 -0
  8. mojo/apps/account/admin.py +91 -0
  9. mojo/apps/account/apps.py +16 -0
  10. mojo/apps/account/migrations/0001_initial.py +77 -0
  11. mojo/apps/account/migrations/0002_user_is_email_verified_user_is_phone_verified.py +23 -0
  12. mojo/apps/account/migrations/0003_group_mojo_secrets_user_mojo_secrets.py +23 -0
  13. mojo/apps/account/migrations/__init__.py +0 -0
  14. mojo/apps/account/models/__init__.py +3 -0
  15. mojo/apps/account/models/group.py +98 -0
  16. mojo/apps/account/models/member.py +95 -0
  17. mojo/apps/account/models/pkey.py +18 -0
  18. mojo/apps/account/models/user.py +211 -0
  19. mojo/apps/account/rest/__init__.py +3 -0
  20. mojo/apps/account/rest/group.py +25 -0
  21. mojo/apps/account/rest/user.py +47 -0
  22. mojo/apps/account/utils/__init__.py +0 -0
  23. mojo/apps/account/utils/jwtoken.py +72 -0
  24. mojo/apps/account/utils/passkeys.py +54 -0
  25. mojo/apps/fileman/README.md +549 -0
  26. mojo/apps/fileman/__init__.py +0 -0
  27. mojo/apps/fileman/apps.py +15 -0
  28. mojo/apps/fileman/backends/__init__.py +117 -0
  29. mojo/apps/fileman/backends/base.py +319 -0
  30. mojo/apps/fileman/backends/filesystem.py +397 -0
  31. mojo/apps/fileman/backends/s3.py +398 -0
  32. mojo/apps/fileman/examples/configurations.py +378 -0
  33. mojo/apps/fileman/examples/usage_example.py +665 -0
  34. mojo/apps/fileman/management/__init__.py +1 -0
  35. mojo/apps/fileman/management/commands/__init__.py +1 -0
  36. mojo/apps/fileman/management/commands/cleanup_expired_uploads.py +222 -0
  37. mojo/apps/fileman/models/__init__.py +7 -0
  38. mojo/apps/fileman/models/file.py +292 -0
  39. mojo/apps/fileman/models/manager.py +227 -0
  40. mojo/apps/fileman/models/render.py +0 -0
  41. mojo/apps/fileman/rest/__init__ +0 -0
  42. mojo/apps/fileman/rest/__init__.py +23 -0
  43. mojo/apps/fileman/rest/fileman.py +13 -0
  44. mojo/apps/fileman/rest/upload.py +92 -0
  45. mojo/apps/fileman/utils/__init__.py +19 -0
  46. mojo/apps/fileman/utils/upload.py +616 -0
  47. mojo/apps/incident/__init__.py +1 -0
  48. mojo/apps/incident/handlers/__init__.py +3 -0
  49. mojo/apps/incident/handlers/event_handlers.py +142 -0
  50. mojo/apps/incident/migrations/0001_initial.py +83 -0
  51. mojo/apps/incident/migrations/0002_rename_bundle_ruleset_bundle_minutes_event_hostname_and_more.py +44 -0
  52. mojo/apps/incident/migrations/0003_alter_event_model_id.py +18 -0
  53. mojo/apps/incident/migrations/0004_alter_incident_model_id.py +18 -0
  54. mojo/apps/incident/migrations/__init__.py +0 -0
  55. mojo/apps/incident/models/__init__.py +3 -0
  56. mojo/apps/incident/models/event.py +135 -0
  57. mojo/apps/incident/models/incident.py +33 -0
  58. mojo/apps/incident/models/rule.py +247 -0
  59. mojo/apps/incident/parsers/__init__.py +0 -0
  60. mojo/apps/incident/parsers/ossec/__init__.py +1 -0
  61. mojo/apps/incident/parsers/ossec/core.py +82 -0
  62. mojo/apps/incident/parsers/ossec/parsed.py +23 -0
  63. mojo/apps/incident/parsers/ossec/rules.py +124 -0
  64. mojo/apps/incident/parsers/ossec/utils.py +169 -0
  65. mojo/apps/incident/reporter.py +42 -0
  66. mojo/apps/incident/rest/__init__.py +2 -0
  67. mojo/apps/incident/rest/event.py +23 -0
  68. mojo/apps/incident/rest/ossec.py +22 -0
  69. mojo/apps/logit/__init__.py +0 -0
  70. mojo/apps/logit/admin.py +37 -0
  71. mojo/apps/logit/migrations/0001_initial.py +32 -0
  72. mojo/apps/logit/migrations/0002_log_duid_log_payload_log_username.py +28 -0
  73. mojo/apps/logit/migrations/0003_log_level.py +18 -0
  74. mojo/apps/logit/migrations/__init__.py +0 -0
  75. mojo/apps/logit/models/__init__.py +1 -0
  76. mojo/apps/logit/models/log.py +57 -0
  77. mojo/apps/logit/rest.py +9 -0
  78. mojo/apps/metrics/README.md +79 -0
  79. mojo/apps/metrics/__init__.py +12 -0
  80. mojo/apps/metrics/redis_metrics.py +331 -0
  81. mojo/apps/metrics/rest/__init__.py +1 -0
  82. mojo/apps/metrics/rest/base.py +152 -0
  83. mojo/apps/metrics/rest/db.py +0 -0
  84. mojo/apps/metrics/utils.py +227 -0
  85. mojo/apps/notify/README.md +91 -0
  86. mojo/apps/notify/README_NOTIFICATIONS.md +566 -0
  87. mojo/apps/notify/__init__.py +0 -0
  88. mojo/apps/notify/admin.py +52 -0
  89. mojo/apps/notify/handlers/__init__.py +0 -0
  90. mojo/apps/notify/handlers/example_handlers.py +516 -0
  91. mojo/apps/notify/handlers/ses/__init__.py +25 -0
  92. mojo/apps/notify/handlers/ses/bounce.py +0 -0
  93. mojo/apps/notify/handlers/ses/complaint.py +25 -0
  94. mojo/apps/notify/handlers/ses/message.py +86 -0
  95. mojo/apps/notify/management/__init__.py +0 -0
  96. mojo/apps/notify/management/commands/__init__.py +1 -0
  97. mojo/apps/notify/management/commands/process_notifications.py +370 -0
  98. mojo/apps/notify/mod +0 -0
  99. mojo/apps/notify/models/__init__.py +12 -0
  100. mojo/apps/notify/models/account.py +128 -0
  101. mojo/apps/notify/models/attachment.py +24 -0
  102. mojo/apps/notify/models/bounce.py +68 -0
  103. mojo/apps/notify/models/complaint.py +40 -0
  104. mojo/apps/notify/models/inbox.py +113 -0
  105. mojo/apps/notify/models/inbox_message.py +173 -0
  106. mojo/apps/notify/models/outbox.py +129 -0
  107. mojo/apps/notify/models/outbox_message.py +288 -0
  108. mojo/apps/notify/models/template.py +30 -0
  109. mojo/apps/notify/providers/__init__.py +0 -0
  110. mojo/apps/notify/providers/aws.py +73 -0
  111. mojo/apps/notify/rest/__init__.py +0 -0
  112. mojo/apps/notify/rest/ses.py +0 -0
  113. mojo/apps/notify/utils/__init__.py +2 -0
  114. mojo/apps/notify/utils/notifications.py +404 -0
  115. mojo/apps/notify/utils/parsing.py +202 -0
  116. mojo/apps/notify/utils/render.py +144 -0
  117. mojo/apps/tasks/README.md +118 -0
  118. mojo/apps/tasks/__init__.py +11 -0
  119. mojo/apps/tasks/manager.py +489 -0
  120. mojo/apps/tasks/rest/__init__.py +2 -0
  121. mojo/apps/tasks/rest/hooks.py +0 -0
  122. mojo/apps/tasks/rest/tasks.py +62 -0
  123. mojo/apps/tasks/runner.py +174 -0
  124. mojo/apps/tasks/tq_handlers.py +14 -0
  125. mojo/decorators/__init__.py +3 -0
  126. mojo/decorators/auth.py +25 -0
  127. mojo/decorators/cron.py +31 -0
  128. mojo/decorators/http.py +132 -0
  129. mojo/decorators/validate.py +14 -0
  130. mojo/errors.py +88 -0
  131. mojo/helpers/__init__.py +0 -0
  132. mojo/helpers/aws/__init__.py +0 -0
  133. mojo/helpers/aws/client.py +8 -0
  134. mojo/helpers/aws/s3.py +268 -0
  135. mojo/helpers/aws/setup_email.py +0 -0
  136. mojo/helpers/cron.py +79 -0
  137. mojo/helpers/crypto/__init__.py +4 -0
  138. mojo/helpers/crypto/aes.py +60 -0
  139. mojo/helpers/crypto/hash.py +59 -0
  140. mojo/helpers/crypto/privpub/__init__.py +1 -0
  141. mojo/helpers/crypto/privpub/hybrid.py +97 -0
  142. mojo/helpers/crypto/privpub/rsa.py +104 -0
  143. mojo/helpers/crypto/sign.py +36 -0
  144. mojo/helpers/crypto/too.l.py +25 -0
  145. mojo/helpers/crypto/utils.py +26 -0
  146. mojo/helpers/daemon.py +94 -0
  147. mojo/helpers/dates.py +69 -0
  148. mojo/helpers/dns/__init__.py +0 -0
  149. mojo/helpers/dns/godaddy.py +62 -0
  150. mojo/helpers/filetypes.py +128 -0
  151. mojo/helpers/logit.py +310 -0
  152. mojo/helpers/modules.py +95 -0
  153. mojo/helpers/paths.py +63 -0
  154. mojo/helpers/redis.py +10 -0
  155. mojo/helpers/request.py +89 -0
  156. mojo/helpers/request_parser.py +269 -0
  157. mojo/helpers/response.py +14 -0
  158. mojo/helpers/settings.py +146 -0
  159. mojo/helpers/sysinfo.py +140 -0
  160. mojo/helpers/ua.py +0 -0
  161. mojo/middleware/__init__.py +0 -0
  162. mojo/middleware/auth.py +26 -0
  163. mojo/middleware/logging.py +55 -0
  164. mojo/middleware/mojo.py +21 -0
  165. mojo/migrations/0001_initial.py +32 -0
  166. mojo/migrations/__init__.py +0 -0
  167. mojo/models/__init__.py +2 -0
  168. mojo/models/meta.py +262 -0
  169. mojo/models/rest.py +538 -0
  170. mojo/models/secrets.py +59 -0
  171. mojo/rest/__init__.py +1 -0
  172. mojo/rest/info.py +26 -0
  173. mojo/serializers/__init__.py +0 -0
  174. mojo/serializers/models.py +165 -0
  175. mojo/serializers/openapi.py +188 -0
  176. mojo/urls.py +38 -0
  177. mojo/ws4redis/README.md +174 -0
  178. mojo/ws4redis/__init__.py +2 -0
  179. mojo/ws4redis/client.py +283 -0
  180. mojo/ws4redis/connection.py +327 -0
  181. mojo/ws4redis/exceptions.py +32 -0
  182. mojo/ws4redis/redis.py +183 -0
  183. mojo/ws4redis/servers/__init__.py +0 -0
  184. mojo/ws4redis/servers/base.py +86 -0
  185. mojo/ws4redis/servers/django.py +171 -0
  186. mojo/ws4redis/servers/uwsgi.py +63 -0
  187. mojo/ws4redis/settings.py +45 -0
  188. mojo/ws4redis/utf8validator.py +128 -0
  189. mojo/ws4redis/websocket.py +403 -0
  190. testit/__init__.py +0 -0
  191. testit/client.py +147 -0
  192. testit/faker.py +20 -0
  193. testit/helpers.py +198 -0
  194. testit/runner.py +262 -0
@@ -0,0 +1,549 @@
1
+ # Django File Manager (fileman)
2
+
3
+ A comprehensive file management system for Django that supports multiple storage backends with direct upload capabilities to services like AWS S3, avoiding server bottlenecks.
4
+
5
+ ## Features
6
+
7
+ - **Multiple Storage Backends**: File system, AWS S3, Azure Blob, Google Cloud Storage
8
+ - **Direct Upload Support**: Generate pre-signed URLs for client-side uploads to cloud services
9
+ - **Flexible Configuration**: Per-group file managers with different backends and restrictions
10
+ - **File Validation**: Size limits, extension filtering, MIME type validation
11
+ - **Upload Lifecycle Management**: Track upload status from initiation to completion
12
+ - **Automatic Cleanup**: Management commands for cleaning expired uploads
13
+ - **Security**: Token-based uploads, access controls, encryption support
14
+
15
+ ## Quick Start
16
+
17
+ ### 1. Add to Django Settings
18
+
19
+ ```python
20
+ INSTALLED_APPS = [
21
+ # ... other apps
22
+ 'mojo.apps.fileman',
23
+ ]
24
+
25
+ # Optional: Configure default settings
26
+ FILEMAN_SETTINGS = {
27
+ 'DEFAULT_MAX_FILE_SIZE': 100 * 1024 * 1024, # 100MB
28
+ 'DEFAULT_UPLOAD_EXPIRES_IN': 3600, # 1 hour
29
+ 'CLEANUP_EXPIRED_UPLOADS': True,
30
+ }
31
+ ```
32
+
33
+ ### 2. Run Migrations
34
+
35
+ ```bash
36
+ python manage.py migrate fileman
37
+ ```
38
+
39
+ ### 3. Create a FileManager
40
+
41
+ ```python
42
+ from mojo.apps.fileman.models import FileManager
43
+ from mojo.apps.account.models import Group
44
+
45
+ # Create a file manager for S3
46
+ file_manager = FileManager.objects.create(
47
+ name="AWS S3 Storage",
48
+ backend_type="s3",
49
+ backend_url="s3://my-bucket/",
50
+ supports_direct_upload=True,
51
+ max_file_size=100 * 1024 * 1024, # 100MB
52
+ settings={
53
+ "bucket_name": "my-bucket",
54
+ "region_name": "us-east-1",
55
+ "access_key_id": "YOUR_ACCESS_KEY",
56
+ "secret_access_key": "YOUR_SECRET_KEY",
57
+ },
58
+ is_default=True,
59
+ is_active=True
60
+ )
61
+ ```
62
+
63
+ ## API Usage
64
+
65
+ ### 1. Initiate Upload
66
+
67
+ **POST** `/fileman/initiate-upload/`
68
+
69
+ ```javascript
70
+ const response = await fetch('/fileman/initiate-upload/', {
71
+ method: 'POST',
72
+ headers: {
73
+ 'Content-Type': 'application/json',
74
+ 'X-CSRFToken': csrfToken,
75
+ },
76
+ body: JSON.stringify({
77
+ files: [
78
+ {
79
+ filename: 'document.pdf',
80
+ content_type: 'application/pdf',
81
+ size: 1024000
82
+ },
83
+ {
84
+ filename: 'image.jpg',
85
+ content_type: 'image/jpeg',
86
+ size: 512000
87
+ }
88
+ ],
89
+ metadata: {
90
+ source: 'web_upload',
91
+ category: 'documents'
92
+ }
93
+ })
94
+ });
95
+
96
+ const data = await response.json();
97
+ ```
98
+
99
+ **Response:**
100
+ ```json
101
+ {
102
+ "success": true,
103
+ "files": [
104
+ {
105
+ "id": 123,
106
+ "filename": "document_20231201_abc12345.pdf",
107
+ "original_filename": "document.pdf",
108
+ "upload_token": "a1b2c3d4e5f6...",
109
+ "upload_url": "https://s3.amazonaws.com/my-bucket/...",
110
+ "method": "POST",
111
+ "fields": {
112
+ "key": "uploads/document_20231201_abc12345.pdf",
113
+ "policy": "eyJ...",
114
+ "x-amz-algorithm": "AWS4-HMAC-SHA256",
115
+ "x-amz-credential": "...",
116
+ "x-amz-date": "20231201T120000Z",
117
+ "x-amz-signature": "..."
118
+ },
119
+ "expires_at": "2023-12-01T13:00:00Z"
120
+ }
121
+ ],
122
+ "file_manager": {
123
+ "id": 1,
124
+ "name": "AWS S3 Storage",
125
+ "backend_type": "s3"
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### 2. Upload File to Pre-signed URL
131
+
132
+ ```javascript
133
+ // For S3 direct upload
134
+ const formData = new FormData();
135
+
136
+ // Add all the fields from the response
137
+ Object.entries(fileData.fields).forEach(([key, value]) => {
138
+ formData.append(key, value);
139
+ });
140
+
141
+ // Add the file last
142
+ formData.append('file', selectedFile);
143
+
144
+ // Upload directly to S3
145
+ const uploadResponse = await fetch(fileData.upload_url, {
146
+ method: fileData.method,
147
+ body: formData
148
+ });
149
+ ```
150
+
151
+ ### 3. Finalize Upload
152
+
153
+ **POST** `/fileman/finalize-upload/`
154
+
155
+ ```javascript
156
+ const finalizeResponse = await fetch('/fileman/finalize-upload/', {
157
+ method: 'POST',
158
+ headers: {
159
+ 'Content-Type': 'application/json',
160
+ 'X-CSRFToken': csrfToken,
161
+ },
162
+ body: JSON.stringify({
163
+ upload_token: fileData.upload_token,
164
+ file_size: selectedFile.size,
165
+ checksum: 'md5:' + calculatedMD5,
166
+ metadata: {
167
+ processing_complete: true
168
+ }
169
+ })
170
+ });
171
+
172
+ const result = await finalizeResponse.json();
173
+ ```
174
+
175
+ **Response:**
176
+ ```json
177
+ {
178
+ "success": true,
179
+ "file": {
180
+ "id": 123,
181
+ "filename": "document_20231201_abc12345.pdf",
182
+ "original_filename": "document.pdf",
183
+ "file_path": "uploads/document_20231201_abc12345.pdf",
184
+ "file_size": 1024000,
185
+ "content_type": "application/pdf",
186
+ "upload_status": "completed",
187
+ "download_url": "https://s3.amazonaws.com/my-bucket/...",
188
+ "checksum": "md5:abcdef123456...",
189
+ "metadata": {...},
190
+ "created": "2023-12-01T12:00:00Z"
191
+ }
192
+ }
193
+ ```
194
+
195
+ ## Storage Backend Configuration
196
+
197
+ ### AWS S3
198
+
199
+ ```python
200
+ {
201
+ "name": "AWS S3 Production",
202
+ "backend_type": "s3",
203
+ "backend_url": "s3://my-bucket/",
204
+ "supports_direct_upload": True,
205
+ "settings": {
206
+ "bucket_name": "my-bucket",
207
+ "region_name": "us-east-1",
208
+ "access_key_id": "AKIAIOSFODNN7EXAMPLE",
209
+ "secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
210
+ "server_side_encryption": "AES256",
211
+ "upload_expires_in": 3600,
212
+ "download_expires_in": 3600
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### Local File System
218
+
219
+ ```python
220
+ {
221
+ "name": "Local Storage",
222
+ "backend_type": "file",
223
+ "backend_url": "file:///app/media/uploads/",
224
+ "supports_direct_upload": False,
225
+ "settings": {
226
+ "base_path": "/app/media/uploads",
227
+ "base_url": "/media/uploads/",
228
+ "create_directories": True,
229
+ "permissions": 0o644,
230
+ "directory_permissions": 0o755
231
+ }
232
+ }
233
+ ```
234
+
235
+ ### S3-Compatible Services (MinIO, DigitalOcean Spaces)
236
+
237
+ ```python
238
+ {
239
+ "name": "DigitalOcean Spaces",
240
+ "backend_type": "s3",
241
+ "backend_url": "https://nyc3.digitaloceanspaces.com/my-space/",
242
+ "supports_direct_upload": True,
243
+ "settings": {
244
+ "bucket_name": "my-space",
245
+ "region_name": "nyc3",
246
+ "endpoint_url": "https://nyc3.digitaloceanspaces.com",
247
+ "access_key_id": "DO00EXAMPLE",
248
+ "secret_access_key": "EXAMPLE_SECRET_KEY"
249
+ }
250
+ }
251
+ ```
252
+
253
+ ## JavaScript Upload Helper
254
+
255
+ ```javascript
256
+ class FileUploader {
257
+ constructor(csrfToken) {
258
+ this.csrfToken = csrfToken;
259
+ }
260
+
261
+ async uploadFiles(files, options = {}) {
262
+ // 1. Initiate upload
263
+ const initResponse = await this.initiateUpload(files, options);
264
+ if (!initResponse.success) {
265
+ throw new Error('Failed to initiate upload');
266
+ }
267
+
268
+ // 2. Upload each file
269
+ const uploadPromises = initResponse.files.map(async (fileData, index) => {
270
+ const file = files[index];
271
+ await this.uploadFile(file, fileData);
272
+ return this.finalizeUpload(fileData.upload_token, file);
273
+ });
274
+
275
+ // 3. Wait for all uploads to complete
276
+ return Promise.all(uploadPromises);
277
+ }
278
+
279
+ async initiateUpload(files, options) {
280
+ const response = await fetch('/fileman/initiate-upload/', {
281
+ method: 'POST',
282
+ headers: {
283
+ 'Content-Type': 'application/json',
284
+ 'X-CSRFToken': this.csrfToken,
285
+ },
286
+ body: JSON.stringify({
287
+ files: files.map(file => ({
288
+ filename: file.name,
289
+ content_type: file.type,
290
+ size: file.size
291
+ })),
292
+ ...options
293
+ })
294
+ });
295
+ return response.json();
296
+ }
297
+
298
+ async uploadFile(file, uploadData) {
299
+ const formData = new FormData();
300
+
301
+ // Add fields for S3 or other backends
302
+ Object.entries(uploadData.fields || {}).forEach(([key, value]) => {
303
+ formData.append(key, value);
304
+ });
305
+
306
+ formData.append('file', file);
307
+
308
+ const response = await fetch(uploadData.upload_url, {
309
+ method: uploadData.method,
310
+ body: formData
311
+ });
312
+
313
+ if (!response.ok) {
314
+ throw new Error(`Upload failed: ${response.statusText}`);
315
+ }
316
+ }
317
+
318
+ async finalizeUpload(uploadToken, file) {
319
+ const response = await fetch('/fileman/finalize-upload/', {
320
+ method: 'POST',
321
+ headers: {
322
+ 'Content-Type': 'application/json',
323
+ 'X-CSRFToken': this.csrfToken,
324
+ },
325
+ body: JSON.stringify({
326
+ upload_token: uploadToken,
327
+ file_size: file.size
328
+ })
329
+ });
330
+ return response.json();
331
+ }
332
+ }
333
+
334
+ // Usage
335
+ const uploader = new FileUploader(document.querySelector('[name=csrfmiddlewaretoken]').value);
336
+ const fileInput = document.getElementById('file-input');
337
+
338
+ fileInput.addEventListener('change', async (e) => {
339
+ const files = Array.from(e.target.files);
340
+ try {
341
+ const results = await uploader.uploadFiles(files, {
342
+ metadata: { source: 'web_form' }
343
+ });
344
+ console.log('Upload completed:', results);
345
+ } catch (error) {
346
+ console.error('Upload failed:', error);
347
+ }
348
+ });
349
+ ```
350
+
351
+ ## File Access and Downloads
352
+
353
+ ### Download Files
354
+
355
+ ```python
356
+ from mojo.apps.fileman.models import File
357
+ from mojo.apps.fileman.backends import get_backend
358
+
359
+ # Get file by upload token
360
+ file_obj = File.objects.get(upload_token='abc123...', upload_status=File.COMPLETED)
361
+
362
+ # Generate download URL
363
+ backend = get_backend(file_obj.file_manager)
364
+ download_url = backend.get_url(file_obj.file_path, expires_in=3600)
365
+
366
+ # Or use the built-in download view
367
+ # /fileman/download/abc123.../
368
+ ```
369
+
370
+ ### Check File Access Permissions
371
+
372
+ ```python
373
+ # Check if user can access file
374
+ can_access = file_obj.can_be_accessed_by(user=request.user, group=user_group)
375
+
376
+ # Public files
377
+ file_obj.is_public = True
378
+ file_obj.save()
379
+ ```
380
+
381
+ ## Management Commands
382
+
383
+ ### Clean Up Expired Uploads
384
+
385
+ ```bash
386
+ # Clean up uploads older than 1 day (default)
387
+ python manage.py cleanup_expired_uploads
388
+
389
+ # Clean up uploads older than 6 hours
390
+ python manage.py cleanup_expired_uploads --hours 6
391
+
392
+ # Dry run to see what would be cleaned
393
+ python manage.py cleanup_expired_uploads --dry-run
394
+
395
+ # Clean up only failed uploads
396
+ python manage.py cleanup_expired_uploads --status failed
397
+
398
+ # Verbose output
399
+ python manage.py cleanup_expired_uploads --verbose
400
+ ```
401
+
402
+ ## File Validation
403
+
404
+ ### Configure File Restrictions
405
+
406
+ ```python
407
+ file_manager = FileManager.objects.create(
408
+ name="Image Storage",
409
+ backend_type="s3",
410
+ max_file_size=10 * 1024 * 1024, # 10MB
411
+ allowed_extensions=["jpg", "jpeg", "png", "gif", "webp"],
412
+ allowed_mime_types=[
413
+ "image/jpeg",
414
+ "image/png",
415
+ "image/gif",
416
+ "image/webp"
417
+ ],
418
+ # ... other settings
419
+ )
420
+ ```
421
+
422
+ ### Custom Validation
423
+
424
+ ```python
425
+ from mojo.apps.fileman.models import File
426
+
427
+ # Custom validation in your views
428
+ def validate_upload(request, file_data):
429
+ # Custom business logic
430
+ if file_data['filename'].startswith('temp_'):
431
+ raise ValidationError('Temporary files not allowed')
432
+
433
+ # Check file size against user's quota
434
+ user_files_size = File.objects.filter(
435
+ uploaded_by=request.user,
436
+ upload_status=File.COMPLETED
437
+ ).aggregate(total=Sum('file_size'))['total'] or 0
438
+
439
+ if user_files_size + file_data['size'] > USER_QUOTA:
440
+ raise ValidationError('Upload would exceed user quota')
441
+ ```
442
+
443
+ ## Security Considerations
444
+
445
+ ### Environment Variables
446
+
447
+ ```bash
448
+ # Production environment variables
449
+ export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
450
+ export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
451
+ export AWS_DEFAULT_REGION="us-east-1"
452
+ export KMS_KEY_ID="arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
453
+ ```
454
+
455
+ ### IAM Policy for S3
456
+
457
+ ```json
458
+ {
459
+ "Version": "2012-10-17",
460
+ "Statement": [
461
+ {
462
+ "Effect": "Allow",
463
+ "Action": [
464
+ "s3:GetObject",
465
+ "s3:PutObject",
466
+ "s3:DeleteObject"
467
+ ],
468
+ "Resource": "arn:aws:s3:::my-bucket/*"
469
+ },
470
+ {
471
+ "Effect": "Allow",
472
+ "Action": [
473
+ "s3:ListBucket"
474
+ ],
475
+ "Resource": "arn:aws:s3:::my-bucket"
476
+ }
477
+ ]
478
+ }
479
+ ```
480
+
481
+ ### CORS Configuration for S3
482
+
483
+ ```json
484
+ [
485
+ {
486
+ "AllowedHeaders": ["*"],
487
+ "AllowedMethods": ["GET", "POST", "PUT"],
488
+ "AllowedOrigins": ["https://yourdomain.com"],
489
+ "ExposeHeaders": ["ETag"],
490
+ "MaxAgeSeconds": 3000
491
+ }
492
+ ]
493
+ ```
494
+
495
+ ## Troubleshooting
496
+
497
+ ### Common Issues
498
+
499
+ 1. **Upload URL Expired**
500
+ - Check `upload_expires_in` setting
501
+ - Ensure client uploads immediately after getting URL
502
+
503
+ 2. **CORS Errors on S3**
504
+ - Configure CORS policy on S3 bucket
505
+ - Ensure your domain is in AllowedOrigins
506
+
507
+ 3. **File Not Found After Upload**
508
+ - Check that finalize-upload was called successfully
509
+ - Verify file exists in storage backend
510
+
511
+ 4. **Permission Denied Errors**
512
+ - Check IAM policies for S3
513
+ - Verify file system permissions for local storage
514
+
515
+ 5. **Large File Upload Failures**
516
+ - Increase `max_file_size` setting
517
+ - Consider enabling S3 multipart uploads for large files
518
+
519
+ ### Debug Mode
520
+
521
+ ```python
522
+ # Enable debug logging
523
+ import logging
524
+ logging.getLogger('mojo.apps.fileman').setLevel(logging.DEBUG)
525
+
526
+ # Check file status
527
+ file_obj = File.objects.get(upload_token='abc123...')
528
+ print(f"Status: {file_obj.upload_status}")
529
+ print(f"Path: {file_obj.file_path}")
530
+ print(f"Metadata: {file_obj.metadata}")
531
+
532
+ # Test backend connection
533
+ from mojo.apps.fileman.backends import get_backend
534
+ backend = get_backend(file_obj.file_manager)
535
+ is_valid, errors = backend.validate_configuration()
536
+ print(f"Backend valid: {is_valid}, Errors: {errors}")
537
+ ```
538
+
539
+ ## Contributing
540
+
541
+ 1. Fork the repository
542
+ 2. Create a feature branch
543
+ 3. Add tests for new functionality
544
+ 4. Ensure all tests pass
545
+ 5. Submit a pull request
546
+
547
+ ## License
548
+
549
+ This project is licensed under the MIT License - see the LICENSE file for details.
File without changes
@@ -0,0 +1,15 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class FilemanConfig(AppConfig):
5
+ default_auto_field = 'django.db.models.BigAutoField'
6
+ name = 'mojo.apps.fileman'
7
+ verbose_name = 'File Manager'
8
+
9
+ def ready(self):
10
+ """
11
+ Perform initialization tasks when the app is ready
12
+ """
13
+ # Import signal handlers if any
14
+ # from . import signals
15
+ pass
@@ -0,0 +1,117 @@
1
+ from typing import Dict, Type
2
+ from .base import StorageBackend
3
+ from .filesystem import FileSystemStorageBackend
4
+ from .s3 import S3StorageBackend
5
+
6
+
7
+ # Registry of available storage backends
8
+ BACKEND_REGISTRY: Dict[str, Type[StorageBackend]] = {
9
+ 'file': FileSystemStorageBackend,
10
+ 's3': S3StorageBackend,
11
+ }
12
+
13
+
14
+ def register_backend(backend_type: str, backend_class: Type[StorageBackend]):
15
+ """
16
+ Register a custom storage backend
17
+
18
+ Args:
19
+ backend_type: Backend type identifier
20
+ backend_class: Backend class that inherits from StorageBackend
21
+ """
22
+ if not issubclass(backend_class, StorageBackend):
23
+ raise ValueError("Backend class must inherit from StorageBackend")
24
+
25
+ BACKEND_REGISTRY[backend_type] = backend_class
26
+
27
+
28
+ def get_backend(file_manager, **kwargs) -> StorageBackend:
29
+ """
30
+ Get a storage backend instance for the given FileManager
31
+
32
+ Args:
33
+ file_manager: FileManager instance with backend configuration
34
+ **kwargs: Additional backend-specific configuration
35
+
36
+ Returns:
37
+ StorageBackend: Configured storage backend instance
38
+
39
+ Raises:
40
+ ValueError: If backend type is not supported
41
+ Exception: If backend initialization fails
42
+ """
43
+ backend_type = file_manager.backend_type
44
+
45
+ if backend_type not in BACKEND_REGISTRY:
46
+ available_backends = ', '.join(BACKEND_REGISTRY.keys())
47
+ raise ValueError(
48
+ f"Unsupported backend type '{backend_type}'. "
49
+ f"Available backends: {available_backends}"
50
+ )
51
+
52
+ backend_class = BACKEND_REGISTRY[backend_type]
53
+
54
+ try:
55
+ return backend_class(file_manager, **kwargs)
56
+ except Exception as e:
57
+ raise Exception(f"Failed to initialize {backend_type} backend: {e}")
58
+
59
+
60
+ def get_available_backends() -> Dict[str, Type[StorageBackend]]:
61
+ """
62
+ Get all available storage backends
63
+
64
+ Returns:
65
+ Dict mapping backend type to backend class
66
+ """
67
+ return BACKEND_REGISTRY.copy()
68
+
69
+
70
+ def is_backend_supported(backend_type: str) -> bool:
71
+ """
72
+ Check if a backend type is supported
73
+
74
+ Args:
75
+ backend_type: Backend type to check
76
+
77
+ Returns:
78
+ bool: True if backend is supported
79
+ """
80
+ return backend_type in BACKEND_REGISTRY
81
+
82
+
83
+ def validate_backend_config(file_manager) -> tuple[bool, list[str]]:
84
+ """
85
+ Validate backend configuration for a FileManager
86
+
87
+ Args:
88
+ file_manager: FileManager instance to validate
89
+
90
+ Returns:
91
+ tuple: (is_valid, list_of_errors)
92
+ """
93
+ try:
94
+ backend = get_backend(file_manager)
95
+
96
+ # Check if backend has a validate_configuration method
97
+ if hasattr(backend, 'validate_configuration') and callable(getattr(backend, 'validate_configuration')):
98
+ return backend.validate_configuration()
99
+ else:
100
+ # Basic validation - just check if we can create the backend
101
+ return True, []
102
+
103
+ except Exception as e:
104
+ return False, [str(e)]
105
+
106
+
107
+ __all__ = [
108
+ 'StorageBackend',
109
+ 'FileSystemStorageBackend',
110
+ 'S3StorageBackend',
111
+ 'BACKEND_REGISTRY',
112
+ 'register_backend',
113
+ 'get_backend',
114
+ 'get_available_backends',
115
+ 'is_backend_supported',
116
+ 'validate_backend_config',
117
+ ]