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.
Files changed (120) hide show
  1. django_nativemojo-0.1.15.dist-info/METADATA +136 -0
  2. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/RECORD +105 -65
  3. mojo/__init__.py +1 -1
  4. mojo/apps/account/management/__init__.py +5 -0
  5. mojo/apps/account/management/commands/__init__.py +6 -0
  6. mojo/apps/account/management/commands/serializer_admin.py +531 -0
  7. mojo/apps/account/migrations/0004_user_avatar.py +20 -0
  8. mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
  9. mojo/apps/account/models/group.py +25 -7
  10. mojo/apps/account/models/member.py +15 -4
  11. mojo/apps/account/models/user.py +197 -20
  12. mojo/apps/account/rest/group.py +1 -0
  13. mojo/apps/account/rest/user.py +6 -2
  14. mojo/apps/aws/rest/__init__.py +1 -0
  15. mojo/apps/aws/rest/s3.py +64 -0
  16. mojo/apps/fileman/README.md +8 -8
  17. mojo/apps/fileman/backends/base.py +76 -70
  18. mojo/apps/fileman/backends/filesystem.py +86 -86
  19. mojo/apps/fileman/backends/s3.py +200 -108
  20. mojo/apps/fileman/migrations/0001_initial.py +106 -0
  21. mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
  22. mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
  23. mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
  24. mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
  25. mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
  26. mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
  27. mojo/apps/fileman/migrations/0008_file_category.py +18 -0
  28. mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
  29. mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
  30. mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
  31. mojo/apps/fileman/models/__init__.py +1 -5
  32. mojo/apps/fileman/models/file.py +204 -58
  33. mojo/apps/fileman/models/manager.py +161 -31
  34. mojo/apps/fileman/models/rendition.py +118 -0
  35. mojo/apps/fileman/renderer/__init__.py +111 -0
  36. mojo/apps/fileman/renderer/audio.py +403 -0
  37. mojo/apps/fileman/renderer/base.py +205 -0
  38. mojo/apps/fileman/renderer/document.py +404 -0
  39. mojo/apps/fileman/renderer/image.py +222 -0
  40. mojo/apps/fileman/renderer/utils.py +297 -0
  41. mojo/apps/fileman/renderer/video.py +304 -0
  42. mojo/apps/fileman/rest/__init__.py +1 -18
  43. mojo/apps/fileman/rest/upload.py +22 -32
  44. mojo/apps/fileman/signals.py +58 -0
  45. mojo/apps/fileman/tasks.py +254 -0
  46. mojo/apps/fileman/utils/__init__.py +40 -16
  47. mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
  48. mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
  49. mojo/apps/incident/models/__init__.py +1 -0
  50. mojo/apps/incident/models/history.py +36 -0
  51. mojo/apps/incident/models/incident.py +1 -1
  52. mojo/apps/incident/reporter.py +3 -1
  53. mojo/apps/incident/rest/event.py +7 -1
  54. mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
  55. mojo/apps/logit/models/log.py +4 -1
  56. mojo/apps/metrics/utils.py +2 -2
  57. mojo/apps/notify/handlers/ses/message.py +1 -1
  58. mojo/apps/notify/providers/aws.py +2 -2
  59. mojo/apps/tasks/__init__.py +34 -1
  60. mojo/apps/tasks/manager.py +200 -45
  61. mojo/apps/tasks/rest/tasks.py +24 -10
  62. mojo/apps/tasks/runner.py +283 -18
  63. mojo/apps/tasks/task.py +99 -0
  64. mojo/apps/tasks/tq_handlers.py +118 -0
  65. mojo/decorators/auth.py +6 -1
  66. mojo/decorators/http.py +7 -2
  67. mojo/helpers/aws/__init__.py +41 -0
  68. mojo/helpers/aws/ec2.py +804 -0
  69. mojo/helpers/aws/iam.py +748 -0
  70. mojo/helpers/aws/s3.py +451 -11
  71. mojo/helpers/aws/ses.py +483 -0
  72. mojo/helpers/aws/sns.py +461 -0
  73. mojo/helpers/crypto/__pycache__/hash.cpython-310.pyc +0 -0
  74. mojo/helpers/crypto/__pycache__/sign.cpython-310.pyc +0 -0
  75. mojo/helpers/crypto/__pycache__/utils.cpython-310.pyc +0 -0
  76. mojo/helpers/dates.py +18 -0
  77. mojo/helpers/response.py +6 -2
  78. mojo/helpers/settings/__init__.py +2 -0
  79. mojo/helpers/{settings.py → settings/helper.py} +1 -37
  80. mojo/helpers/settings/parser.py +132 -0
  81. mojo/middleware/logging.py +1 -1
  82. mojo/middleware/mojo.py +5 -0
  83. mojo/models/rest.py +261 -46
  84. mojo/models/secrets.py +13 -4
  85. mojo/serializers/__init__.py +100 -0
  86. mojo/serializers/advanced/README.md +363 -0
  87. mojo/serializers/advanced/__init__.py +247 -0
  88. mojo/serializers/advanced/formats/__init__.py +28 -0
  89. mojo/serializers/advanced/formats/csv.py +416 -0
  90. mojo/serializers/advanced/formats/excel.py +516 -0
  91. mojo/serializers/advanced/formats/json.py +239 -0
  92. mojo/serializers/advanced/formats/localizers.py +509 -0
  93. mojo/serializers/advanced/formats/response.py +485 -0
  94. mojo/serializers/advanced/serializer.py +568 -0
  95. mojo/serializers/manager.py +501 -0
  96. mojo/serializers/optimized.py +618 -0
  97. mojo/serializers/settings_example.py +322 -0
  98. mojo/serializers/{models.py → simple.py} +38 -15
  99. testit/helpers.py +21 -4
  100. django_nativemojo-0.1.10.dist-info/METADATA +0 -96
  101. mojo/apps/metrics/rest/db.py +0 -0
  102. mojo/helpers/aws/setup_email.py +0 -0
  103. mojo/ws4redis/README.md +0 -174
  104. mojo/ws4redis/__init__.py +0 -2
  105. mojo/ws4redis/client.py +0 -283
  106. mojo/ws4redis/connection.py +0 -327
  107. mojo/ws4redis/exceptions.py +0 -32
  108. mojo/ws4redis/redis.py +0 -183
  109. mojo/ws4redis/servers/base.py +0 -86
  110. mojo/ws4redis/servers/django.py +0 -171
  111. mojo/ws4redis/servers/uwsgi.py +0 -63
  112. mojo/ws4redis/settings.py +0 -45
  113. mojo/ws4redis/utf8validator.py +0 -128
  114. mojo/ws4redis/websocket.py +0 -403
  115. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/LICENSE +0 -0
  116. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/NOTICE +0 -0
  117. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.15.dist-info}/WHEEL +0 -0
  118. /mojo/{ws4redis/servers → apps/aws}/__init__.py +0 -0
  119. /mojo/apps/{fileman/models/render.py → aws/models/__init__.py} +0 -0
  120. /mojo/apps/fileman/{rest/__init__ → migrations/__init__.py} +0 -0
