django-nativemojo 0.1.15__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.
Files changed (221) hide show
  1. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.16.dist-info}/METADATA +3 -1
  2. django_nativemojo-0.1.16.dist-info/RECORD +302 -0
  3. mojo/__init__.py +1 -1
  4. mojo/apps/account/management/commands/serializer_admin.py +121 -1
  5. mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
  6. mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
  7. mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
  8. mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
  9. mojo/apps/account/migrations/0010_group_avatar.py +20 -0
  10. mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
  11. mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
  12. mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
  13. mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
  14. mojo/apps/account/models/__init__.py +2 -0
  15. mojo/apps/account/models/device.py +281 -0
  16. mojo/apps/account/models/group.py +294 -8
  17. mojo/apps/account/models/member.py +14 -1
  18. mojo/apps/account/models/push/__init__.py +4 -0
  19. mojo/apps/account/models/push/config.py +112 -0
  20. mojo/apps/account/models/push/delivery.py +93 -0
  21. mojo/apps/account/models/push/device.py +66 -0
  22. mojo/apps/account/models/push/template.py +99 -0
  23. mojo/apps/account/models/user.py +190 -17
  24. mojo/apps/account/rest/__init__.py +2 -0
  25. mojo/apps/account/rest/device.py +39 -0
  26. mojo/apps/account/rest/group.py +8 -0
  27. mojo/apps/account/rest/push.py +187 -0
  28. mojo/apps/account/rest/user.py +95 -5
  29. mojo/apps/account/services/__init__.py +1 -0
  30. mojo/apps/account/services/push.py +363 -0
  31. mojo/apps/aws/migrations/0001_initial.py +206 -0
  32. mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
  33. mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
  34. mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
  35. mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
  36. mojo/apps/aws/models/__init__.py +19 -0
  37. mojo/apps/aws/models/email_attachment.py +99 -0
  38. mojo/apps/aws/models/email_domain.py +218 -0
  39. mojo/apps/aws/models/email_template.py +132 -0
  40. mojo/apps/aws/models/incoming_email.py +197 -0
  41. mojo/apps/aws/models/mailbox.py +288 -0
  42. mojo/apps/aws/models/sent_message.py +175 -0
  43. mojo/apps/aws/rest/__init__.py +6 -0
  44. mojo/apps/aws/rest/email.py +33 -0
  45. mojo/apps/aws/rest/email_ops.py +183 -0
  46. mojo/apps/aws/rest/messages.py +32 -0
  47. mojo/apps/aws/rest/send.py +101 -0
  48. mojo/apps/aws/rest/sns.py +403 -0
  49. mojo/apps/aws/rest/templates.py +19 -0
  50. mojo/apps/aws/services/__init__.py +32 -0
  51. mojo/apps/aws/services/email.py +390 -0
  52. mojo/apps/aws/services/email_ops.py +548 -0
  53. mojo/apps/docit/__init__.py +6 -0
  54. mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
  55. mojo/apps/docit/markdown_plugins/toc.py +12 -0
  56. mojo/apps/docit/migrations/0001_initial.py +113 -0
  57. mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
  58. mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
  59. mojo/apps/docit/models/__init__.py +17 -0
  60. mojo/apps/docit/models/asset.py +231 -0
  61. mojo/apps/docit/models/book.py +227 -0
  62. mojo/apps/docit/models/page.py +319 -0
  63. mojo/apps/docit/models/page_revision.py +203 -0
  64. mojo/apps/docit/rest/__init__.py +10 -0
  65. mojo/apps/docit/rest/asset.py +17 -0
  66. mojo/apps/docit/rest/book.py +22 -0
  67. mojo/apps/docit/rest/page.py +22 -0
  68. mojo/apps/docit/rest/page_revision.py +17 -0
  69. mojo/apps/docit/services/__init__.py +11 -0
  70. mojo/apps/docit/services/docit.py +315 -0
  71. mojo/apps/docit/services/markdown.py +44 -0
  72. mojo/apps/fileman/backends/s3.py +209 -0
  73. mojo/apps/fileman/models/file.py +45 -9
  74. mojo/apps/fileman/models/manager.py +269 -3
  75. mojo/apps/incident/migrations/0007_event_uid.py +18 -0
  76. mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
  77. mojo/apps/incident/migrations/0009_incident_status.py +18 -0
  78. mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
  79. mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
  80. mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
  81. mojo/apps/incident/models/__init__.py +1 -0
  82. mojo/apps/incident/models/event.py +35 -0
  83. mojo/apps/incident/models/incident.py +2 -0
  84. mojo/apps/incident/models/ticket.py +62 -0
  85. mojo/apps/incident/reporter.py +21 -3
  86. mojo/apps/incident/rest/__init__.py +1 -0
  87. mojo/apps/incident/rest/ticket.py +43 -0
  88. mojo/apps/jobs/__init__.py +489 -0
  89. mojo/apps/jobs/adapters.py +24 -0
  90. mojo/apps/jobs/cli.py +616 -0
  91. mojo/apps/jobs/daemon.py +370 -0
  92. mojo/apps/jobs/examples/sample_jobs.py +376 -0
  93. mojo/apps/jobs/examples/webhook_examples.py +203 -0
  94. mojo/apps/jobs/handlers/__init__.py +5 -0
  95. mojo/apps/jobs/handlers/webhook.py +317 -0
  96. mojo/apps/jobs/job_engine.py +734 -0
  97. mojo/apps/jobs/keys.py +203 -0
  98. mojo/apps/jobs/local_queue.py +363 -0
  99. mojo/apps/jobs/management/__init__.py +3 -0
  100. mojo/apps/jobs/management/commands/__init__.py +3 -0
  101. mojo/apps/jobs/manager.py +1327 -0
  102. mojo/apps/jobs/migrations/0001_initial.py +97 -0
  103. mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
  104. mojo/apps/jobs/models/__init__.py +6 -0
  105. mojo/apps/jobs/models/job.py +441 -0
  106. mojo/apps/jobs/rest/__init__.py +2 -0
  107. mojo/apps/jobs/rest/control.py +466 -0
  108. mojo/apps/jobs/rest/jobs.py +421 -0
  109. mojo/apps/jobs/scheduler.py +571 -0
  110. mojo/apps/jobs/services/__init__.py +6 -0
  111. mojo/apps/jobs/services/job_actions.py +465 -0
  112. mojo/apps/jobs/settings.py +209 -0
  113. mojo/apps/logit/models/log.py +3 -0
  114. mojo/apps/metrics/__init__.py +8 -1
  115. mojo/apps/metrics/redis_metrics.py +198 -0
  116. mojo/apps/metrics/rest/__init__.py +3 -0
  117. mojo/apps/metrics/rest/categories.py +266 -0
  118. mojo/apps/metrics/rest/helpers.py +48 -0
  119. mojo/apps/metrics/rest/permissions.py +99 -0
  120. mojo/apps/metrics/rest/values.py +277 -0
  121. mojo/apps/metrics/utils.py +17 -0
  122. mojo/decorators/http.py +40 -1
  123. mojo/helpers/aws/__init__.py +11 -7
  124. mojo/helpers/aws/inbound_email.py +309 -0
  125. mojo/helpers/aws/kms.py +413 -0
  126. mojo/helpers/aws/ses_domain.py +959 -0
  127. mojo/helpers/crypto/__init__.py +1 -1
  128. mojo/helpers/crypto/utils.py +15 -0
  129. mojo/helpers/location/__init__.py +2 -0
  130. mojo/helpers/location/countries.py +262 -0
  131. mojo/helpers/location/geolocation.py +196 -0
  132. mojo/helpers/logit.py +37 -0
  133. mojo/helpers/redis/__init__.py +2 -0
  134. mojo/helpers/redis/adapter.py +606 -0
  135. mojo/helpers/redis/client.py +48 -0
  136. mojo/helpers/redis/pool.py +225 -0
  137. mojo/helpers/request.py +8 -0
  138. mojo/helpers/response.py +8 -0
  139. mojo/middleware/auth.py +1 -1
  140. mojo/middleware/cors.py +40 -0
  141. mojo/middleware/logging.py +131 -12
  142. mojo/middleware/mojo.py +5 -0
  143. mojo/models/rest.py +271 -57
  144. mojo/models/secrets.py +86 -0
  145. mojo/serializers/__init__.py +16 -10
  146. mojo/serializers/core/__init__.py +90 -0
  147. mojo/serializers/core/cache/__init__.py +121 -0
  148. mojo/serializers/core/cache/backends.py +518 -0
  149. mojo/serializers/core/cache/base.py +102 -0
  150. mojo/serializers/core/cache/disabled.py +181 -0
  151. mojo/serializers/core/cache/memory.py +287 -0
  152. mojo/serializers/core/cache/redis.py +533 -0
  153. mojo/serializers/core/cache/utils.py +454 -0
  154. mojo/serializers/{manager.py → core/manager.py} +53 -4
  155. mojo/serializers/core/serializer.py +475 -0
  156. mojo/serializers/{advanced/formats → formats}/csv.py +116 -139
  157. mojo/serializers/suggested_improvements.md +388 -0
  158. testit/client.py +1 -1
  159. testit/helpers.py +14 -0
  160. testit/runner.py +23 -6
  161. django_nativemojo-0.1.15.dist-info/RECORD +0 -234
  162. mojo/apps/notify/README.md +0 -91
  163. mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
  164. mojo/apps/notify/admin.py +0 -52
  165. mojo/apps/notify/handlers/example_handlers.py +0 -516
  166. mojo/apps/notify/handlers/ses/__init__.py +0 -25
  167. mojo/apps/notify/handlers/ses/complaint.py +0 -25
  168. mojo/apps/notify/handlers/ses/message.py +0 -86
  169. mojo/apps/notify/management/commands/__init__.py +0 -1
  170. mojo/apps/notify/management/commands/process_notifications.py +0 -370
  171. mojo/apps/notify/mod +0 -0
  172. mojo/apps/notify/models/__init__.py +0 -12
  173. mojo/apps/notify/models/account.py +0 -128
  174. mojo/apps/notify/models/attachment.py +0 -24
  175. mojo/apps/notify/models/bounce.py +0 -68
  176. mojo/apps/notify/models/complaint.py +0 -40
  177. mojo/apps/notify/models/inbox.py +0 -113
  178. mojo/apps/notify/models/inbox_message.py +0 -173
  179. mojo/apps/notify/models/outbox.py +0 -129
  180. mojo/apps/notify/models/outbox_message.py +0 -288
  181. mojo/apps/notify/models/template.py +0 -30
  182. mojo/apps/notify/providers/aws.py +0 -73
  183. mojo/apps/notify/rest/ses.py +0 -0
  184. mojo/apps/notify/utils/__init__.py +0 -2
  185. mojo/apps/notify/utils/notifications.py +0 -404
  186. mojo/apps/notify/utils/parsing.py +0 -202
  187. mojo/apps/notify/utils/render.py +0 -144
  188. mojo/apps/tasks/README.md +0 -118
  189. mojo/apps/tasks/__init__.py +0 -44
  190. mojo/apps/tasks/manager.py +0 -644
  191. mojo/apps/tasks/rest/__init__.py +0 -2
  192. mojo/apps/tasks/rest/hooks.py +0 -0
  193. mojo/apps/tasks/rest/tasks.py +0 -76
  194. mojo/apps/tasks/runner.py +0 -439
  195. mojo/apps/tasks/task.py +0 -99
  196. mojo/apps/tasks/tq_handlers.py +0 -132
  197. mojo/helpers/crypto/__pycache__/hash.cpython-310.pyc +0 -0
  198. mojo/helpers/crypto/__pycache__/sign.cpython-310.pyc +0 -0
  199. mojo/helpers/crypto/__pycache__/utils.cpython-310.pyc +0 -0
  200. mojo/helpers/redis.py +0 -10
  201. mojo/models/meta.py +0 -262
  202. mojo/serializers/advanced/README.md +0 -363
  203. mojo/serializers/advanced/__init__.py +0 -247
  204. mojo/serializers/advanced/formats/__init__.py +0 -28
  205. mojo/serializers/advanced/formats/excel.py +0 -516
  206. mojo/serializers/advanced/formats/json.py +0 -239
  207. mojo/serializers/advanced/formats/response.py +0 -485
  208. mojo/serializers/advanced/serializer.py +0 -568
  209. mojo/serializers/optimized.py +0 -618
  210. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.16.dist-info}/LICENSE +0 -0
  211. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
  212. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
  213. /mojo/apps/{notify → aws/migrations}/__init__.py +0 -0
  214. /mojo/apps/{notify/handlers → docit/markdown_plugins}/__init__.py +0 -0
  215. /mojo/apps/{notify/management → docit/migrations}/__init__.py +0 -0
  216. /mojo/apps/{notify/providers → jobs/examples}/__init__.py +0 -0
  217. /mojo/apps/{notify/rest → jobs/migrations}/__init__.py +0 -0
  218. /mojo/{serializers → rest}/openapi.py +0 -0
  219. /mojo/serializers/{settings_example.py → examples/settings.py} +0 -0
  220. /mojo/{apps/notify/handlers/ses/bounce.py → serializers/formats/__init__.py} +0 -0
  221. /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('&', '&amp;')
312
- .replace('<', '&lt;')
313
- .replace('>', '&gt;')
314
- .replace('"', '&quot;')
315
- .replace("'", '&#39;'))
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