django-cfg 1.4.120__py3-none-any.whl → 1.5.2__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.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (182) hide show
  1. django_cfg/__init__.py +8 -4
  2. django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
  3. django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
  4. django_cfg/apps/dashboard/serializers/__init__.py +0 -12
  5. django_cfg/apps/dashboard/serializers/activity.py +1 -1
  6. django_cfg/apps/dashboard/services/__init__.py +0 -2
  7. django_cfg/apps/dashboard/services/charts_service.py +4 -3
  8. django_cfg/apps/dashboard/services/statistics_service.py +11 -2
  9. django_cfg/apps/dashboard/services/system_health_service.py +64 -106
  10. django_cfg/apps/dashboard/urls.py +0 -2
  11. django_cfg/apps/dashboard/views/__init__.py +0 -2
  12. django_cfg/apps/dashboard/views/commands_views.py +3 -6
  13. django_cfg/apps/dashboard/views/overview_views.py +14 -13
  14. django_cfg/apps/grpc/__init__.py +9 -0
  15. django_cfg/apps/grpc/admin/__init__.py +11 -0
  16. django_cfg/apps/{tasks → grpc}/admin/config.py +32 -41
  17. django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
  18. django_cfg/apps/grpc/apps.py +28 -0
  19. django_cfg/apps/grpc/auth/__init__.py +9 -0
  20. django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
  21. django_cfg/apps/grpc/interceptors/__init__.py +19 -0
  22. django_cfg/apps/grpc/interceptors/errors.py +241 -0
  23. django_cfg/apps/grpc/interceptors/logging.py +270 -0
  24. django_cfg/apps/grpc/interceptors/metrics.py +306 -0
  25. django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
  26. django_cfg/apps/grpc/management/__init__.py +1 -0
  27. django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
  28. django_cfg/apps/grpc/managers/__init__.py +10 -0
  29. django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
  30. django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
  31. django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
  32. django_cfg/apps/grpc/models/__init__.py +9 -0
  33. django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
  34. django_cfg/apps/grpc/serializers/__init__.py +23 -0
  35. django_cfg/apps/grpc/serializers/health.py +18 -0
  36. django_cfg/apps/grpc/serializers/requests.py +18 -0
  37. django_cfg/apps/grpc/serializers/services.py +50 -0
  38. django_cfg/apps/grpc/serializers/stats.py +22 -0
  39. django_cfg/apps/grpc/services/__init__.py +16 -0
  40. django_cfg/apps/grpc/services/base.py +375 -0
  41. django_cfg/apps/grpc/services/discovery.py +415 -0
  42. django_cfg/apps/grpc/urls.py +23 -0
  43. django_cfg/apps/grpc/utils/__init__.py +13 -0
  44. django_cfg/apps/grpc/utils/proto_gen.py +423 -0
  45. django_cfg/apps/grpc/views/__init__.py +9 -0
  46. django_cfg/apps/grpc/views/monitoring.py +497 -0
  47. django_cfg/apps/knowbase/apps.py +2 -2
  48. django_cfg/apps/maintenance/admin/api_key_admin.py +7 -9
  49. django_cfg/apps/maintenance/admin/site_admin.py +5 -4
  50. django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
  51. django_cfg/apps/payments/admin/balance_admin.py +26 -36
  52. django_cfg/apps/payments/admin/payment_admin.py +65 -85
  53. django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
  54. django_cfg/apps/rq/__init__.py +9 -0
  55. django_cfg/apps/rq/apps.py +80 -0
  56. django_cfg/apps/rq/management/__init__.py +1 -0
  57. django_cfg/apps/rq/management/commands/__init__.py +1 -0
  58. django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
  59. django_cfg/apps/rq/management/commands/rqstats.py +33 -0
  60. django_cfg/apps/rq/management/commands/rqworker.py +31 -0
  61. django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
  62. django_cfg/apps/rq/serializers/__init__.py +40 -0
  63. django_cfg/apps/rq/serializers/health.py +60 -0
  64. django_cfg/apps/rq/serializers/job.py +100 -0
  65. django_cfg/apps/rq/serializers/queue.py +80 -0
  66. django_cfg/apps/rq/serializers/schedule.py +178 -0
  67. django_cfg/apps/rq/serializers/testing.py +139 -0
  68. django_cfg/apps/rq/serializers/worker.py +58 -0
  69. django_cfg/apps/rq/services/__init__.py +25 -0
  70. django_cfg/apps/rq/services/config_helper.py +233 -0
  71. django_cfg/apps/rq/services/models/README.md +417 -0
  72. django_cfg/apps/rq/services/models/__init__.py +30 -0
  73. django_cfg/apps/rq/services/models/event.py +123 -0
  74. django_cfg/apps/rq/services/models/job.py +99 -0
  75. django_cfg/apps/rq/services/models/queue.py +92 -0
  76. django_cfg/apps/rq/services/models/worker.py +104 -0
  77. django_cfg/apps/rq/services/rq_converters.py +183 -0
  78. django_cfg/apps/rq/tasks/__init__.py +23 -0
  79. django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
  80. django_cfg/apps/rq/urls.py +54 -0
  81. django_cfg/apps/rq/views/__init__.py +19 -0
  82. django_cfg/apps/rq/views/jobs.py +882 -0
  83. django_cfg/apps/rq/views/monitoring.py +248 -0
  84. django_cfg/apps/rq/views/queues.py +261 -0
  85. django_cfg/apps/rq/views/schedule.py +400 -0
  86. django_cfg/apps/rq/views/testing.py +761 -0
  87. django_cfg/apps/rq/views/workers.py +195 -0
  88. django_cfg/apps/urls.py +13 -8
  89. django_cfg/config.py +106 -0
  90. django_cfg/core/base/config_model.py +16 -26
  91. django_cfg/core/builders/apps_builder.py +7 -11
  92. django_cfg/core/generation/integration_generators/__init__.py +3 -6
  93. django_cfg/core/generation/integration_generators/django_rq.py +80 -0
  94. django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
  95. django_cfg/core/generation/orchestrator.py +15 -15
  96. django_cfg/core/integration/display/startup.py +6 -20
  97. django_cfg/mixins/__init__.py +2 -0
  98. django_cfg/mixins/superadmin_api.py +59 -0
  99. django_cfg/models/__init__.py +3 -3
  100. django_cfg/models/api/grpc/__init__.py +59 -0
  101. django_cfg/models/api/grpc/config.py +364 -0
  102. django_cfg/models/django/__init__.py +3 -3
  103. django_cfg/models/django/django_rq.py +621 -0
  104. django_cfg/models/django/revolution_legacy.py +1 -1
  105. django_cfg/modules/base.py +19 -6
  106. django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
  107. django_cfg/modules/django_admin/config/background_task_config.py +4 -4
  108. django_cfg/modules/django_admin/utils/__init__.py +41 -3
  109. django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
  110. django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
  111. django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
  112. django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
  113. django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
  114. django_cfg/modules/django_admin/utils/html/badges.py +47 -0
  115. django_cfg/modules/django_admin/utils/html/base.py +167 -0
  116. django_cfg/modules/django_admin/utils/html/code.py +87 -0
  117. django_cfg/modules/django_admin/utils/html/composition.py +205 -0
  118. django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
  119. django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
  120. django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
  121. django_cfg/modules/django_admin/utils/html/progress.py +127 -0
  122. django_cfg/modules/django_admin/utils/html_builder.py +55 -408
  123. django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
  124. django_cfg/modules/django_unfold/navigation.py +21 -18
  125. django_cfg/pyproject.toml +4 -6
  126. django_cfg/registry/core.py +4 -7
  127. django_cfg/registry/modules.py +6 -0
  128. django_cfg/static/frontend/admin.zip +0 -0
  129. django_cfg/templates/admin/constance/includes/results_list.html +73 -0
  130. django_cfg/templates/admin/index.html +187 -62
  131. django_cfg/templatetags/django_cfg.py +61 -1
  132. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/METADATA +12 -4
  133. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/RECORD +140 -96
  134. django_cfg/apps/dashboard/permissions.py +0 -48
  135. django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
  136. django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
  137. django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
  138. django_cfg/apps/tasks/__init__.py +0 -64
  139. django_cfg/apps/tasks/admin/__init__.py +0 -4
  140. django_cfg/apps/tasks/admin/task_log.py +0 -265
  141. django_cfg/apps/tasks/apps.py +0 -15
  142. django_cfg/apps/tasks/filters/__init__.py +0 -10
  143. django_cfg/apps/tasks/filters/task_log.py +0 -121
  144. django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
  145. django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
  146. django_cfg/apps/tasks/models/__init__.py +0 -4
  147. django_cfg/apps/tasks/models/task_log.py +0 -246
  148. django_cfg/apps/tasks/serializers/__init__.py +0 -28
  149. django_cfg/apps/tasks/serializers/task_log.py +0 -249
  150. django_cfg/apps/tasks/services/__init__.py +0 -10
  151. django_cfg/apps/tasks/services/client/__init__.py +0 -7
  152. django_cfg/apps/tasks/services/client/client.py +0 -234
  153. django_cfg/apps/tasks/services/config_helper.py +0 -63
  154. django_cfg/apps/tasks/services/sync.py +0 -204
  155. django_cfg/apps/tasks/urls.py +0 -16
  156. django_cfg/apps/tasks/views/__init__.py +0 -10
  157. django_cfg/apps/tasks/views/task_log.py +0 -41
  158. django_cfg/apps/tasks/views/task_log_base.py +0 -41
  159. django_cfg/apps/tasks/views/task_log_overview.py +0 -100
  160. django_cfg/apps/tasks/views/task_log_related.py +0 -41
  161. django_cfg/apps/tasks/views/task_log_stats.py +0 -91
  162. django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
  163. django_cfg/core/generation/integration_generators/django_q2.py +0 -133
  164. django_cfg/core/generation/integration_generators/tasks.py +0 -88
  165. django_cfg/models/django/django_q2.py +0 -514
  166. django_cfg/models/tasks/__init__.py +0 -49
  167. django_cfg/models/tasks/backends.py +0 -122
  168. django_cfg/models/tasks/config.py +0 -209
  169. django_cfg/models/tasks/utils.py +0 -162
  170. django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
  171. django_cfg/modules/django_q2/README.md +0 -140
  172. django_cfg/modules/django_q2/__init__.py +0 -8
  173. django_cfg/modules/django_q2/apps.py +0 -107
  174. django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
  175. django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
  176. /django_cfg/apps/{tasks/migrations → grpc/management/commands}/__init__.py +0 -0
  177. /django_cfg/{modules/django_q2/management → apps/grpc/migrations}/__init__.py +0 -0
  178. /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
  179. /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
  180. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
  181. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
  182. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -209,93 +209,69 @@ class PaymentAdmin(PydanticAdmin):
