django-cfg 1.4.107__py3-none-any.whl → 1.4.108__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/views/profile.py +19 -9
- django_cfg/apps/centrifugo/views/admin_api.py +4 -7
- django_cfg/apps/centrifugo/views/monitoring.py +3 -6
- django_cfg/apps/centrifugo/views/testing_api.py +3 -6
- django_cfg/apps/dashboard/services/system_health_service.py +16 -11
- django_cfg/apps/dashboard/views/activity_views.py +3 -5
- django_cfg/apps/dashboard/views/apizones_views.py +4 -5
- django_cfg/apps/dashboard/views/charts_views.py +4 -5
- django_cfg/apps/dashboard/views/overview_views.py +4 -5
- django_cfg/apps/dashboard/views/statistics_views.py +4 -5
- django_cfg/apps/dashboard/views/system_views.py +4 -5
- django_cfg/apps/knowbase/__init__.py +2 -2
- django_cfg/apps/knowbase/apps.py +2 -8
- django_cfg/apps/knowbase/views/base.py +9 -4
- django_cfg/apps/support/views/api.py +16 -7
- django_cfg/apps/tasks/__init__.py +61 -2
- django_cfg/apps/tasks/admin/__init__.py +3 -10
- django_cfg/apps/tasks/admin/config.py +98 -0
- django_cfg/apps/tasks/admin/task_log.py +265 -0
- django_cfg/apps/tasks/apps.py +7 -9
- django_cfg/apps/tasks/filters/__init__.py +10 -0
- django_cfg/apps/tasks/filters/task_log.py +121 -0
- django_cfg/apps/tasks/migrations/0001_initial.py +196 -0
- django_cfg/apps/tasks/models/__init__.py +4 -0
- django_cfg/apps/tasks/models/task_log.py +246 -0
- django_cfg/apps/tasks/serializers/__init__.py +28 -0
- django_cfg/apps/tasks/serializers/task_log.py +249 -0
- django_cfg/apps/tasks/services/__init__.py +10 -0
- django_cfg/apps/tasks/services/client/__init__.py +7 -0
- django_cfg/apps/tasks/services/client/client.py +234 -0
- django_cfg/apps/tasks/services/config_helper.py +63 -0
- django_cfg/apps/tasks/services/sync.py +204 -0
- django_cfg/apps/tasks/urls.py +7 -13
- django_cfg/apps/tasks/views/__init__.py +4 -10
- django_cfg/apps/tasks/views/task_log.py +41 -0
- django_cfg/apps/tasks/views/task_log_base.py +41 -0
- django_cfg/apps/tasks/views/task_log_overview.py +100 -0
- django_cfg/apps/tasks/views/task_log_related.py +41 -0
- django_cfg/apps/tasks/views/task_log_stats.py +91 -0
- django_cfg/apps/tasks/views/task_log_timeline.py +81 -0
- django_cfg/apps/urls.py +0 -1
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/cli/utils.py +1 -1
- django_cfg/core/base/config_model.py +1 -1
- django_cfg/core/builders/apps_builder.py +1 -1
- django_cfg/core/generation/integration_generators/__init__.py +1 -1
- django_cfg/core/generation/integration_generators/tasks.py +14 -18
- django_cfg/core/generation/security_generators/crypto_fields.py +2 -1
- django_cfg/core/integration/display/startup.py +1 -1
- django_cfg/mixins/__init__.py +12 -0
- django_cfg/mixins/admin_api.py +37 -0
- django_cfg/mixins/client_api.py +39 -0
- django_cfg/models/django/constance.py +2 -8
- django_cfg/models/django/crypto_fields.py +13 -48
- django_cfg/models/tasks/__init__.py +8 -10
- django_cfg/models/tasks/backends.py +76 -207
- django_cfg/models/tasks/config.py +20 -127
- django_cfg/models/tasks/utils.py +17 -29
- django_cfg/modules/django_client/management/commands/generate_client.py +13 -1
- django_cfg/modules/django_unfold/navigation.py +121 -22
- django_cfg/pyproject.toml +2 -2
- django_cfg/registry/core.py +1 -1
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.108.dist-info}/METADATA +3 -3
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.108.dist-info}/RECORD +70 -107
- django_cfg/apps/tasks/admin/actions.py +0 -29
- django_cfg/apps/tasks/admin/tasks_admin.py +0 -154
- django_cfg/apps/tasks/api/serializers.py +0 -82
- django_cfg/apps/tasks/api/views.py +0 -571
- django_cfg/apps/tasks/serializers.py +0 -82
- django_cfg/apps/tasks/static/tasks/css/dashboard-alpine.css +0 -299
- django_cfg/apps/tasks/static/tasks/css/dashboard.css +0 -120
- django_cfg/apps/tasks/static/tasks/js/alpine/README.md +0 -47
- django_cfg/apps/tasks/static/tasks/js/alpine/actions/index.js +0 -8
- django_cfg/apps/tasks/static/tasks/js/alpine/actions/management.js +0 -123
- django_cfg/apps/tasks/static/tasks/js/alpine/actions/pagination.js +0 -21
- django_cfg/apps/tasks/static/tasks/js/alpine/actions/tasks.js +0 -101
- django_cfg/apps/tasks/static/tasks/js/alpine/actions/workers.js +0 -59
- django_cfg/apps/tasks/static/tasks/js/alpine/computed.js +0 -35
- django_cfg/apps/tasks/static/tasks/js/alpine/index.js +0 -148
- django_cfg/apps/tasks/static/tasks/js/alpine/loaders/index.js +0 -36
- django_cfg/apps/tasks/static/tasks/js/alpine/loaders/overview.js +0 -37
- django_cfg/apps/tasks/static/tasks/js/alpine/loaders/queues.js +0 -27
- django_cfg/apps/tasks/static/tasks/js/alpine/loaders/tasks.js +0 -32
- django_cfg/apps/tasks/static/tasks/js/alpine/loaders/workers.js +0 -21
- django_cfg/apps/tasks/static/tasks/js/alpine/state.js +0 -36
- django_cfg/apps/tasks/static/tasks/js/alpine/utils/formatters.js +0 -42
- django_cfg/apps/tasks/static/tasks/js/alpine/utils/helpers.js +0 -68
- django_cfg/apps/tasks/static/tasks/js/dashboard-alpine.js +0 -725
- django_cfg/apps/tasks/tasks/__init__.py +0 -10
- django_cfg/apps/tasks/tasks/demo_tasks.py +0 -127
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +0 -71
- django_cfg/apps/tasks/templates/tasks/components/overview_content.html +0 -94
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +0 -44
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +0 -45
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -151
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +0 -61
- django_cfg/apps/tasks/templates/tasks/components/tasks_mjs_integration.html +0 -269
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +0 -60
- django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -20
- django_cfg/apps/tasks/templates/tasks/pages/dashboard-improved.html +0 -168
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +0 -77
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +0 -40
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +0 -40
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +0 -86
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +0 -90
- django_cfg/apps/tasks/urls_admin.py +0 -15
- django_cfg/apps/tasks/utils/__init__.py +0 -1
- django_cfg/apps/tasks/utils/simulator.py +0 -353
- django_cfg/apps/tasks/views/api.py +0 -571
- django_cfg/apps/tasks/views/dashboard.py +0 -89
- django_cfg/management/commands/rundramatiq.py +0 -24
- django_cfg/management/commands/rundramatiq_simulator.py +0 -22
- django_cfg/management/commands/task_clear.py +0 -25
- django_cfg/management/commands/task_status.py +0 -24
- django_cfg/modules/django_tasks/__init__.py +0 -29
- django_cfg/modules/django_tasks/dramatiq_setup.py +0 -20
- django_cfg/modules/django_tasks/factory.py +0 -127
- django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq.py +0 -253
- django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +0 -436
- django_cfg/modules/django_tasks/management/commands/task_clear.py +0 -226
- django_cfg/modules/django_tasks/management/commands/task_status.py +0 -257
- django_cfg/modules/django_tasks/service.py +0 -281
- django_cfg/modules/django_tasks/settings.py +0 -107
- /django_cfg/{modules/django_tasks/management → apps/tasks/migrations}/__init__.py +0 -0
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.108.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.108.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.108.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Task admin actions.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from django.contrib import messages
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def retry_failed_tasks(modeladmin, request, queryset):
|
|
9
|
-
"""Retry selected failed tasks."""
|
|
10
|
-
failed_tasks = queryset.filter(status='failed')
|
|
11
|
-
count = failed_tasks.count()
|
|
12
|
-
|
|
13
|
-
if count > 0:
|
|
14
|
-
# Here you would implement the retry logic
|
|
15
|
-
messages.success(request, f"Queued {count} tasks for retry.")
|
|
16
|
-
else:
|
|
17
|
-
messages.warning(request, "No failed tasks selected.")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def cancel_pending_tasks(modeladmin, request, queryset):
|
|
21
|
-
"""Cancel selected pending tasks."""
|
|
22
|
-
pending_tasks = queryset.filter(status='pending')
|
|
23
|
-
count = pending_tasks.count()
|
|
24
|
-
|
|
25
|
-
if count > 0:
|
|
26
|
-
pending_tasks.update(status='cancelled')
|
|
27
|
-
messages.success(request, f"Cancelled {count} pending tasks.")
|
|
28
|
-
else:
|
|
29
|
-
messages.warning(request, "No pending tasks selected.")
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Tasks Admin v2.0 - NEW Declarative Pydantic Approach
|
|
3
|
-
|
|
4
|
-
Enhanced Dramatiq task management with Material Icons and auto-generated displays.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import logging
|
|
8
|
-
from django.contrib import admin, messages
|
|
9
|
-
from django.contrib.admin.views.main import ChangeList
|
|
10
|
-
from django.db.models import Count
|
|
11
|
-
|
|
12
|
-
from django_cfg.modules.django_admin import (
|
|
13
|
-
ActionConfig,
|
|
14
|
-
AdminConfig,
|
|
15
|
-
BadgeField,
|
|
16
|
-
DateTimeField,
|
|
17
|
-
FieldsetConfig,
|
|
18
|
-
Icons,
|
|
19
|
-
)
|
|
20
|
-
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
21
|
-
from django_cfg.modules.django_tasks import DjangoTasks
|
|
22
|
-
from .actions import retry_failed_tasks, cancel_pending_tasks
|
|
23
|
-
|
|
24
|
-
try:
|
|
25
|
-
from django_dramatiq.admin import TaskAdmin as BaseDramatiqTaskAdmin
|
|
26
|
-
from django_dramatiq.models import Task
|
|
27
|
-
DRAMATIQ_AVAILABLE = True
|
|
28
|
-
except ImportError:
|
|
29
|
-
Task = None
|
|
30
|
-
BaseDramatiqTaskAdmin = None
|
|
31
|
-
DRAMATIQ_AVAILABLE = False
|
|
32
|
-
|
|
33
|
-
if DRAMATIQ_AVAILABLE:
|
|
34
|
-
|
|
35
|
-
class TaskQueueChangeList(ChangeList):
|
|
36
|
-
"""Custom changelist for task queue management."""
|
|
37
|
-
def __init__(self, *args, **kwargs):
|
|
38
|
-
super().__init__(*args, **kwargs)
|
|
39
|
-
self.tasks_service = DjangoTasks()
|
|
40
|
-
|
|
41
|
-
task_config = AdminConfig(
|
|
42
|
-
model=Task,
|
|
43
|
-
list_display=["id", "actor_name", "status", "queue_name", "created_at"],
|
|
44
|
-
display_fields=[
|
|
45
|
-
BadgeField(name="id", title="Task ID", variant="info", icon=Icons.TAG),
|
|
46
|
-
BadgeField(name="actor_name", title="Actor", variant="secondary", icon=Icons.FUNCTIONS, empty_value="Unknown"),
|
|
47
|
-
BadgeField(name="status", title="Status", label_map={
|
|
48
|
-
"pending": "warning", "running": "info", "done": "success",
|
|
49
|
-
"failed": "danger", "cancelled": "secondary"
|
|
50
|
-
}),
|
|
51
|
-
BadgeField(name="queue_name", title="Queue", variant="info", icon=Icons.QUEUE, empty_value="default"),
|
|
52
|
-
DateTimeField(name="created_at", title="Created", ordering="created_at"),
|
|
53
|
-
],
|
|
54
|
-
search_fields=["actor_name", "queue_name", "message_id"],
|
|
55
|
-
list_filter=["status", "queue_name", "actor_name", "created_at", "updated_at"],
|
|
56
|
-
readonly_fields=["id", "actor_name", "queue_name", "args_preview", "kwargs_preview",
|
|
57
|
-
"result_preview", "created_at", "duration_display", "retries_display", "error_message_display"],
|
|
58
|
-
fieldsets=[
|
|
59
|
-
FieldsetConfig(title="Task Information", fields=["status", "actor_name", "queue_name"]),
|
|
60
|
-
FieldsetConfig(title="Execution Details", fields=["args_preview", "kwargs_preview", "result_preview", "error_message_display"]),
|
|
61
|
-
FieldsetConfig(title="Timing", fields=["created_at", "duration_display"]),
|
|
62
|
-
FieldsetConfig(title="Retry Information", fields=["retries_display"]),
|
|
63
|
-
],
|
|
64
|
-
actions=[
|
|
65
|
-
ActionConfig(name="retry_failed_tasks", description="Retry failed tasks",
|
|
66
|
-
variant="warning", handler=retry_failed_tasks),
|
|
67
|
-
ActionConfig(name="cancel_pending_tasks", description="Cancel pending tasks",
|
|
68
|
-
variant="danger", handler=cancel_pending_tasks),
|
|
69
|
-
],
|
|
70
|
-
ordering=["-created_at"],
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
try:
|
|
74
|
-
admin.site.unregister(Task)
|
|
75
|
-
except admin.sites.NotRegistered:
|
|
76
|
-
pass
|
|
77
|
-
|
|
78
|
-
@admin.register(Task)
|
|
79
|
-
class TaskAdmin(PydanticAdmin):
|
|
80
|
-
"""Enhanced admin for Dramatiq Task model."""
|
|
81
|
-
config = task_config
|
|
82
|
-
|
|
83
|
-
def has_add_permission(self, request):
|
|
84
|
-
return False
|
|
85
|
-
|
|
86
|
-
def has_delete_permission(self, request, obj=None):
|
|
87
|
-
return False
|
|
88
|
-
|
|
89
|
-
def get_changelist(self, request, **kwargs):
|
|
90
|
-
return TaskQueueChangeList
|
|
91
|
-
|
|
92
|
-
# Custom readonly methods (for detail view)
|
|
93
|
-
def args_preview(self, obj):
|
|
94
|
-
if hasattr(obj, 'args') and obj.args:
|
|
95
|
-
args_text = str(obj.args)
|
|
96
|
-
return args_text[:100] + "..." if len(args_text) > 100 else args_text
|
|
97
|
-
return "No arguments"
|
|
98
|
-
args_preview.short_description = "Arguments"
|
|
99
|
-
|
|
100
|
-
def kwargs_preview(self, obj):
|
|
101
|
-
if hasattr(obj, 'kwargs') and obj.kwargs:
|
|
102
|
-
kwargs_text = str(obj.kwargs)
|
|
103
|
-
return kwargs_text[:100] + "..." if len(kwargs_text) > 100 else kwargs_text
|
|
104
|
-
return "No kwargs"
|
|
105
|
-
kwargs_preview.short_description = "Keyword Arguments"
|
|
106
|
-
|
|
107
|
-
def result_preview(self, obj):
|
|
108
|
-
if hasattr(obj, 'result') and obj.result:
|
|
109
|
-
result_text = str(obj.result)
|
|
110
|
-
return result_text[:100] + "..." if len(result_text) > 100 else result_text
|
|
111
|
-
return "No result"
|
|
112
|
-
result_preview.short_description = "Result"
|
|
113
|
-
|
|
114
|
-
def duration_display(self, obj):
|
|
115
|
-
if obj.started_at and obj.finished_at:
|
|
116
|
-
duration = obj.finished_at - obj.started_at
|
|
117
|
-
return f"{duration.total_seconds():.2f}s"
|
|
118
|
-
return "N/A"
|
|
119
|
-
duration_display.short_description = "Duration"
|
|
120
|
-
|
|
121
|
-
def retries_display(self, obj):
|
|
122
|
-
return str(getattr(obj, 'retries', 0))
|
|
123
|
-
retries_display.short_description = "Retries"
|
|
124
|
-
|
|
125
|
-
def error_message_display(self, obj):
|
|
126
|
-
if hasattr(obj, 'error') and obj.error:
|
|
127
|
-
error_text = str(obj.error)
|
|
128
|
-
return error_text[:100] + "..." if len(error_text) > 100 else error_text
|
|
129
|
-
return "No error"
|
|
130
|
-
error_message_display.short_description = "Error"
|
|
131
|
-
|
|
132
|
-
def changelist_view(self, request, extra_context=None):
|
|
133
|
-
extra_context = extra_context or {}
|
|
134
|
-
try:
|
|
135
|
-
total_tasks = self.get_queryset(request).count()
|
|
136
|
-
status_stats = self.get_queryset(request).values('status').annotate(count=Count('id')).order_by('status')
|
|
137
|
-
actor_stats = self.get_queryset(request).values('actor_name').annotate(count=Count('id')).order_by('-count')[:10]
|
|
138
|
-
queue_stats = self.get_queryset(request).values('queue_name').annotate(count=Count('id')).order_by('-count')
|
|
139
|
-
extra_context.update({
|
|
140
|
-
'task_statistics': {
|
|
141
|
-
'total_tasks': total_tasks,
|
|
142
|
-
'status_distribution': list(status_stats),
|
|
143
|
-
'top_actors': list(actor_stats),
|
|
144
|
-
'queue_distribution': list(queue_stats),
|
|
145
|
-
}
|
|
146
|
-
})
|
|
147
|
-
except Exception as e:
|
|
148
|
-
extra_context['task_error'] = str(e)
|
|
149
|
-
return super().changelist_view(request, extra_context)
|
|
150
|
-
|
|
151
|
-
else:
|
|
152
|
-
class TaskAdmin:
|
|
153
|
-
"""Placeholder when django-dramatiq is not available."""
|
|
154
|
-
pass
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Serializers for Django CFG Tasks app.
|
|
3
|
-
|
|
4
|
-
Provides DRF serializers for task management API endpoints.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from rest_framework import serializers
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class QueueStatusSerializer(serializers.Serializer):
|
|
12
|
-
"""Serializer for queue status data."""
|
|
13
|
-
|
|
14
|
-
queues = serializers.DictField(
|
|
15
|
-
child=serializers.DictField(
|
|
16
|
-
child=serializers.IntegerField()
|
|
17
|
-
),
|
|
18
|
-
help_text="Queue information with pending/failed counts"
|
|
19
|
-
)
|
|
20
|
-
workers = serializers.IntegerField(help_text="Number of active workers")
|
|
21
|
-
redis_connected = serializers.BooleanField(help_text="Redis connection status")
|
|
22
|
-
timestamp = serializers.CharField(help_text="Current timestamp")
|
|
23
|
-
error = serializers.CharField(required=False, help_text="Error message if any")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class TaskStatisticsSerializer(serializers.Serializer):
|
|
27
|
-
"""Serializer for task statistics data."""
|
|
28
|
-
|
|
29
|
-
statistics = serializers.DictField(
|
|
30
|
-
child=serializers.IntegerField(),
|
|
31
|
-
help_text="Task count statistics"
|
|
32
|
-
)
|
|
33
|
-
recent_tasks = serializers.ListField(
|
|
34
|
-
child=serializers.DictField(),
|
|
35
|
-
help_text="List of recent tasks"
|
|
36
|
-
)
|
|
37
|
-
timestamp = serializers.CharField(help_text="Current timestamp")
|
|
38
|
-
error = serializers.CharField(required=False, help_text="Error message if any")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class WorkerActionSerializer(serializers.Serializer):
|
|
42
|
-
"""Serializer for worker management actions."""
|
|
43
|
-
|
|
44
|
-
action = serializers.ChoiceField(
|
|
45
|
-
choices=['start', 'stop', 'restart'],
|
|
46
|
-
help_text="Action to perform on workers"
|
|
47
|
-
)
|
|
48
|
-
processes = serializers.IntegerField(
|
|
49
|
-
default=1,
|
|
50
|
-
min_value=1,
|
|
51
|
-
max_value=10,
|
|
52
|
-
help_text="Number of worker processes"
|
|
53
|
-
)
|
|
54
|
-
threads = serializers.IntegerField(
|
|
55
|
-
default=2,
|
|
56
|
-
min_value=1,
|
|
57
|
-
max_value=20,
|
|
58
|
-
help_text="Number of threads per process"
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class QueueActionSerializer(serializers.Serializer):
|
|
63
|
-
"""Serializer for queue management actions."""
|
|
64
|
-
|
|
65
|
-
action = serializers.ChoiceField(
|
|
66
|
-
choices=['clear', 'clear_all', 'purge', 'purge_failed', 'flush'],
|
|
67
|
-
help_text="Action to perform on queues"
|
|
68
|
-
)
|
|
69
|
-
queue_names = serializers.ListField(
|
|
70
|
-
child=serializers.CharField(),
|
|
71
|
-
required=False,
|
|
72
|
-
help_text="Specific queues to target (empty = all queues)"
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class APIResponseSerializer(serializers.Serializer):
|
|
77
|
-
"""Standard API response serializer."""
|
|
78
|
-
|
|
79
|
-
success = serializers.BooleanField(help_text="Operation success status")
|
|
80
|
-
message = serializers.CharField(required=False, help_text="Success message")
|
|
81
|
-
error = serializers.CharField(required=False, help_text="Error message")
|
|
82
|
-
data = serializers.DictField(required=False, help_text="Response data")
|