django-nativemojo 0.1.10__py3-none-any.whl → 0.1.16__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.16.dist-info/METADATA +138 -0
- django_nativemojo-0.1.16.dist-info/RECORD +302 -0
- 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 +651 -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/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 +281 -0
- mojo/apps/account/models/group.py +319 -15
- mojo/apps/account/models/member.py +29 -5
- 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 +369 -19
- mojo/apps/account/rest/__init__.py +2 -0
- mojo/apps/account/rest/device.py +39 -0
- mojo/apps/account/rest/group.py +9 -0
- mojo/apps/account/rest/push.py +187 -0
- mojo/apps/account/rest/user.py +100 -6
- 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 +7 -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/s3.py +64 -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/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 +409 -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 +240 -58
- mojo/apps/fileman/models/manager.py +427 -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/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 +2 -0
- mojo/apps/incident/models/event.py +35 -0
- mojo/apps/incident/models/history.py +36 -0
- mojo/apps/incident/models/incident.py +3 -1
- mojo/apps/incident/models/ticket.py +62 -0
- mojo/apps/incident/reporter.py +21 -1
- mojo/apps/incident/rest/__init__.py +1 -0
- mojo/apps/incident/rest/event.py +7 -1
- 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/migrations/0004_alter_log_level.py +18 -0
- mojo/apps/logit/models/log.py +7 -1
- 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 +19 -2
- mojo/decorators/auth.py +6 -1
- mojo/decorators/http.py +47 -3
- mojo/helpers/aws/__init__.py +45 -0
- mojo/helpers/aws/ec2.py +804 -0
- mojo/helpers/aws/iam.py +748 -0
- mojo/helpers/aws/inbound_email.py +309 -0
- mojo/helpers/aws/kms.py +413 -0
- mojo/helpers/aws/s3.py +451 -11
- mojo/helpers/aws/ses.py +483 -0
- mojo/helpers/aws/ses_domain.py +959 -0
- mojo/helpers/aws/sns.py +461 -0
- mojo/helpers/crypto/__init__.py +1 -1
- mojo/helpers/crypto/utils.py +15 -0
- mojo/helpers/dates.py +18 -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 +14 -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/auth.py +1 -1
- mojo/middleware/cors.py +40 -0
- mojo/middleware/logging.py +131 -12
- mojo/middleware/mojo.py +10 -0
- mojo/models/rest.py +494 -65
- mojo/models/secrets.py +98 -3
- mojo/serializers/__init__.py +106 -0
- 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/core/manager.py +550 -0
- mojo/serializers/core/serializer.py +475 -0
- mojo/serializers/examples/settings.py +322 -0
- mojo/serializers/formats/csv.py +393 -0
- mojo/serializers/formats/localizers.py +509 -0
- mojo/serializers/{models.py → simple.py} +38 -15
- mojo/serializers/suggested_improvements.md +388 -0
- testit/client.py +1 -1
- testit/helpers.py +35 -4
- testit/runner.py +23 -6
- django_nativemojo-0.1.10.dist-info/METADATA +0 -96
- django_nativemojo-0.1.10.dist-info/RECORD +0 -194
- mojo/apps/metrics/rest/db.py +0 -0
- 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/bounce.py +0 -0
- 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 -11
- mojo/apps/tasks/manager.py +0 -489
- mojo/apps/tasks/rest/__init__.py +0 -2
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +0 -62
- mojo/apps/tasks/runner.py +0 -174
- mojo/apps/tasks/tq_handlers.py +0 -14
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/redis.py +0 -10
- mojo/models/meta.py +0 -262
- 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.16.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
- /mojo/apps/{notify → aws}/__init__.py +0 -0
- /mojo/apps/{notify/handlers → aws/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/management → docit/markdown_plugins}/__init__.py +0 -0
- /mojo/apps/{notify/providers → docit/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/rest → fileman/migrations}/__init__.py +0 -0
- /mojo/{ws4redis/servers → apps/jobs/examples}/__init__.py +0 -0
- /mojo/apps/{fileman/models/render.py → jobs/migrations/__init__.py} +0 -0
- /mojo/{serializers → rest}/openapi.py +0 -0
- /mojo/{apps/fileman/rest/__init__ → serializers/formats/__init__.py} +0 -0
@@ -0,0 +1,315 @@
|
|
1
|
+
from mojo.helpers import logit
|
2
|
+
from ..models import Book, Page, PageRevision, Asset
|
3
|
+
|
4
|
+
|
5
|
+
class DocItService:
|
6
|
+
"""
|
7
|
+
Business logic service for DocIt operations
|
8
|
+
|
9
|
+
Handles complex operations that span multiple models or contain
|
10
|
+
business logic that doesn't belong in individual model methods.
|
11
|
+
"""
|
12
|
+
|
13
|
+
@staticmethod
|
14
|
+
def create_book_with_homepage(title, description, group, user, homepage_title="Home"):
|
15
|
+
"""
|
16
|
+
Create a new book with an initial homepage
|
17
|
+
|
18
|
+
This is a common pattern where new books should have at least one page
|
19
|
+
"""
|
20
|
+
try:
|
21
|
+
# Create the book
|
22
|
+
book = Book.objects.create(
|
23
|
+
title=title,
|
24
|
+
description=description,
|
25
|
+
group=group,
|
26
|
+
user=user,
|
27
|
+
created_by=user,
|
28
|
+
modified_by=user
|
29
|
+
)
|
30
|
+
|
31
|
+
# Create the homepage
|
32
|
+
homepage = Page.objects.create(
|
33
|
+
book=book,
|
34
|
+
title=homepage_title,
|
35
|
+
content=f"# {homepage_title}\n\nWelcome to {title}.",
|
36
|
+
order_priority=1000, # High priority to appear first
|
37
|
+
user=user,
|
38
|
+
created_by=user,
|
39
|
+
modified_by=user
|
40
|
+
)
|
41
|
+
|
42
|
+
# Create initial revision
|
43
|
+
homepage.create_revision(
|
44
|
+
user=user,
|
45
|
+
change_summary="Initial page creation"
|
46
|
+
)
|
47
|
+
|
48
|
+
logit.info(f"Created new book '{title}' with homepage for user {user.username}")
|
49
|
+
|
50
|
+
return book, homepage
|
51
|
+
|
52
|
+
except Exception as e:
|
53
|
+
logit.error(f"Failed to create book '{title}': {str(e)}")
|
54
|
+
raise
|
55
|
+
|
56
|
+
@staticmethod
|
57
|
+
def move_page(page, new_parent=None, new_position=None):
|
58
|
+
"""
|
59
|
+
Move a page to a new location in the hierarchy
|
60
|
+
|
61
|
+
Handles validation and maintains data integrity
|
62
|
+
"""
|
63
|
+
try:
|
64
|
+
old_parent = page.parent
|
65
|
+
old_path = page.full_path
|
66
|
+
|
67
|
+
# Validate the move
|
68
|
+
if new_parent and new_parent.book != page.book:
|
69
|
+
raise ValueError(f"Cannot move page to a different book: from '{page.book.id}' to '{new_parent.book.id}'")
|
70
|
+
|
71
|
+
if new_parent and page._would_create_cycle(new_parent):
|
72
|
+
raise ValueError("Move would create circular reference")
|
73
|
+
|
74
|
+
# Update the page
|
75
|
+
page.parent = new_parent
|
76
|
+
|
77
|
+
if new_position is not None:
|
78
|
+
page.order_priority = new_position
|
79
|
+
|
80
|
+
page.save()
|
81
|
+
|
82
|
+
new_path = page.full_path
|
83
|
+
logit.info(f"Moved page '{page.title}' from '{old_path}' to '{new_path}'")
|
84
|
+
|
85
|
+
return page
|
86
|
+
|
87
|
+
except Exception as e:
|
88
|
+
logit.error(f"Failed to move page '{page.title}': {str(e)}")
|
89
|
+
raise
|
90
|
+
|
91
|
+
@staticmethod
|
92
|
+
def duplicate_page(page, new_title, new_parent=None, include_children=False, user=None):
|
93
|
+
"""
|
94
|
+
Create a duplicate of a page, optionally with its children
|
95
|
+
"""
|
96
|
+
try:
|
97
|
+
# Create the duplicate
|
98
|
+
duplicate = Page.objects.create(
|
99
|
+
book=page.book,
|
100
|
+
parent=new_parent or page.parent,
|
101
|
+
title=new_title,
|
102
|
+
content=page.content,
|
103
|
+
order_priority=page.order_priority,
|
104
|
+
metadata=page.metadata.copy(),
|
105
|
+
is_published=False, # Start as draft
|
106
|
+
user=page.user,
|
107
|
+
created_by=user or page.created_by,
|
108
|
+
modified_by=user or page.modified_by
|
109
|
+
)
|
110
|
+
|
111
|
+
# Create initial revision for the duplicate
|
112
|
+
duplicate.create_revision(
|
113
|
+
user=user or page.created_by,
|
114
|
+
change_summary=f"Duplicated from '{page.title}'"
|
115
|
+
)
|
116
|
+
|
117
|
+
# Duplicate children if requested
|
118
|
+
if include_children:
|
119
|
+
for child in page.get_all_children(include_unpublished=True):
|
120
|
+
DocItService.duplicate_page(
|
121
|
+
page=child,
|
122
|
+
new_title=child.title,
|
123
|
+
new_parent=duplicate,
|
124
|
+
include_children=True, # Recursive
|
125
|
+
user=user
|
126
|
+
)
|
127
|
+
|
128
|
+
logit.info(f"Duplicated page '{page.title}' as '{new_title}' (children: {include_children})")
|
129
|
+
|
130
|
+
return duplicate
|
131
|
+
|
132
|
+
except Exception as e:
|
133
|
+
logit.error(f"Failed to duplicate page '{page.title}': {str(e)}")
|
134
|
+
raise
|
135
|
+
|
136
|
+
@staticmethod
|
137
|
+
def bulk_update_page_status(pages, is_published, user):
|
138
|
+
"""
|
139
|
+
Bulk update publication status for multiple pages
|
140
|
+
"""
|
141
|
+
try:
|
142
|
+
updated_count = 0
|
143
|
+
|
144
|
+
for page in pages:
|
145
|
+
if page.is_published != is_published:
|
146
|
+
page.is_published = is_published
|
147
|
+
page.modified_by = user
|
148
|
+
page.save()
|
149
|
+
updated_count += 1
|
150
|
+
|
151
|
+
status = "published" if is_published else "unpublished"
|
152
|
+
logit.info(f"Bulk updated {updated_count} pages to {status} by {user.username}")
|
153
|
+
|
154
|
+
return updated_count
|
155
|
+
|
156
|
+
except Exception as e:
|
157
|
+
logit.error(f"Failed to bulk update page status: {str(e)}")
|
158
|
+
raise
|
159
|
+
|
160
|
+
@staticmethod
|
161
|
+
def get_book_structure(book, include_unpublished=False):
|
162
|
+
"""
|
163
|
+
Get the complete hierarchical structure of a book
|
164
|
+
|
165
|
+
Returns a nested dictionary representing the page tree
|
166
|
+
"""
|
167
|
+
def build_tree(pages, parent_id=None):
|
168
|
+
tree = []
|
169
|
+
for page in pages:
|
170
|
+
if page.parent_id == parent_id:
|
171
|
+
page_data = {
|
172
|
+
'id': page.id,
|
173
|
+
'title': page.title,
|
174
|
+
'slug': page.slug,
|
175
|
+
'is_published': page.is_published,
|
176
|
+
'order_priority': page.order_priority,
|
177
|
+
'children': build_tree(pages, page.id)
|
178
|
+
}
|
179
|
+
tree.append(page_data)
|
180
|
+
return tree
|
181
|
+
|
182
|
+
try:
|
183
|
+
queryset = book.pages.all()
|
184
|
+
if not include_unpublished:
|
185
|
+
queryset = queryset.filter(is_published=True)
|
186
|
+
|
187
|
+
pages = list(queryset.order_by('-order_priority', 'title'))
|
188
|
+
structure = build_tree(pages)
|
189
|
+
|
190
|
+
logit.debug(f"Generated structure for book '{book.title}' with {len(pages)} pages")
|
191
|
+
|
192
|
+
return structure
|
193
|
+
|
194
|
+
except Exception as e:
|
195
|
+
logit.error(f"Failed to get book structure for '{book.title}': {str(e)}")
|
196
|
+
raise
|
197
|
+
|
198
|
+
@staticmethod
|
199
|
+
def organize_assets(book, asset_ids_in_order):
|
200
|
+
"""
|
201
|
+
Reorder assets within a book based on provided ID list
|
202
|
+
"""
|
203
|
+
try:
|
204
|
+
assets = Asset.objects.filter(book=book, id__in=asset_ids_in_order)
|
205
|
+
|
206
|
+
updated_count = 0
|
207
|
+
for index, asset_id in enumerate(asset_ids_in_order):
|
208
|
+
asset = assets.filter(id=asset_id).first()
|
209
|
+
if asset:
|
210
|
+
new_priority = len(asset_ids_in_order) - index # Higher index = higher priority
|
211
|
+
if asset.order_priority != new_priority:
|
212
|
+
asset.order_priority = new_priority
|
213
|
+
asset.save()
|
214
|
+
updated_count += 1
|
215
|
+
|
216
|
+
logit.info(f"Reorganized {updated_count} assets in book '{book.title}'")
|
217
|
+
|
218
|
+
return updated_count
|
219
|
+
|
220
|
+
except Exception as e:
|
221
|
+
logit.error(f"Failed to organize assets for book '{book.title}': {str(e)}")
|
222
|
+
raise
|
223
|
+
|
224
|
+
@staticmethod
|
225
|
+
def cleanup_orphaned_revisions(max_revisions_per_page=50):
|
226
|
+
"""
|
227
|
+
Clean up old revisions across all pages to prevent database bloat
|
228
|
+
"""
|
229
|
+
try:
|
230
|
+
total_cleaned = 0
|
231
|
+
|
232
|
+
# Get all pages that have more than the max revisions
|
233
|
+
for page in Page.objects.all():
|
234
|
+
revision_count = page.revisions.count()
|
235
|
+
|
236
|
+
if revision_count > max_revisions_per_page:
|
237
|
+
cleaned = PageRevision.cleanup_old_revisions(page, max_revisions_per_page)
|
238
|
+
total_cleaned += cleaned
|
239
|
+
|
240
|
+
if total_cleaned > 0:
|
241
|
+
logit.info(f"Cleaned up {total_cleaned} old page revisions")
|
242
|
+
|
243
|
+
return total_cleaned
|
244
|
+
|
245
|
+
except Exception as e:
|
246
|
+
logit.error(f"Failed to cleanup orphaned revisions: {str(e)}")
|
247
|
+
raise
|
248
|
+
|
249
|
+
@staticmethod
|
250
|
+
def get_book_statistics(book):
|
251
|
+
"""
|
252
|
+
Get comprehensive statistics for a book
|
253
|
+
"""
|
254
|
+
try:
|
255
|
+
stats = {
|
256
|
+
'total_pages': book.get_page_count(),
|
257
|
+
'published_pages': book.pages.filter(is_published=True).count(),
|
258
|
+
'draft_pages': book.pages.filter(is_published=False).count(),
|
259
|
+
'total_assets': book.get_asset_count(),
|
260
|
+
'image_assets': book.assets.filter(file__category='image').count(),
|
261
|
+
'document_assets': book.assets.filter(file__category='document').count(),
|
262
|
+
'total_revisions': PageRevision.objects.filter(page__book=book).count(),
|
263
|
+
'root_pages': book.get_root_pages(published_only=False).count(),
|
264
|
+
'max_depth': 0
|
265
|
+
}
|
266
|
+
|
267
|
+
# Calculate maximum page depth
|
268
|
+
for page in book.pages.all():
|
269
|
+
depth = page.get_depth()
|
270
|
+
if depth > stats['max_depth']:
|
271
|
+
stats['max_depth'] = depth
|
272
|
+
|
273
|
+
logit.debug(f"Generated statistics for book '{book.title}'")
|
274
|
+
|
275
|
+
return stats
|
276
|
+
|
277
|
+
except Exception as e:
|
278
|
+
logit.error(f"Failed to get statistics for book '{book.title}': {str(e)}")
|
279
|
+
raise
|
280
|
+
|
281
|
+
@staticmethod
|
282
|
+
def validate_book_integrity(book):
|
283
|
+
"""
|
284
|
+
Validate the integrity of a book and its pages
|
285
|
+
|
286
|
+
Returns a list of issues found
|
287
|
+
"""
|
288
|
+
issues = []
|
289
|
+
|
290
|
+
try:
|
291
|
+
# Check for circular references in page hierarchy
|
292
|
+
for page in book.pages.all():
|
293
|
+
try:
|
294
|
+
_ = page.full_path # This will fail if there's a cycle
|
295
|
+
except RecursionError:
|
296
|
+
issues.append(f"Circular reference detected in page hierarchy: {page.title}")
|
297
|
+
|
298
|
+
# Check for orphaned assets (assets without files)
|
299
|
+
orphaned_assets = book.assets.filter(file__isnull=True)
|
300
|
+
if orphaned_assets.exists():
|
301
|
+
issues.append(f"Found {orphaned_assets.count()} orphaned assets without files")
|
302
|
+
|
303
|
+
# Check for pages with same slug
|
304
|
+
slugs = book.pages.values_list('slug', flat=True)
|
305
|
+
duplicate_slugs = [slug for slug in set(slugs) if slugs.count(slug) > 1]
|
306
|
+
if duplicate_slugs:
|
307
|
+
issues.append(f"Duplicate page slugs found: {duplicate_slugs}")
|
308
|
+
|
309
|
+
logit.info(f"Book integrity check for '{book.title}' found {len(issues)} issues")
|
310
|
+
|
311
|
+
return issues
|
312
|
+
|
313
|
+
except Exception as e:
|
314
|
+
logit.error(f"Failed to validate book integrity for '{book.title}': {str(e)}")
|
315
|
+
raise
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import mistune
|
2
|
+
from pygments import highlight
|
3
|
+
from pygments.lexers import get_lexer_by_name
|
4
|
+
from pygments.styles import get_style_by_name
|
5
|
+
from pygments.formatters import HtmlFormatter
|
6
|
+
|
7
|
+
|
8
|
+
class HighlightRenderer(mistune.HTMLRenderer):
|
9
|
+
def block_code(self, code, info=None):
|
10
|
+
if not info:
|
11
|
+
return f'\n<pre>{mistune.escape(code)}</pre>\n'
|
12
|
+
lexer = get_lexer_by_name(info, stripall=True)
|
13
|
+
formatter = HtmlFormatter(
|
14
|
+
linenos=False,
|
15
|
+
cssclass="highlight",
|
16
|
+
style=get_style_by_name("monokai")
|
17
|
+
)
|
18
|
+
return highlight(code, lexer, formatter)
|
19
|
+
|
20
|
+
class MarkdownRenderer:
|
21
|
+
_renderer = None
|
22
|
+
|
23
|
+
def __init__(self):
|
24
|
+
if not self._renderer:
|
25
|
+
self._initialize_renderer()
|
26
|
+
|
27
|
+
def _initialize_renderer(self):
|
28
|
+
# plugins = self._discover_plugins()
|
29
|
+
self._renderer = mistune.create_markdown(
|
30
|
+
renderer=HighlightRenderer(escape=False),
|
31
|
+
escape=False,
|
32
|
+
hard_wrap=True,
|
33
|
+
plugins=[]
|
34
|
+
)
|
35
|
+
|
36
|
+
def _discover_plugins(self):
|
37
|
+
# from mojo.apps.docit.markdown_plugins import syntax_highlight
|
38
|
+
plugins = [
|
39
|
+
'table', 'url', 'task_list',
|
40
|
+
'footnotes', 'abbr', 'mark', 'math']
|
41
|
+
return plugins
|
42
|
+
|
43
|
+
def render(self, markdown_text):
|
44
|
+
return self._renderer(markdown_text)
|
mojo/apps/fileman/README.md
CHANGED
@@ -103,8 +103,8 @@ const data = await response.json();
|
|
103
103
|
"files": [
|
104
104
|
{
|
105
105
|
"id": 123,
|
106
|
-
"
|
107
|
-
"
|
106
|
+
"storage_filename": "document_20231201_abc12345.pdf",
|
107
|
+
"filename": "document.pdf",
|
108
108
|
"upload_token": "a1b2c3d4e5f6...",
|
109
109
|
"upload_url": "https://s3.amazonaws.com/my-bucket/...",
|
110
110
|
"method": "POST",
|
@@ -297,12 +297,12 @@ class FileUploader {
|
|
297
297
|
|
298
298
|
async uploadFile(file, uploadData) {
|
299
299
|
const formData = new FormData();
|
300
|
-
|
300
|
+
|
301
301
|
// Add fields for S3 or other backends
|
302
302
|
Object.entries(uploadData.fields || {}).forEach(([key, value]) => {
|
303
303
|
formData.append(key, value);
|
304
304
|
});
|
305
|
-
|
305
|
+
|
306
306
|
formData.append('file', file);
|
307
307
|
|
308
308
|
const response = await fetch(uploadData.upload_url, {
|
@@ -411,7 +411,7 @@ file_manager = FileManager.objects.create(
|
|
411
411
|
allowed_extensions=["jpg", "jpeg", "png", "gif", "webp"],
|
412
412
|
allowed_mime_types=[
|
413
413
|
"image/jpeg",
|
414
|
-
"image/png",
|
414
|
+
"image/png",
|
415
415
|
"image/gif",
|
416
416
|
"image/webp"
|
417
417
|
],
|
@@ -429,13 +429,13 @@ def validate_upload(request, file_data):
|
|
429
429
|
# Custom business logic
|
430
430
|
if file_data['filename'].startswith('temp_'):
|
431
431
|
raise ValidationError('Temporary files not allowed')
|
432
|
-
|
432
|
+
|
433
433
|
# Check file size against user's quota
|
434
434
|
user_files_size = File.objects.filter(
|
435
435
|
uploaded_by=request.user,
|
436
436
|
upload_status=File.COMPLETED
|
437
437
|
).aggregate(total=Sum('file_size'))['total'] or 0
|
438
|
-
|
438
|
+
|
439
439
|
if user_files_size + file_data['size'] > USER_QUOTA:
|
440
440
|
raise ValidationError('Upload would exceed user quota')
|
441
441
|
```
|
@@ -546,4 +546,4 @@ print(f"Backend valid: {is_valid}, Errors: {errors}")
|
|
546
546
|
|
547
547
|
## License
|
548
548
|
|
549
|
-
This project is licensed under the MIT License - see the LICENSE file for details.
|
549
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|