209
209
  age = timezone.now() - obj.created_at
210
210
  age_text = f"{age.days} days, {age.seconds // 3600} hours"
211
211
 
212
- # Build details list
213
- details = []
214
-
215
- # Basic info
216
- details.append(self.html.inline([
217
- self.html.span("Internal ID:", "font-semibold"),
218
- self.html.span(obj.internal_payment_id, "")
219
- ], separator=" "))
220
-
221
- details.append(self.html.inline([
222
- self.html.span("Age:", "font-semibold"),
223
- self.html.span(age_text, "")
224
- ], separator=" "))
225
-
226
- # Provider info
227
- if obj.provider_payment_id:
228
- details.append(self.html.inline([
229
- self.html.span("Provider Payment ID:", "font-semibold"),
230
- self.html.span(obj.provider_payment_id, "")
231
- ], separator=" "))
232
-
233
212
  # Transaction details
213
+ transaction_value = None
234
214
  if obj.transaction_hash:
235
215
  explorer_link = obj.get_explorer_link()
236
216
  if explorer_link:
237
- details.append(self.html.inline([
238
- self.html.span("Transaction:", "font-semibold"),
239
- self.html.span(f'<a href="{explorer_link}" target="_blank">{obj.transaction_hash[:16]}...</a>', "")
240
- ], separator=" "))
217
+ transaction_value = self.html.link(
218
+ explorer_link,
219
+ f"{obj.transaction_hash[:16]}...",
220
+ target="_blank"
221
+ )
241
222
  else:
242
- details.append(self.html.inline([
243
- self.html.span("Transaction Hash:", "font-semibold"),
244
- self.html.span(obj.transaction_hash, "")
245
- ], separator=" "))
246
-
247
- if obj.confirmations_count > 0:
248
- details.append(self.html.inline([
249
- self.html.span("Confirmations:", "font-semibold"),
223
+ transaction_value = self.html.code(obj.transaction_hash)
224
+
225
+ return self.html.breakdown(
226
+ self.html.key_value("Internal ID", obj.internal_payment_id),
227
+ self.html.key_value("Age", age_text),
228
+ self.html.key_value(
229
+ "Provider Payment ID",
230
+ obj.provider_payment_id
231
+ ) if obj.provider_payment_id else None,
232
+ self.html.key_value(
233
+ "Transaction",
234
+ transaction_value
235
+ ) if obj.transaction_hash else None,
236
+ self.html.key_value(
237
+ "Confirmations",
250
238
  self.html.badge(str(obj.confirmations_count), variant="info", icon=Icons.CHECK_CIRCLE)
251
- ], separator=" "))
252
-
253
- if obj.pay_address:
254
- details.append(self.html.inline([
255
- self.html.span("Pay Address:", "font-semibold"),
256
- self.html.span(f'<code>{obj.pay_address}</code>', "")
257
- ], separator=" "))
258
-
259
- if obj.pay_amount:
260
- details.append(self.html.inline([
261
- self.html.span("Pay Amount:", "font-semibold"),
262
- self.html.span(f'{obj.pay_amount:.8f} {obj.currency.token}', "")
263
- ], separator=" "))
264
-
265
- if obj.actual_amount:
266
- details.append(self.html.inline([
267
- self.html.span("Actual Amount:", "font-semibold"),
268
- self.html.span(f'{obj.actual_amount:.8f} {obj.currency.token}', "")
269
- ], separator=" "))
270
-
271
- # URLs
272
- if obj.payment_url:
273
- details.append(self.html.inline([
274
- self.html.span("Payment URL:", "font-semibold"),
275
- self.html.span(f'<a href="{obj.payment_url}" target="_blank">Open</a>', "")
276
- ], separator=" "))
277
-
278
- # Expiration
279
- if obj.expires_at:
280
- if obj.is_expired:
281
- details.append(self.html.inline([
282
- self.html.span("Expired:", "font-semibold"),
283
- self.html.badge(f"Yes ({obj.expires_at})", variant="danger", icon=Icons.ERROR)
284
- ], separator=" "))
285
- else:
286
- details.append(self.html.inline([
287
- self.html.span("Expires At:", "font-semibold"),
288
- self.html.span(str(obj.expires_at), "")
289
- ], separator=" "))
290
-
291
- # Description
292
- if obj.description:
293
- details.append(self.html.inline([
294
- self.html.span("Description:", "font-semibold"),
295
- self.html.span(obj.description, "")
296
- ], separator=" "))
297
-
298
- return "<br>".join(details)
239
+ ) if obj.confirmations_count > 0 else None,
240
+ self.html.key_value(
241
+ "Pay Address",
242
+ self.html.code(obj.pay_address)
243
+ ) if obj.pay_address else None,
244
+ self.html.key_value(
245
+ "Pay Amount",
246
+ self.html.inline(
247
+ self.html.number(obj.pay_amount, precision=8),
248
+ obj.currency.token,
249
+ separator=" "
250
+ )
251
+ ) if obj.pay_amount else None,
252
+ self.html.key_value(
253
+ "Actual Amount",
254
+ self.html.inline(
255
+ self.html.number(obj.actual_amount, precision=8),
256
+ obj.currency.token,
257
+ separator=" "
258
+ )
259
+ ) if obj.actual_amount else None,
260
+ self.html.key_value(
261
+ "Payment URL",
262
+ self.html.link(obj.payment_url, "Open", target="_blank")
263
+ ) if obj.payment_url else None,
264
+ self.html.key_value(
265
+ "Expired",
266
+ self.html.badge(f"Yes ({obj.expires_at})", variant="danger", icon=Icons.ERROR)
267
+ ) if obj.expires_at and obj.is_expired else (
268
+ self.html.key_value("Expires At", str(obj.expires_at)) if obj.expires_at else None
269
+ ),
270
+ self.html.key_value(
271
+ "Description",
272
+ obj.description
273
+ ) if obj.description else None
274
+ )
299
275
 
