django-nativemojo 0.1.15__py3-none-any.whl → 0.1.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/METADATA +3 -2
- django_nativemojo-0.1.17.dist-info/RECORD +302 -0
- mojo/__init__.py +1 -1
- mojo/apps/account/management/commands/serializer_admin.py +121 -1
- mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
- mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
- mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
- mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
- mojo/apps/account/migrations/0010_group_avatar.py +20 -0
- mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
- mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
- mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
- mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
- mojo/apps/account/models/__init__.py +2 -0
- mojo/apps/account/models/device.py +279 -0
- mojo/apps/account/models/group.py +294 -8
- mojo/apps/account/models/member.py +14 -1
- mojo/apps/account/models/push/__init__.py +4 -0
- mojo/apps/account/models/push/config.py +112 -0
- mojo/apps/account/models/push/delivery.py +93 -0
- mojo/apps/account/models/push/device.py +66 -0
- mojo/apps/account/models/push/template.py +99 -0
- mojo/apps/account/models/user.py +190 -17
- mojo/apps/account/rest/__init__.py +2 -0
- mojo/apps/account/rest/device.py +39 -0
- mojo/apps/account/rest/group.py +8 -0
- mojo/apps/account/rest/push.py +187 -0
- mojo/apps/account/rest/user.py +95 -5
- mojo/apps/account/services/__init__.py +1 -0
- mojo/apps/account/services/push.py +363 -0
- mojo/apps/aws/migrations/0001_initial.py +206 -0
- mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
- mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
- mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
- mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
- mojo/apps/aws/models/__init__.py +19 -0
- mojo/apps/aws/models/email_attachment.py +99 -0
- mojo/apps/aws/models/email_domain.py +218 -0
- mojo/apps/aws/models/email_template.py +132 -0
- mojo/apps/aws/models/incoming_email.py +197 -0
- mojo/apps/aws/models/mailbox.py +288 -0
- mojo/apps/aws/models/sent_message.py +175 -0
- mojo/apps/aws/rest/__init__.py +6 -0
- mojo/apps/aws/rest/email.py +33 -0
- mojo/apps/aws/rest/email_ops.py +183 -0
- mojo/apps/aws/rest/messages.py +32 -0
- mojo/apps/aws/rest/send.py +101 -0
- mojo/apps/aws/rest/sns.py +403 -0
- mojo/apps/aws/rest/templates.py +19 -0
- mojo/apps/aws/services/__init__.py +32 -0
- mojo/apps/aws/services/email.py +390 -0
- mojo/apps/aws/services/email_ops.py +548 -0
- mojo/apps/docit/__init__.py +6 -0
- mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
- mojo/apps/docit/markdown_plugins/toc.py +12 -0
- mojo/apps/docit/migrations/0001_initial.py +113 -0
- mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
- mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
- mojo/apps/docit/models/__init__.py +17 -0
- mojo/apps/docit/models/asset.py +231 -0
- mojo/apps/docit/models/book.py +227 -0
- mojo/apps/docit/models/page.py +319 -0
- mojo/apps/docit/models/page_revision.py +203 -0
- mojo/apps/docit/rest/__init__.py +10 -0
- mojo/apps/docit/rest/asset.py +17 -0
- mojo/apps/docit/rest/book.py +22 -0
- mojo/apps/docit/rest/page.py +22 -0
- mojo/apps/docit/rest/page_revision.py +17 -0
- mojo/apps/docit/services/__init__.py +11 -0
- mojo/apps/docit/services/docit.py +315 -0
- mojo/apps/docit/services/markdown.py +44 -0
- mojo/apps/fileman/backends/s3.py +209 -0
- mojo/apps/fileman/models/file.py +45 -9
- mojo/apps/fileman/models/manager.py +269 -3
- mojo/apps/incident/migrations/0007_event_uid.py +18 -0
- mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
- mojo/apps/incident/migrations/0009_incident_status.py +18 -0
- mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
- mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
- mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
- mojo/apps/incident/models/__init__.py +1 -0
- mojo/apps/incident/models/event.py +35 -0
- mojo/apps/incident/models/incident.py +2 -0
- mojo/apps/incident/models/ticket.py +62 -0
- mojo/apps/incident/reporter.py +21 -3
- mojo/apps/incident/rest/__init__.py +1 -0
- mojo/apps/incident/rest/ticket.py +43 -0
- mojo/apps/jobs/__init__.py +489 -0
- mojo/apps/jobs/adapters.py +24 -0
- mojo/apps/jobs/cli.py +616 -0
- mojo/apps/jobs/daemon.py +370 -0
- mojo/apps/jobs/examples/sample_jobs.py +376 -0
- mojo/apps/jobs/examples/webhook_examples.py +203 -0
- mojo/apps/jobs/handlers/__init__.py +5 -0
- mojo/apps/jobs/handlers/webhook.py +317 -0
- mojo/apps/jobs/job_engine.py +734 -0
- mojo/apps/jobs/keys.py +203 -0
- mojo/apps/jobs/local_queue.py +363 -0
- mojo/apps/jobs/management/__init__.py +3 -0
- mojo/apps/jobs/management/commands/__init__.py +3 -0
- mojo/apps/jobs/manager.py +1327 -0
- mojo/apps/jobs/migrations/0001_initial.py +97 -0
- mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
- mojo/apps/jobs/models/__init__.py +6 -0
- mojo/apps/jobs/models/job.py +441 -0
- mojo/apps/jobs/rest/__init__.py +2 -0
- mojo/apps/jobs/rest/control.py +466 -0
- mojo/apps/jobs/rest/jobs.py +421 -0
- mojo/apps/jobs/scheduler.py +571 -0
- mojo/apps/jobs/services/__init__.py +6 -0
- mojo/apps/jobs/services/job_actions.py +465 -0
- mojo/apps/jobs/settings.py +209 -0
- mojo/apps/logit/models/log.py +3 -0
- mojo/apps/metrics/__init__.py +8 -1
- mojo/apps/metrics/redis_metrics.py +198 -0
- mojo/apps/metrics/rest/__init__.py +3 -0
- mojo/apps/metrics/rest/categories.py +266 -0
- mojo/apps/metrics/rest/helpers.py +48 -0
- mojo/apps/metrics/rest/permissions.py +99 -0
- mojo/apps/metrics/rest/values.py +277 -0
- mojo/apps/metrics/utils.py +17 -0
- mojo/decorators/http.py +40 -1
- mojo/helpers/aws/__init__.py +11 -7
- mojo/helpers/aws/inbound_email.py +309 -0
- mojo/helpers/aws/kms.py +413 -0
- mojo/helpers/aws/ses_domain.py +959 -0
- mojo/helpers/crypto/__init__.py +1 -1
- mojo/helpers/crypto/utils.py +15 -0
- mojo/helpers/location/__init__.py +2 -0
- mojo/helpers/location/countries.py +262 -0
- mojo/helpers/location/geolocation.py +196 -0
- mojo/helpers/logit.py +37 -0
- mojo/helpers/redis/__init__.py +2 -0
- mojo/helpers/redis/adapter.py +606 -0
- mojo/helpers/redis/client.py +48 -0
- mojo/helpers/redis/pool.py +225 -0
- mojo/helpers/request.py +8 -0
- mojo/helpers/response.py +8 -0
- mojo/middleware/auth.py +1 -1
- mojo/middleware/cors.py +40 -0
- mojo/middleware/logging.py +131 -12
- mojo/middleware/mojo.py +5 -0
- mojo/models/rest.py +271 -57
- mojo/models/secrets.py +86 -0
- mojo/serializers/__init__.py +16 -10
- mojo/serializers/core/__init__.py +90 -0
- mojo/serializers/core/cache/__init__.py +121 -0
- mojo/serializers/core/cache/backends.py +518 -0
- mojo/serializers/core/cache/base.py +102 -0
- mojo/serializers/core/cache/disabled.py +181 -0
- mojo/serializers/core/cache/memory.py +287 -0
- mojo/serializers/core/cache/redis.py +533 -0
- mojo/serializers/core/cache/utils.py +454 -0
- mojo/serializers/{manager.py → core/manager.py} +53 -4
- mojo/serializers/core/serializer.py +475 -0
- mojo/serializers/{advanced/formats → formats}/csv.py +116 -139
- mojo/serializers/suggested_improvements.md +388 -0
- testit/client.py +1 -1
- testit/helpers.py +14 -0
- testit/runner.py +23 -6
- django_nativemojo-0.1.15.dist-info/RECORD +0 -234
- mojo/apps/notify/README.md +0 -91
- mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
- mojo/apps/notify/admin.py +0 -52
- mojo/apps/notify/handlers/example_handlers.py +0 -516
- mojo/apps/notify/handlers/ses/__init__.py +0 -25
- mojo/apps/notify/handlers/ses/complaint.py +0 -25
- mojo/apps/notify/handlers/ses/message.py +0 -86
- mojo/apps/notify/management/commands/__init__.py +0 -1
- mojo/apps/notify/management/commands/process_notifications.py +0 -370
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +0 -12
- mojo/apps/notify/models/account.py +0 -128
- mojo/apps/notify/models/attachment.py +0 -24
- mojo/apps/notify/models/bounce.py +0 -68
- mojo/apps/notify/models/complaint.py +0 -40
- mojo/apps/notify/models/inbox.py +0 -113
- mojo/apps/notify/models/inbox_message.py +0 -173
- mojo/apps/notify/models/outbox.py +0 -129
- mojo/apps/notify/models/outbox_message.py +0 -288
- mojo/apps/notify/models/template.py +0 -30
- mojo/apps/notify/providers/aws.py +0 -73
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +0 -2
- mojo/apps/notify/utils/notifications.py +0 -404
- mojo/apps/notify/utils/parsing.py +0 -202
- mojo/apps/notify/utils/render.py +0 -144
- mojo/apps/tasks/README.md +0 -118
- mojo/apps/tasks/__init__.py +0 -44
- mojo/apps/tasks/manager.py +0 -644
- mojo/apps/tasks/rest/__init__.py +0 -2
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +0 -76
- mojo/apps/tasks/runner.py +0 -439
- mojo/apps/tasks/task.py +0 -99
- mojo/apps/tasks/tq_handlers.py +0 -132
- mojo/helpers/crypto/__pycache__/hash.cpython-310.pyc +0 -0
- mojo/helpers/crypto/__pycache__/sign.cpython-310.pyc +0 -0
- mojo/helpers/crypto/__pycache__/utils.cpython-310.pyc +0 -0
- mojo/helpers/redis.py +0 -10
- mojo/models/meta.py +0 -262
- mojo/serializers/advanced/README.md +0 -363
- mojo/serializers/advanced/__init__.py +0 -247
- mojo/serializers/advanced/formats/__init__.py +0 -28
- mojo/serializers/advanced/formats/excel.py +0 -516
- mojo/serializers/advanced/formats/json.py +0 -239
- mojo/serializers/advanced/formats/response.py +0 -485
- mojo/serializers/advanced/serializer.py +0 -568
- mojo/serializers/optimized.py +0 -618
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/WHEEL +0 -0
- /mojo/apps/{notify → aws/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/handlers → docit/markdown_plugins}/__init__.py +0 -0
- /mojo/apps/{notify/management → docit/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/providers → jobs/examples}/__init__.py +0 -0
- /mojo/apps/{notify/rest → jobs/migrations}/__init__.py +0 -0
- /mojo/{serializers → rest}/openapi.py +0 -0
- /mojo/serializers/{settings_example.py → examples/settings.py} +0 -0
- /mojo/{apps/notify/handlers/ses/bounce.py → serializers/formats/__init__.py} +0 -0
- /mojo/serializers/{advanced/formats → formats}/localizers.py +0 -0
@@ -1,485 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
import hashlib
|
3
|
-
from django.http import HttpResponse, StreamingHttpResponse
|
4
|
-
from django.shortcuts import render
|
5
|
-
from django.core.cache import cache
|
6
|
-
from django.conf import settings
|
7
|
-
|
8
|
-
from mojo.helpers import logit
|
9
|
-
from .json import JsonFormatter
|
10
|
-
from .csv import CsvFormatter
|
11
|
-
from .excel import ExcelFormatter
|
12
|
-
|
13
|
-
logger = logit.get_logger("response_formatter", "response_formatter.log")
|
14
|
-
|
15
|
-
# Configuration constants
|
16
|
-
STATUS_ON_PERM_DENIED = getattr(settings, 'STATUS_ON_PERM_DENIED', 403)
|
17
|
-
REST_LIST_CACHE_COUNT = getattr(settings, 'REST_LIST_CACHE_COUNT', False)
|
18
|
-
DEBUG_REST_NO_LISTS = getattr(settings, 'DEBUG_REST_NO_LISTS', False)
|
19
|
-
REST_DISCLAIMER = getattr(settings, 'REST_DISCLAIMER', '')
|
20
|
-
|
21
|
-
|
22
|
-
class ResponseFormatter:
|
23
|
-
"""
|
24
|
-
Advanced response formatter that handles multiple output formats and HTTP responses.
|
25
|
-
"""
|
26
|
-
|
27
|
-
def __init__(self, request=None):
|
28
|
-
"""
|
29
|
-
Initialize response formatter.
|
30
|
-
|
31
|
-
:param request: Django request object
|
32
|
-
"""
|
33
|
-
self.request = request
|
34
|
-
self.json_formatter = JsonFormatter()
|
35
|
-
self.csv_formatter = CsvFormatter()
|
36
|
-
self.excel_formatter = ExcelFormatter()
|
37
|
-
|
38
|
-
def format_response(self, data, format_type="json", status=200, **kwargs):
|
39
|
-
"""
|
40
|
-
Format data as HTTP response in specified format.
|
41
|
-
|
42
|
-
:param data: Data to format
|
43
|
-
:param format_type: Output format ("json", "csv", "excel", "html")
|
44
|
-
:param status: HTTP status code
|
45
|
-
:param kwargs: Additional options
|
46
|
-
:return: HttpResponse
|
47
|
-
"""
|
48
|
-
if format_type == "json":
|
49
|
-
return self._json_response(data, status, **kwargs)
|
50
|
-
elif format_type == "csv":
|
51
|
-
return self._csv_response(data, **kwargs)
|
52
|
-
elif format_type in ["excel", "xlsx"]:
|
53
|
-
return self._excel_response(data, **kwargs)
|
54
|
-
elif format_type == "html":
|
55
|
-
return self._html_response(data, status, **kwargs)
|
56
|
-
else:
|
57
|
-
# Default to JSON
|
58
|
-
return self._json_response(data, status, **kwargs)
|
59
|
-
|
60
|
-
def auto_format_response(self, data, status=200, **kwargs):
|
61
|
-
"""
|
62
|
-
Automatically determine response format based on request Accept header.
|
63
|
-
|
64
|
-
:param data: Data to format
|
65
|
-
:param status: HTTP status code
|
66
|
-
:param kwargs: Additional options
|
67
|
-
:return: HttpResponse
|
68
|
-
"""
|
69
|
-
if not self.request:
|
70
|
-
return self._json_response(data, status, **kwargs)
|
71
|
-
|
72
|
-
accept_types = self._parse_accept_header()
|
73
|
-
|
74
|
-
if 'text/html' in accept_types:
|
75
|
-
return self._html_response(data, status, **kwargs)
|
76
|
-
elif 'text/csv' in accept_types:
|
77
|
-
return self._csv_response(data, **kwargs)
|
78
|
-
elif 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' in accept_types:
|
79
|
-
return self._excel_response(data, **kwargs)
|
80
|
-
else:
|
81
|
-
return self._json_response(data, status, **kwargs)
|
82
|
-
|
83
|
-
def _json_response(self, data, status=200, **kwargs):
|
84
|
-
"""Create JSON HTTP response."""
|
85
|
-
try:
|
86
|
-
json_data = self.json_formatter.serialize(data, **kwargs)
|
87
|
-
response = HttpResponse(json_data, content_type='application/json', status=status)
|
88
|
-
|
89
|
-
# Add elapsed time if request is available
|
90
|
-
if self.request and hasattr(self.request, '_started'):
|
91
|
-
elapsed = int((time.perf_counter() - self.request._started) * 1000)
|
92
|
-
response['X-Response-Time'] = f"{elapsed}ms"
|
93
|
-
|
94
|
-
return response
|
95
|
-
except Exception as e:
|
96
|
-
logger.error(f"JSON response formatting failed: {e}")
|
97
|
-
error_data = {
|
98
|
-
'status': False,
|
99
|
-
'error': 'Response formatting failed',
|
100
|
-
'message': str(e)
|
101
|
-
}
|
102
|
-
return HttpResponse(
|
103
|
-
self.json_formatter.serialize(error_data),
|
104
|
-
content_type='application/json',
|
105
|
-
status=500
|
106
|
-
)
|
107
|
-
|
108
|
-
def _csv_response(self, data, **kwargs):
|
109
|
-
"""Create CSV HTTP response."""
|
110
|
-
try:
|
111
|
-
filename = kwargs.get('filename', 'export.csv')
|
112
|
-
|
113
|
-
if hasattr(data, 'model'): # QuerySet
|
114
|
-
return self.csv_formatter.serialize_queryset(
|
115
|
-
data,
|
116
|
-
filename=filename,
|
117
|
-
**kwargs
|
118
|
-
)
|
119
|
-
elif isinstance(data, list):
|
120
|
-
return self.csv_formatter.serialize_data(
|
121
|
-
data,
|
122
|
-
filename=filename,
|
123
|
-
**kwargs
|
124
|
-
)
|
125
|
-
else:
|
126
|
-
# Convert single item to list
|
127
|
-
return self.csv_formatter.serialize_data(
|
128
|
-
[data],
|
129
|
-
filename=filename,
|
130
|
-
**kwargs
|
131
|
-
)
|
132
|
-
except Exception as e:
|
133
|
-
logger.error(f"CSV response formatting failed: {e}")
|
134
|
-
return self._error_response(f"CSV export failed: {str(e)}")
|
135
|
-
|
136
|
-
def _excel_response(self, data, **kwargs):
|
137
|
-
"""Create Excel HTTP response."""
|
138
|
-
try:
|
139
|
-
filename = kwargs.get('filename', 'export.xlsx')
|
140
|
-
|
141
|
-
if hasattr(data, 'model'): # QuerySet
|
142
|
-
return self.excel_formatter.serialize_queryset(
|
143
|
-
data,
|
144
|
-
filename=filename,
|
145
|
-
**kwargs
|
146
|
-
)
|
147
|
-
elif isinstance(data, list):
|
148
|
-
return self.excel_formatter.serialize_data(
|
149
|
-
data,
|
150
|
-
filename=filename,
|
151
|
-
**kwargs
|
152
|
-
)
|
153
|
-
else:
|
154
|
-
# Convert single item to list
|
155
|
-
return self.excel_formatter.serialize_data(
|
156
|
-
[data],
|
157
|
-
filename=filename,
|
158
|
-
**kwargs
|
159
|
-
)
|
160
|
-
except Exception as e:
|
161
|
-
logger.error(f"Excel response formatting failed: {e}")
|
162
|
-
return self._error_response(f"Excel export failed: {str(e)}")
|
163
|
-
|
164
|
-
def _html_response(self, data, status=200, template=None, context=None, **kwargs):
|
165
|
-
"""Create HTML HTTP response."""
|
166
|
-
if template and context is not None:
|
167
|
-
# Use custom template
|
168
|
-
return render(self.request, template, context, status=status)
|
169
|
-
|
170
|
-
# Generate debug/API view HTML
|
171
|
-
return self._render_debug_html(data, status, **kwargs)
|
172
|
-
|
173
|
-
def _render_debug_html(self, data, status=200, **kwargs):
|
174
|
-
"""Render debug HTML view for API responses."""
|
175
|
-
try:
|
176
|
-
# Format data as pretty JSON for display
|
177
|
-
json_output = self.json_formatter.pretty_serialize(data)
|
178
|
-
|
179
|
-
# Get request information
|
180
|
-
request_data = {}
|
181
|
-
if self.request:
|
182
|
-
if hasattr(self.request, 'DATA'):
|
183
|
-
request_data = self.request.DATA.asDict()
|
184
|
-
else:
|
185
|
-
request_data = {
|
186
|
-
'method': self.request.method,
|
187
|
-
'path': self.request.path,
|
188
|
-
'params': dict(self.request.GET.items())
|
189
|
-
}
|
190
|
-
|
191
|
-
# Generate HTML content
|
192
|
-
html_content = f"""
|
193
|
-
<!DOCTYPE html>
|
194
|
-
<html>
|
195
|
-
<head>
|
196
|
-
<title>API Response</title>
|
197
|
-
<meta charset="utf-8">
|
198
|
-
<style>
|
199
|
-
body {{
|
200
|
-
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
201
|
-
margin: 0;
|
202
|
-
padding: 20px;
|
203
|
-
background-color: #f8f9fa;
|
204
|
-
}}
|
205
|
-
.container {{
|
206
|
-
max-width: 1200px;
|
207
|
-
margin: 0 auto;
|
208
|
-
background: white;
|
209
|
-
border-radius: 8px;
|
210
|
-
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
211
|
-
overflow: hidden;
|
212
|
-
}}
|
213
|
-
.header {{
|
214
|
-
background: #343a40;
|
215
|
-
color: white;
|
216
|
-
padding: 15px 20px;
|
217
|
-
border-bottom: 1px solid #dee2e6;
|
218
|
-
}}
|
219
|
-
.header h1 {{
|
220
|
-
margin: 0;
|
221
|
-
font-size: 18px;
|
222
|
-
}}
|
223
|
-
.section {{
|
224
|
-
border-bottom: 1px solid #dee2e6;
|
225
|
-
}}
|
226
|
-
.section-header {{
|
227
|
-
background: #e9ecef;
|
228
|
-
padding: 10px 20px;
|
229
|
-
font-weight: bold;
|
230
|
-
color: #495057;
|
231
|
-
border-bottom: 1px solid #dee2e6;
|
232
|
-
}}
|
233
|
-
.content {{
|
234
|
-
padding: 0;
|
235
|
-
}}
|
236
|
-
pre {{
|
237
|
-
margin: 0;
|
238
|
-
padding: 20px;
|
239
|
-
background: #f8f9fa;
|
240
|
-
overflow-x: auto;
|
241
|
-
font-size: 12px;
|
242
|
-
line-height: 1.4;
|
243
|
-
color: #212529;
|
244
|
-
}}
|
245
|
-
.json {{
|
246
|
-
background: #fff;
|
247
|
-
}}
|
248
|
-
.status-success {{ color: #28a745; }}
|
249
|
-
.status-error {{ color: #dc3545; }}
|
250
|
-
.meta {{
|
251
|
-
padding: 15px 20px;
|
252
|
-
background: #f8f9fa;
|
253
|
-
color: #6c757d;
|
254
|
-
font-size: 12px;
|
255
|
-
}}
|
256
|
-
</style>
|
257
|
-
</head>
|
258
|
-
<body>
|
259
|
-
<div class="container">
|
260
|
-
<div class="header">
|
261
|
-
<h1>API Response Debug View</h1>
|
262
|
-
</div>
|
263
|
-
|
264
|
-
{self._render_request_section(request_data)}
|
265
|
-
|
266
|
-
<div class="section">
|
267
|
-
<div class="section-header">Response Data</div>
|
268
|
-
<div class="content">
|
269
|
-
<pre class="json">{self._escape_html(json_output)}</pre>
|
270
|
-
</div>
|
271
|
-
</div>
|
272
|
-
|
273
|
-
<div class="meta">
|
274
|
-
Status: <span class="{'status-success' if status < 400 else 'status-error'}">{status}</span>
|
275
|
-
{f" | Generated: {time.strftime('%Y-%m-%d %H:%M:%S')}" if self.request else ""}
|
276
|
-
{f" | {REST_DISCLAIMER}" if REST_DISCLAIMER else ""}
|
277
|
-
</div>
|
278
|
-
</div>
|
279
|
-
</body>
|
280
|
-
</html>
|
281
|
-
"""
|
282
|
-
|
283
|
-
return HttpResponse(html_content, content_type='text/html', status=status)
|
284
|
-
|
285
|
-
except Exception as e:
|
286
|
-
logger.error(f"HTML response rendering failed: {e}")
|
287
|
-
return HttpResponse(
|
288
|
-
f"<html><body><h1>Error rendering response</h1><p>{str(e)}</p></body></html>",
|
289
|
-
content_type='text/html',
|
290
|
-
status=500
|
291
|
-
)
|
292
|
-
|
293
|
-
def _render_request_section(self, request_data):
|
294
|
-
"""Render request information section."""
|
295
|
-
if not request_data:
|
296
|
-
return ""
|
297
|
-
|
298
|
-
request_json = self.json_formatter.pretty_serialize(request_data)
|
299
|
-
return f"""
|
300
|
-
<div class="section">
|
301
|
-
<div class="section-header">Request Information</div>
|
302
|
-
<div class="content">
|
303
|
-
<pre>{self._escape_html(request_json)}</pre>
|
304
|
-
</div>
|
305
|
-
</div>
|
306
|
-
"""
|
307
|
-
|
308
|
-
def _escape_html(self, text):
|
309
|
-
"""Escape HTML characters in text."""
|
310
|
-
return (str(text)
|
311
|
-
.replace('&', '&')
|
312
|
-
.replace('<', '<')
|
313
|
-
.replace('>', '>')
|
314
|
-
.replace('"', '"')
|
315
|
-
.replace("'", '''))
|
316
|
-
|
317
|
-
def _error_response(self, message, status=500):
|
318
|
-
"""Create error response."""
|
319
|
-
error_data = {
|
320
|
-
'status': False,
|
321
|
-
'error': message,
|
322
|
-
'datetime': int(time.time())
|
323
|
-
}
|
324
|
-
return self._json_response(error_data, status)
|
325
|
-
|
326
|
-
def _parse_accept_header(self):
|
327
|
-
"""Parse request Accept header."""
|
328
|
-
if not self.request:
|
329
|
-
return ['application/json']
|
330
|
-
|
331
|
-
# Check for explicit format parameter
|
332
|
-
if hasattr(self.request, 'DATA') and hasattr(self.request.DATA, 'get'):
|
333
|
-
format_param = self.request.DATA.get('_format') or self.request.DATA.get('format')
|
334
|
-
if format_param:
|
335
|
-
format_map = {
|
336
|
-
'json': 'application/json',
|
337
|
-
'csv': 'text/csv',
|
338
|
-
'excel': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
339
|
-
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
340
|
-
'html': 'text/html'
|
341
|
-
}
|
342
|
-
if format_param in format_map:
|
343
|
-
return [format_map[format_param]]
|
344
|
-
|
345
|
-
# Parse Accept header
|
346
|
-
accept_header = self.request.META.get('HTTP_ACCEPT', 'application/json')
|
347
|
-
accept_types = [media_type.strip().split(';')[0] for media_type in accept_header.split(',')]
|
348
|
-
|
349
|
-
return accept_types
|
350
|
-
|
351
|
-
|
352
|
-
# Convenience functions for backwards compatibility
|
353
|
-
def rest_status(request, status, data=None, **kwargs):
|
354
|
-
"""
|
355
|
-
Create status response.
|
356
|
-
|
357
|
-
:param request: Django request
|
358
|
-
:param status: Boolean status
|
359
|
-
:param data: Response data
|
360
|
-
:param kwargs: Additional data
|
361
|
-
:return: HttpResponse
|
362
|
-
"""
|
363
|
-
if isinstance(data, str):
|
364
|
-
if status:
|
365
|
-
response_data = {'message': data}
|
366
|
-
else:
|
367
|
-
response_data = {'error': data}
|
368
|
-
else:
|
369
|
-
response_data = data or {}
|
370
|
-
|
371
|
-
response_data.update(kwargs)
|
372
|
-
response_data['status'] = status
|
373
|
-
response_data['datetime'] = int(time.time())
|
374
|
-
|
375
|
-
formatter = ResponseFormatter(request)
|
376
|
-
return formatter.auto_format_response(response_data)
|
377
|
-
|
378
|
-
|
379
|
-
def rest_success(request, data=None, **kwargs):
|
380
|
-
"""Create success response."""
|
381
|
-
return rest_status(request, True, data, **kwargs)
|
382
|
-
|
383
|
-
|
384
|
-
def rest_error(request, error, error_code=None, status=400):
|
385
|
-
"""Create error response."""
|
386
|
-
response_data = {
|
387
|
-
'error': error,
|
388
|
-
'status': False,
|
389
|
-
'datetime': int(time.time())
|
390
|
-
}
|
391
|
-
|
392
|
-
if error_code:
|
393
|
-
response_data['error_code'] = error_code
|
394
|
-
|
395
|
-
formatter = ResponseFormatter(request)
|
396
|
-
return formatter.auto_format_response(response_data, status=status)
|
397
|
-
|
398
|
-
|
399
|
-
def rest_permission_denied(request, error="Permission denied", error_code=403):
|
400
|
-
"""Create permission denied response."""
|
401
|
-
return rest_error(request, error, error_code, status=STATUS_ON_PERM_DENIED)
|
402
|
-
|
403
|
-
|
404
|
-
def rest_not_found(request, error="Not found"):
|
405
|
-
"""Create not found response."""
|
406
|
-
return rest_error(request, error, error_code=404, status=404)
|
407
|
-
|
408
|
-
|
409
|
-
def rest_json(request, data, filename="export.json", **kwargs):
|
410
|
-
"""Create JSON file download response."""
|
411
|
-
formatter = ResponseFormatter(request)
|
412
|
-
json_data = formatter.json_formatter.serialize(data, **kwargs)
|
413
|
-
|
414
|
-
response = HttpResponse(json_data, content_type='application/json')
|
415
|
-
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
416
|
-
return response
|
417
|
-
|
418
|
-
|
419
|
-
def rest_csv(request, queryset, fields, filename="export.csv", **kwargs):
|
420
|
-
"""Create CSV download response."""
|
421
|
-
formatter = ResponseFormatter(request)
|
422
|
-
return formatter.csv_formatter.serialize_queryset(
|
423
|
-
queryset=queryset,
|
424
|
-
fields=fields,
|
425
|
-
filename=filename,
|
426
|
-
**kwargs
|
427
|
-
)
|
428
|
-
|
429
|
-
|
430
|
-
def rest_excel(request, queryset, fields, filename="export.xlsx", **kwargs):
|
431
|
-
"""Create Excel download response."""
|
432
|
-
formatter = ResponseFormatter(request)
|
433
|
-
return formatter.excel_formatter.serialize_queryset(
|
434
|
-
queryset=queryset,
|
435
|
-
fields=fields,
|
436
|
-
filename=filename,
|
437
|
-
**kwargs
|
438
|
-
)
|
439
|
-
|
440
|
-
|
441
|
-
def rest_html(request, html_content=None, template=None, context=None, status=200):
|
442
|
-
"""Create HTML response."""
|
443
|
-
formatter = ResponseFormatter(request)
|
444
|
-
|
445
|
-
if html_content:
|
446
|
-
return HttpResponse(html_content, content_type='text/html', status=status)
|
447
|
-
elif template:
|
448
|
-
return render(request, template, context or {}, status=status)
|
449
|
-
else:
|
450
|
-
return HttpResponse(
|
451
|
-
"<html><body><h1>Hello, World!</h1><p>Welcome to the API.</p></body></html>",
|
452
|
-
content_type='text/html',
|
453
|
-
status=status
|
454
|
-
)
|
455
|
-
|
456
|
-
|
457
|
-
def get_cached_count(queryset, timeout=1800):
|
458
|
-
"""
|
459
|
-
Get cached count for queryset.
|
460
|
-
|
461
|
-
:param queryset: Django QuerySet
|
462
|
-
:param timeout: Cache timeout in seconds
|
463
|
-
:return: Count integer
|
464
|
-
"""
|
465
|
-
if not REST_LIST_CACHE_COUNT:
|
466
|
-
return queryset.count()
|
467
|
-
|
468
|
-
# Generate cache key from query
|
469
|
-
query_hash = hashlib.sha256(str(queryset.query).encode()).hexdigest()
|
470
|
-
cache_key = f"rest_count_{query_hash}"
|
471
|
-
|
472
|
-
count = cache.get(cache_key)
|
473
|
-
if count is None:
|
474
|
-
count = queryset.count()
|
475
|
-
cache.set(cache_key, count, timeout=timeout)
|
476
|
-
logger.debug(f"Cached count for query: {count}")
|
477
|
-
|
478
|
-
return count
|
479
|
-
|
480
|
-
|
481
|
-
def get_request_elapsed(request):
|
482
|
-
"""Get elapsed time for request in milliseconds."""
|
483
|
-
if request and hasattr(request, '_started'):
|
484
|
-
return int((time.perf_counter() - request._started) * 1000)
|
485
|
-
return 0
|