@@ -1,5 +1,7 @@
1
+ import os
1
2
  from django.db import models
2
3
  from mojo.models import MojoModel, MojoSecrets
4
+ from urllib.parse import urlparse
3
5
 
4
6
 
5
7
  class FileManager(MojoSecrets, MojoModel):
@@ -11,7 +13,7 @@ class FileManager(MojoSecrets, MojoModel):
11
13
  CAN_SAVE = CAN_CREATE = True
12
14
  CAN_DELETE = True
13
15
  DEFAULT_SORT = "-id"
14
- VIEW_PERMS = ["view_fileman"]
16
+ VIEW_PERMS = ["view_fileman", "manage_files"]
15
17
  SEARCH_FIELDS = ["name", "backend_type", "description"]
16
18
  SEARCH_TERMS = [
17
19
  "name", "backend_type", "description",
@@ -19,12 +21,19 @@ class FileManager(MojoSecrets, MojoModel):
19
21
 
20
22
  GRAPHS = {
21
23
  "default": {
24
+ "fields": [
25
+ "created", "id", "name", "backend_type", "backend_url",
26
+ "settings", "is_active", "is_default"],
22
27
  "graphs": {
28
+ "user": "basic",
23
29
  "group": "basic"
24
30
  }
25
31
  },
26
32
  "list": {
33
+ "fields": ["created", "id", "name", "backend_type", "backend_url",
34
+ "settings", "is_active", "is_default"],
27
35
  "graphs": {
36
+ "user": "basic",
28
37
  "group": "basic"
29
38
  }
30
39
  }
@@ -58,6 +67,16 @@ class FileManager(MojoSecrets, MojoModel):
58
67
  help_text="Group that owns this file manager configuration"
59
68
  )
60
69
 
70
+ user = models.ForeignKey(
71
+ "account.User",
72
+ related_name="file_managers",
73
+ null=True,
74
+ blank=True,
75
+ default=None,
76
+ on_delete=models.CASCADE,
77
+ help_text="User that owns this file manager configuration"
78
+ )
79
+
61
80
  name = models.CharField(
62
81
  max_length=255,
63
82
  db_index=True,
@@ -88,7 +107,7 @@ class FileManager(MojoSecrets, MojoModel):
88
107
  )
89
108
 
90
109
  max_file_size = models.BigIntegerField(
91
- default=100 * 1024 * 1024, # 100MB default
110
+ default=1000 * 1024 * 1024, # 100MB default
92
111
  help_text="Maximum file size in bytes (0 for unlimited)"
93
112
  )
94
113
 
@@ -114,6 +133,19 @@ class FileManager(MojoSecrets, MojoModel):
114
133
  help_text="Whether this is the default file manager for the group or user"
115
134
  )