300
276
  payment_details_display.short_description = "Payment Details"
301
277
 
@@ -306,8 +282,12 @@ class PaymentAdmin(PydanticAdmin):
306
282
 
307
283
  qr_url = obj.get_qr_code_url(size=200)
308
284
  if qr_url:
309
- return (
310
- f'<img src="{qr_url}" alt="QR Code" style="max-width:200px;"><br>'
311
- f'<small>Scan to pay: <code>{obj.pay_address}</code></small>'
285
+ from django.utils.html import format_html
286
+ img_html = format_html('<img src="{}" alt="QR Code" style="max-width:200px;">', qr_url)
287
+ caption = self.html.inline(
288
+ self.html.text("Scan to pay:", size="sm"),
289
+ self.html.code(obj.pay_address),
290
+ separator=" "
312
291
  )
313
- return self.html.span(f"Address: {obj.pay_address}", "text-sm")
292
+ return self.html.breakdown(img_html, caption)
293
+ return self.html.text(f"Address: {obj.pay_address}", size="sm")
@@ -251,105 +251,70 @@ class WithdrawalRequestAdmin(PydanticAdmin):
251
251
  if not obj.pk:
252
252
  return "Save to see details"
253
253
 
254
- # Build details list
255
- details = []
256
-
257
- details.append(self.html.inline([
258
- self.html.span("Withdrawal ID:", "font-semibold"),
259
- self.html.span(str(obj.id), "")
260
- ], separator=" "))
261
-
262
- details.append(self.html.inline([
263
- self.html.span("User:", "font-semibold"),
264
- self.html.span(f"{obj.user.username} ({obj.user.email})", "")
265
- ], separator=" "))
266
-
267
- details.append(self.html.inline([
268
- self.html.span("Amount:", "font-semibold"),
269
- self.html.span(f"${obj.amount_usd:.2f} USD", "")
270
- ], separator=" "))
271
-
272
- details.append(self.html.inline([
273
- self.html.span("Currency:", "font-semibold"),
274
- self.html.span(obj.currency.code, "")
275
- ], separator=" "))
276
-
277
- details.append(self.html.inline([
278
- self.html.span("Wallet Address:", "font-semibold"),
279
- self.html.span(f"<code>{obj.wallet_address}</code>", "")
280
- ], separator=" "))
281
-
282
- details.append(self.html.inline([
283
- self.html.span("Status:", "font-semibold"),
284
- self.html.span(obj.get_status_display(), "")
285
- ], separator=" "))
286
-
287
- if obj.network_fee_usd:
288
- details.append(self.html.inline([
289
- self.html.span("Network Fee:", "font-semibold"),
290
- self.html.span(f"${obj.network_fee_usd:.2f} USD", "")
291
- ], separator=" "))
292
-
293
- if obj.service_fee_usd:
294
- details.append(self.html.inline([
295
- self.html.span("Service Fee:", "font-semibold"),
296
- self.html.span(f"${obj.service_fee_usd:.2f} USD", "")
297
- ], separator=" "))
298
-
299
- if obj.total_fee_usd:
300
- details.append(self.html.inline([
301
- self.html.span("Total Fee:", "font-semibold"),
302
- self.html.span(f"${obj.total_fee_usd:.2f} USD", "")
303
- ], separator=" "))
304
-
305
- if obj.final_amount_usd:
306
- details.append(self.html.inline([
307
- self.html.span("Final Amount:", "font-semibold"),
308
- self.html.span(f"${obj.final_amount_usd:.2f} USD", "")
309
- ], separator=" "))
310
-
311
- if obj.admin_user:
312
- details.append(self.html.inline([
313
- self.html.span("Approved By:", "font-semibold"),
314
- self.html.span(obj.admin_user.username, "")
315
- ], separator=" "))
316
-
317
- if obj.admin_notes:
318
- details.append(self.html.inline([
319
- self.html.span("Admin Notes:", "font-semibold"),
320
- self.html.span(obj.admin_notes, "")
321
- ], separator=" "))
322
-
323
- if obj.transaction_hash:
324
- details.append(self.html.inline([
325
- self.html.span("Transaction Hash:", "font-semibold"),
326
- self.html.span(f"<code>{obj.transaction_hash}</code>", "")
327
- ], separator=" "))
328
-
329
- if obj.crypto_amount:
330
- details.append(self.html.inline([
331
- self.html.span("Crypto Amount:", "font-semibold"),
332
- self.html.span(f"{obj.crypto_amount:.8f} {obj.currency.token}", "")
333
- ], separator=" "))
334
-
335
- if obj.approved_at:
336
- details.append(self.html.inline([
337
- self.html.span("Approved At:", "font-semibold"),
338
- self.html.span(str(obj.approved_at), "")
339
- ], separator=" "))
340
-
341
- if obj.completed_at:
342
- details.append(self.html.inline([
343
- self.html.span("Completed At:", "font-semibold"),
344
- self.html.span(str(obj.completed_at), "")
345
- ], separator=" "))
346
-
347
- if obj.rejected_at:
348
- details.append(self.html.inline([
349
- self.html.span("Rejected At:", "font-semibold"),
350
- self.html.span(str(obj.rejected_at), "")
351
- ], separator=" "))
352
-
353
- return "<br>".join(details)
254
+ return self.html.breakdown(
255
+ self.html.key_value("Withdrawal ID", str(obj.id)),
256
+ self.html.key_value(
257
+ "User",
258
+ f"{obj.user.username} ({obj.user.email})"
259
+ ),
260
+ self.html.key_value(
261
+ "Amount",
262
+ self.html.number(obj.amount_usd, precision=2, prefix="$", suffix=" USD")
263
+ ),
264
+ self.html.key_value("Currency", obj.currency.code),
265
+ self.html.key_value(
266
+ "Wallet Address",
267
+ self.html.code(obj.wallet_address)
268
+ ),
269
+ self.html.key_value("Status", obj.get_status_display()),
270
+ self.html.key_value(
271
+ "Network Fee",
272
+ self.html.number(obj.network_fee_usd, precision=2, prefix="$", suffix=" USD")
273
+ ) if obj.network_fee_usd else None,
274
+ self.html.key_value(
275
+ "Service Fee",
276
+ self.html.number(obj.service_fee_usd, precision=2, prefix="$", suffix=" USD")
277
+ ) if obj.service_fee_usd else None,
278
+ self.html.key_value(
279
+ "Total Fee",
280
+ self.html.number(obj.total_fee_usd, precision=2, prefix="$", suffix=" USD")
281
+ ) if obj.total_fee_usd else None,
282
+ self.html.key_value(
283
+ "Final Amount",
284
+ self.html.number(obj.final_amount_usd, precision=2, prefix="$", suffix=" USD")
285
+ ) if obj.final_amount_usd else None,
286
+ self.html.key_value(
287
+ "Approved By",
288
+ obj.admin_user.username
289
+ ) if obj.admin_user else None,
290
+ self.html.key_value(
291
+ "Admin Notes",
292
+ obj.admin_notes
293
+ ) if obj.admin_notes else None,
294
+ self.html.key_value(
295
+ "Transaction Hash",
296
+ self.html.code(obj.transaction_hash)
297
+ ) if obj.transaction_hash else None,
298
+ self.html.key_value(
299
+ "Crypto Amount",
300
+ self.html.inline(
301
+ self.html.number(obj.crypto_amount, precision=8),
302
+ obj.currency.token,
303
+ separator=" "
304
+ )
305
+ ) if obj.crypto_amount else None,
306
+ self.html.key_value(
307
+ "Approved At",
308
+ str(obj.approved_at)
309
+ ) if obj.approved_at else None,
310
+ self.html.key_value(
311
+ "Completed At",
312
+ str(obj.completed_at)
313
+ ) if obj.completed_at else None,
314
+ self.html.key_value(
315
+ "Rejected At",
316
+ str(obj.rejected_at)
317
+ ) if obj.rejected_at else None
318
+ )
354
319
 
355
320
  withdrawal_details_display.short_description = "Withdrawal Details"
@@ -0,0 +1,9 @@
1
+ """
2
+ Django-RQ integration for django-cfg.
3
+
4
+ Provides REST API endpoints for monitoring and managing RQ task queues.
5
+ """
6
+
7
+ default_app_config = 'django_cfg.apps.rq.apps.RQAppConfig'
8
+
9
+ __version__ = '1.0.0'
@@ -0,0 +1,80 @@
1
+ """
2
+ AppConfig for Django-RQ integration with monitoring and API capabilities.
3
+
4
+ This app provides REST API endpoints for Django-RQ task queue monitoring,
5
+ management, and statistics. It wraps django-rq's functionality with modern
6
+ DRF ViewSets and unified django-cfg patterns.
7
+
8
+ Features:
9
+ - REST API for monitoring queues, workers, and jobs
10
+ - Prometheus metrics integration
11
+ - Enhanced monitoring interfaces
12
+ - Job management (view, requeue, delete)
13
+ - Integration with django-cfg ecosystem (Centrifugo, auth)
14
+ """
15
+
16
+ from django.apps import AppConfig
17
+
18
+
19
+ class RQAppConfig(AppConfig):
20
+ """
21
+ AppConfig for Django-RQ monitoring and management application.
22
+
23
+ Provides:
24
+ - REST API endpoints for monitoring
25
+ - Prometheus metrics export
26
+ - Job and queue management
27
+ - Worker statistics
28
+ - Integration with django-cfg authentication
29
+
30
+ Usage:
31
+ Add to INSTALLED_APPS:
32
+ INSTALLED_APPS = [
33
+ ...
34
+ 'django_rq', # Required: django-rq core
35
+ 'django_cfg.apps.rq', # Django-CFG RQ monitoring
36
+ ]
37
+
38
+ Configure in django-cfg config:
39
+ class MyConfig(BaseConfig):
40
+ django_rq: DjangoRQConfig = DjangoRQConfig(
41
+ enabled=True,
42
+ queues={
43
+ 'default': {
44
+ 'host': 'localhost',
45
+ 'port': 6379,
46
+ 'db': 0,
47
+ }
48
+ },
49
+ prometheus_enabled=True,
50
+ )
51
+ """
52
+
53
+ default_auto_field = 'django.db.models.BigAutoField'
54
+ name = 'django_cfg.apps.rq'
55
+ verbose_name = 'Django-CFG RQ Monitoring'
56
+ label = 'django_cfg_rq'
57
+
58
+ def ready(self):
59
+ """
60
+ Initialize the app when Django starts.
61
+
62
+ Registers:
63
+ - Admin interfaces (if not already registered)
64
+ - Signal handlers for monitoring
65
+ - Scheduled jobs from config
66
+ """
67
+ # Import admin to register custom admin classes
68
+ try:
69
+ from . import admin # noqa: F401
70
+ except ImportError:
71
+ pass
72
+
73
+ # Register scheduled jobs from config (runs once on startup)
74
+ try:
75
+ from .services import register_schedules_from_config
76
+ register_schedules_from_config()
77
+ except Exception as e:
78
+ from django_cfg.modules.django_logging import get_logger
79
+ logger = get_logger("rq.apps")
80
+ logger.warning(f"Failed to register schedules: {e}")
@@ -0,0 +1 @@
1
+ """Management commands for Django-RQ integration."""
@@ -0,0 +1 @@
1
+ """Django-RQ management commands for django-cfg."""
@@ -0,0 +1,31 @@
1
+ """
2
+ Django-CFG wrapper for django-rq rqscheduler command.
3
+
4
+ Runs the RQ scheduler daemon for scheduled/periodic jobs.
5
+
6
+ Example:
7
+ python manage.py rqscheduler
8
+ python manage.py rqscheduler --queue default
9
+ """
10
+
11
+ from django_rq.management.commands.rqscheduler import Command as DjangoRQSchedulerCommand
12
+
13
+
14
+ class Command(DjangoRQSchedulerCommand):
15
+ """
16
+ Runs the RQ scheduler daemon.
17
+
18
+ The scheduler handles:
19
+ - Scheduled jobs (enqueue at specific time)
20
+ - Periodic jobs (cron-like scheduling)
21
+ - Delayed job execution
22
+
23
+ Inherits all functionality from django-rq's rqscheduler command.
24
+
25
+ Common options:
26
+ --queue QUEUE Queue to schedule jobs on (default: 'default')
27
+ --interval SECONDS Polling interval (default: 1)
28
+ --pid FILE Write PID to file
29
+ """
30
+
31
+ help = 'Runs RQ scheduler daemon for django-cfg (wrapper for django-rq rqscheduler)'
@@ -0,0 +1,33 @@
1
+ """
2
+ Django-CFG wrapper for django-rq rqstats command.
3
+
4
+ Displays real-time statistics about RQ queues and workers.
5
+
6
+ Example:
7
+ python manage.py rqstats
8
+ python manage.py rqstats --interval 5
9
+ """
10
+
11
+ from django_rq.management.commands.rqstats import Command as DjangoRQStatsCommand
12
+
13
+
14
+ class Command(DjangoRQStatsCommand):
15
+ """
16
+ Displays real-time RQ statistics in terminal.
17
+
18
+ Shows:
19
+ - Queue sizes (queued, started, finished, failed)
20
+ - Worker count and status
21
+ - Job processing rates
22
+ - Updates in real-time
23
+
24
+ Inherits all functionality from django-rq's rqstats command.
25
+
26
+ Common options:
27
+ --interval SECONDS Update interval (default: 1)
28
+ --raw Show raw numbers (no colors)
29
+ --only-queues Show only queue statistics
30
+ --only-workers Show only worker statistics
31
+ """
32
+
33
+ help = 'Shows real-time RQ statistics for django-cfg (wrapper for django-rq rqstats)'
@@ -0,0 +1,31 @@
1
+ """
2
+ Django-CFG wrapper for django-rq rqworker command.
3
+
4
+ This is a simple proxy that inherits all functionality from django-rq's rqworker.
5
+ Allows running: python manage.py rqworker [queues]
6
+
7
+ Example:
8
+ python manage.py rqworker default
9
+ python manage.py rqworker high default low
10
+ python manage.py rqworker default --with-scheduler
11
+ """
12
+
13
+ from django_rq.management.commands.rqworker import Command as DjangoRQWorkerCommand
14
+
15
+
16
+ class Command(DjangoRQWorkerCommand):
17
+ """
18
+ Runs RQ workers on specified queues.
19
+
20
+ Inherits all functionality from django-rq's rqworker command.
21
+ See django-rq documentation for available options.
22
+
23
+ Common options:
24
+ --burst Run in burst mode (exit when queue is empty)
25
+ --with-scheduler Run worker with embedded scheduler
26
+ --name NAME Custom worker name
27
+ --worker-ttl SEC Worker timeout (default: 420)
28
+ --sentry-dsn DSN Report exceptions to Sentry
29
+ """
30
+
31
+ help = 'Runs RQ workers for django-cfg (wrapper for django-rq rqworker)'
@@ -0,0 +1,27 @@
1
+ """
2
+ Django-CFG wrapper for django-rq rqworker-pool command.
3
+
4
+ Runs multiple RQ workers in a pool for better performance.
5
+
6
+ Example:
7
+ python manage.py rqworker_pool default --num-workers 4
8
+ python manage.py rqworker_pool high default --num-workers 8
9
+ """
10
+
11
+ from django_rq.management.commands.rqworker_pool import Command as DjangoRQWorkerPoolCommand
12
+
13
+
14
+ class Command(DjangoRQWorkerPoolCommand):
15
+ """
16
+ Runs a pool of RQ workers for improved throughput.
17
+
18
+ Inherits all functionality from django-rq's rqworker-pool command.
19
+ Creates multiple worker processes to handle jobs in parallel.
20
+
21
+ Common options:
22
+ --num-workers N Number of worker processes (default: CPU count)
23
+ --burst Run in burst mode
24
+ --name NAME Worker name prefix
25
+ """
26
+
27
+ help = 'Runs a pool of RQ workers for django-cfg (wrapper for django-rq rqworker-pool)'
@@ -0,0 +1,40 @@
1
+ """
2
+ DRF serializers for Django-RQ monitoring API.
3
+ """
4
+
5
+ from .health import HealthCheckSerializer, RQConfigSerializer
6
+ from .queue import QueueStatsSerializer, QueueDetailSerializer
7
+ from .worker import WorkerSerializer, WorkerStatsSerializer
8
+ from .job import JobListSerializer, JobDetailSerializer, JobActionResponseSerializer
9
+ from .schedule import (
10
+ ScheduleCreateSerializer,
11
+ ScheduledJobSerializer,
12
+ ScheduleActionResponseSerializer,
13
+ )
14
+ from .testing import (
15
+ TestScenarioSerializer,
16
+ RunDemoRequestSerializer,
17
+ StressTestRequestSerializer,
18
+ TestingActionResponseSerializer,
19
+ CleanupRequestSerializer,
20
+ )
21
+
22
+ __all__ = [
23
+ 'HealthCheckSerializer',
24
+ 'RQConfigSerializer',
25
+ 'QueueStatsSerializer',
26
+ 'QueueDetailSerializer',
27
+ 'WorkerSerializer',
28
+ 'WorkerStatsSerializer',
29
+ 'JobListSerializer',
30
+ 'JobDetailSerializer',
31
+ 'JobActionResponseSerializer',
32
+ 'ScheduleCreateSerializer',
33
+ 'ScheduledJobSerializer',
34
+ 'ScheduleActionResponseSerializer',
35
+ 'TestScenarioSerializer',
36
+ 'RunDemoRequestSerializer',
37
+ 'StressTestRequestSerializer',
38
+ 'TestingActionResponseSerializer',
39
+ 'CleanupRequestSerializer',
40
+ ]