116
135
 
136
+ is_public = models.BooleanField(
137
+ default=True,
138
+ help_text="Whether this allows public access to the files"
139
+ )
140
+
141
+ parent = models.ForeignKey(
142
+ 'self',
143
+ on_delete=models.CASCADE,
144
+ null=True,
145
+ blank=True,
146
+ help_text="Used if this file manager is a child of another file manager, and inherits settings from its parent"
147
+ )
148
+
117
149
  class Meta:
118
150
  unique_together = [
119
151
  ['group', 'name'],
@@ -131,16 +163,74 @@ class FileManager(MojoSecrets, MojoModel):
131
163
 
132
164
  def get_setting(self, key, default=None):
133
165
  """Get a specific setting value"""
134
- return self.get_secret(key, default)
166
+ value = self.get_secret(key, default)
167
+ if value is None:
168
+ value = self.primary_parent.get_secret(key, default)
169
+ return value
135
170
 
136
171
  def set_setting(self, key, value):
137
172
  """Set a specific setting value"""
138
173
  self.set_secret(key, value)
139
174
 
175
+ def set_settings(self, value):
176
+ """Set a specific setting value"""
177
+ self.set_secrets(value)
178
+
179
+ def set_backend_url(self, url, *args):
180
+ """Set the backend URL"""
181
+ self.backend_url = os.path.join(url, *args)
182
+ self.backend_type = self.backend_url.split(':')[0]
183
+
184
+ def _update_default(self):
185
+ if self.is_default:
186
+ if self.pk is None:
187
+ FileManager.objects.filter(
188
+ group=self.group,
189
+ user=self.user,
190
+ is_default=True
191
+ ).update(is_default=False)
192
+ else:
193
+ FileManager.objects.filter(
194
+ group=self.group,
195
+ user=self.user,
196
+ is_default=True
197
+ ).exclude(pk=self.pk).update(is_default=False)
198
+
199
+ _backend = None
200
+
201
+ @property
202
+ def backend(self):
203
+ """Get the backend instance"""
204
+ from mojo.apps.fileman import backends
205
+ if not self._backend:
206
+ self._backend = backends.get_backend(self)
207
+ return self._backend
208
+
140
209
  @property
141
210
  def settings(self):
142
211
  return self.secrets
143
212
 
213
+ @property
214
+ def primary_settings(self):
215
+ return self.primary_parent.secrets
216
+
217
+ @property
218
+ def primary_parent(self):
219
+ parent = self
220
+ while parent.parent:
221
+ parent = parent.parent
222
+ return parent
223
+
224
+ @property
225
+ def root_path(self):
226
+ purl = urlparse(self.backend_url)
227
+ return purl.path.lstrip('/')
228
+
229
+ @property
230
+ def root_location(self):
231
+ purl = urlparse(self.backend_url)
232
+ return purl.netloc
233
+
144
234
  @property
145
235
  def is_file_system(self):
146
236
  return self.backend_type == self.FILE_SYSTEM
@@ -185,43 +275,83 @@ class FileManager(MojoSecrets, MojoModel):
185
275
  return True
186
276
  return mime_type.lower() in [mt.lower() for mt in self.allowed_mime_types]
187
277
 
188
- def save(self, *args, **kwargs):
189
- """Custom save to enforce only one default per group"""
190
- if self.is_default:
191
- # Set all other file managers in the same group to not default
192
- FileManager.objects.filter(
193
- group=self.group,
194
- is_default=True
195
- ).exclude(pk=self.pk).update(is_default=False)
196
-
197
- super().save(*args, **kwargs)
278
+ def on_rest_created(self):
279
+ self._update_default()
280
+
281
+ def on_rest_pre_save(self, changed_fields, created):
282
+ self._update_default()
283
+ if not self.name:
284
+ self.name = self.generate_name()
285
+ if created or "is_default" in changed_fields:
286
+ self._update_default()
287
+
288
+ def on_rest_saved(self, changed_fields, created):
289
+ self._update_default()
290
+ if not self.name:
291
+ self.name = self.generate_name()
292
+ if "is_public" in changed_fields or created:
293
+ if self.is_public:
294
+ self.backend.make_path_public()
295
+ else:
296
+ self.backend.make_path_private()
297
+
298
+ def generate_name(self):
299
+ if self.user and self.group:
300
+ return f"{self.user.username}@{self.group.name}'s {self.backend_type} FileManager"
301
+ elif self.user:
302
+ return f"{self.user.username}'s {self.backend_type} FileManager"
303
+ elif self.group:
304
+ return f"{self.group.name}'s {self.backend_type} FileManager"
305
+ return f"{self.backend_type} FileManager"
198
306
 
199
307
  @classmethod
200
308
  def get_from_request(cls, request):
201
309
  """Get the file manager from the request"""
202
- if request.DATA.fileman:
203
- return cls.objects.get(pk=request.DATA.fileman)
204
- if request.DATA.use_groups_fileman:
310
+ if request.DATA.get(["fileman", "filemanager"]):
311
+ return cls.objects.get(pk=request.DATA.get(["fileman", "filemanager"]))
312
+ if request.DATA.use_groups_fileman and request.group:
205
313
  return cls.get_for_user_group(group=request.group)
206
314
  return cls.get_for_user_group(user=request.user, group=request.group)
207
315
 
208
316
  @classmethod
209
- def get_for_user_group(cls, user=None, group=None):
210
- """Get the file manager from the user and/or group"""
211
- file_manager = None
212
- if not file_manager and user:
213
- file_manager = cls.objects.filter(
214
- user=user, group=group, is_default=True
215
- ).first()
216
-
217
- if not file_manager and group:
218
- file_manager = cls.objects.filter(
219
- group=group, is_default=True
220
- ).first()
317
+ def get_for_user(cls, user, group=None):
318
+ file_manager = cls.objects.filter(
319
+ user=user, group=group, is_default=True, is_active=True
320
+ ).first()
321
+ if file_manager is None:
322
+ if group:
323
+ sys_manager = cls.get_for_group(group=group)
324
+ else:
325
+ sys_manager = cls.objects.filter(user=None, group=None, is_default=True, is_active=True).first()
326
+ if sys_manager is not None:
327
+ file_manager = cls(user=user, is_default=True, group=group, parent=sys_manager)
328
+ file_manager.set_backend_url(sys_manager.backend_url, user.uuid.hex)
329
+ file_manager.save()
330
+ return file_manager
221
331
 
222
- if not file_manager:
223
- file_manager = cls.objects.filter(
224
- group=None, user=None, is_default=True
332
+ @classmethod
333
+ def get_for_group(cls, group=None):
334
+ file_manager = cls.objects.filter(
335
+ user=None, group=group, is_default=True, is_active=True
336
+ ).first()
337
+ if file_manager is None:
338
+ sys_manager = cls.objects.filter(
339
+ user=None, group=None, is_default=True, is_active=True
225
340
  ).first()
341
+ if sys_manager is not None:
342
+ file_manager = cls(group=group, is_default=True, user=None, parent=sys_manager)
343
+ file_manager.set_backend_url(sys_manager.backend_url, group.uuid.hex)
344
+ file_manager.save()
345
+ return file_manager
226
346
 
347
+ @classmethod
348
+ def get_for_user_group(cls, user=None, group=None):
349
+ """Get the file manager from the user and/or group"""
350
+ file_manager = None
351
+ if user and group is None:
352
+ file_manager = cls.get_for_user(user=user)
353
+ if not file_manager and group and user is None:
354
+ file_manager = cls.get_for_user_group(group=group)
355
+ if not file_manager and group and user:
356
+ file_manager = cls.get_for_user(user=user, group=group)
227
357
  return file_manager
@@ -0,0 +1,118 @@
1
+ from django.db import models
2
+ from mojo.models import MojoModel
3
+ import uuid
4
+ import hashlib
5
+ import mimetypes
6
+ from datetime import datetime
7
+ import os
8
+ from mojo.apps.fileman import utils
9
+ from mojo.apps.fileman.models import FileManager
10
+ from typing import Text
11
+
12
+
13
+ class FileRendition(models.Model, MojoModel):
14
+ """
15
+ File model representing uploaded files with metadata and storage information
16
+ """
17
+
18
+ class RestMeta:
19
+ CAN_SAVE = CAN_CREATE = True
20
+ CAN_DELETE = True
21
+ DEFAULT_SORT = "-created"
22
+ VIEW_PERMS = ["view_fileman", "manage_files"]
23
+ SEARCH_FIELDS = ["filename", "content_type"]
24
+ SEARCH_TERMS = [
25
+ "filename", "content_type",
26
+ ("group", "group__name"),
27
+ ("file_manager", "file_manager__name")]
28
+
29
+ GRAPHS = {
30
+ "upload": {
31
+ "fields": ["id", "filename", "content_type", "file_size"],
32
+ },
33
+ "default": {
34
+ "extra": ["url"],
35
+ },
36
+ "list": {
37
+ "extra": ["url"],
38
+ }
39
+ }
40
+
41
+ # Upload status choices
42
+ PENDING = 'pending'
43
+ RENDERING = 'rendering'
44
+ COMPLETED = 'completed'
45
+ FAILED = 'failed'
46
+ EXPIRED = 'expired'
47
+
48
+ created = models.DateTimeField(auto_now_add=True, editable=False, db_index=True)
49
+ modified = models.DateTimeField(auto_now=True)
50
+
51
+ original_file = models.ForeignKey(
52
+ "fileman.File",
53
+ related_name="file_renditions",
54
+ on_delete=models.CASCADE,
55
+ help_text="The parent file"
56
+ )
57
+
58
+ filename = models.CharField(
59
+ max_length=255,
60
+ db_index=True,
61
+ help_text="rendition filename"
62
+ )
63
+
64
+ storage_path = models.TextField(
65
+ help_text="Storage path and filename",
66
+ )
67
+
68
+ download_url = models.TextField(
69
+ blank=True,
70
+ null=True,
71
+ default=None,
72
+ help_text="Persistent URL for downloading the file, (if allowed)"
73
+ )
74
+
75
+ file_size = models.BigIntegerField(
76
+ null=True,
77
+ blank=True,
78
+ help_text="File size in bytes"
79
+ )
80
+
81
+ content_type = models.CharField(
82
+ max_length=255,
83
+ help_text="MIME type of the file"
84
+ )
85
+
86
+ category = models.CharField(
87
+ max_length=255,
88
+ help_text="A category for the file, like 'image', 'document', 'video', etc."
89
+ )
90
+
91
+ role = models.CharField(
92
+ max_length=255,
93
+ db_index=True,
94
+ help_text="The role of the file, like 'thumbnail', 'preview', 'full', etc."
95
+ )
96
+
97
+ upload_status = models.CharField(
98
+ max_length=32,
99
+ default=PENDING,
100
+ db_index=True,
101
+ help_text="Current status of rendering"
102
+ )
103
+
104
+ @property
105
+ def file_manager(self):
106
+ return self.original_file.file_manager
107
+
108
+ @property
109
+ def url(self):
110
+ return self.generate_download_url()
111
+
112
+ def generate_download_url(self):
113
+ if self.download_url:
114
+ return self.download_url
115
+ if self.file_manager.is_public:
116
+ self.download_url = self.file_manager.backend.get_url(self.storage_path)
117
+ return self.download_url
118
+ return self.file_manager.backend.get_url(self.storage_path, self.get_setting("urls_expire_in", 3600))
@@ -0,0 +1,111 @@
1
+ import logging
2
+ from typing import Dict, List, Optional, Tuple, Any, Union
3
+
4
+ # Import renderer classes
5
+ from mojo.apps.fileman.models import File, FileRendition
6
+ from mojo.apps.fileman.renderer.base import BaseRenderer, RenditionRole
7
+ from mojo.apps.fileman.renderer.image import ImageRenderer
8
+ from mojo.apps.fileman.renderer.video import VideoRenderer
9
+ from mojo.apps.fileman.renderer.document import DocumentRenderer
10
+ from mojo.apps.fileman.renderer.audio import AudioRenderer
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # Register renderers in order of preference
15
+ RENDERERS = [
16
+ ImageRenderer,
17
+ VideoRenderer,
18
+ DocumentRenderer,
19
+ AudioRenderer,
20
+ ]
21
+
22
+ __all__ = [
23
+ 'BaseRenderer', 'RenditionRole', 'ImageRenderer', 'VideoRenderer', 'DocumentRenderer', 'AudioRenderer',
24
+ 'get_renderer_for_file', 'create_rendition', 'create_all_renditions',
25
+ 'get_rendition', 'get_or_create_rendition'
26
+ ]
27
+
28
+ def get_renderer_for_file(file: File) -> Optional[BaseRenderer]:
29
+ """
30
+ Get the appropriate renderer for a file based on its category
31
+
32
+ Args:
33
+ file: The file to get a renderer for
34
+
35
+ Returns:
36
+ BaseRenderer: The renderer instance, or None if no renderer supports the file
37
+ """
38
+ for renderer_class in RENDERERS:
39
+ if renderer_class.supports_file(file):
40
+ return renderer_class(file)
41
+ return None
42
+
43
+ def create_rendition(file: File, role: str, options: Dict = None) -> Optional[FileRendition]:
44
+ """
45
+ Create a rendition for a file
46
+
47
+ Args:
48
+ file: The file to create a rendition for
49
+ role: The role of the rendition (e.g., 'thumbnail', 'preview')
50
+ options: Additional options for creating the rendition
51
+
52
+ Returns:
53
+ FileRendition: The created rendition, or None if creation failed
54
+ """
55
+ renderer = get_renderer_for_file(file)
56
+ if not renderer:
57
+ logger.warning(f"No renderer available for file {file.id} ({file.filename}, {file.category})")
58
+ return None
59
+
60
+ return renderer.create_rendition(role, options)
61
+
62
+ def create_all_renditions(file: File) -> List[FileRendition]:
63
+ """
64
+ Create all default renditions for a file
65
+
66
+ Args:
67
+ file: The file to create renditions for
68
+
69
+ Returns:
70
+ List[FileRendition]: List of created renditions
71
+ """
72
+ renderer = get_renderer_for_file(file)
73
+ if not renderer:
74
+ logger.warning(f"No renderer available for file {file.id} ({file.filename}, {file.category})")
75
+ return []
76
+
77
+ return renderer.create_all_renditions()
78
+
79
+ def get_rendition(file: File, role: str) -> Optional[FileRendition]:
80
+ """
81
+ Get an existing rendition for a file
82
+
83
+ Args:
84
+ file: The file to get a rendition for
85
+ role: The role of the rendition
86
+
87
+ Returns:
88
+ FileRendition: The rendition, or None if not found
89
+ """
90
+ try:
91
+ return FileRendition.objects.get(original_file=file, role=role)
92
+ except FileRendition.DoesNotExist:
93
+ return None
94
+
95
+ def get_or_create_rendition(file: File, role: str, options: Dict = None) -> Optional[FileRendition]:
96
+ """
97
+ Get an existing rendition or create a new one if it doesn't exist
98
+
99
+ Args:
100
+ file: The file to get or create a rendition for
101
+ role: The role of the rendition
102
+ options: Additional options for creating the rendition
103
+
104
+ Returns:
105
+ FileRendition: The rendition, or None if not found and creation failed
106
+ """
107
+ rendition = get_rendition(file, role)
108
+ if rendition:
109
+ return rendition
110
+
111
+ return create_rendition(file, role, options)