django-cfg 1.4.106__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_admin/RESOURCE_CONFIG_ENHANCEMENT.md +350 -0
- django_cfg/modules/django_admin/__init__.py +4 -0
- django_cfg/modules/django_admin/base/pydantic_admin.py +70 -15
- django_cfg/modules/django_admin/config/__init__.py +4 -0
- django_cfg/modules/django_admin/config/admin_config.py +13 -1
- django_cfg/modules/django_admin/config/background_task_config.py +76 -0
- django_cfg/modules/django_admin/config/resource_config.py +129 -0
- 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.106.dist-info → django_cfg-1.4.108.dist-info}/METADATA +5 -3
- {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/RECORD +77 -111
- 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.106.dist-info → django_cfg-1.4.108.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.106.dist-info → django_cfg-1.4.108.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# ResourceConfig & BackgroundTaskConfig Enhancement
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Enhanced django-admin module with declarative import/export configuration and background task support.
|
|
6
|
+
|
|
7
|
+
## New Features
|
|
8
|
+
|
|
9
|
+
### 1. ResourceConfig
|
|
10
|
+
|
|
11
|
+
Declarative configuration for django-import-export Resource classes.
|
|
12
|
+
|
|
13
|
+
**Location:** `django_cfg/modules/django_admin/config/resource_config.py`
|
|
14
|
+
|
|
15
|
+
**Features:**
|
|
16
|
+
- Field selection and exclusion
|
|
17
|
+
- Import ID fields configuration
|
|
18
|
+
- Import/export behavior control
|
|
19
|
+
- Hook support (before/after import, per-row hooks)
|
|
20
|
+
- Export field ordering
|
|
21
|
+
- Batch processing options
|
|
22
|
+
|
|
23
|
+
**Example:**
|
|
24
|
+
```python
|
|
25
|
+
from django_cfg.modules.django_admin import ResourceConfig
|
|
26
|
+
|
|
27
|
+
resource_config = ResourceConfig(
|
|
28
|
+
fields=['host', 'port', 'username', 'password'],
|
|
29
|
+
exclude=['metadata', 'config'],
|
|
30
|
+
import_id_fields=['host', 'port'],
|
|
31
|
+
skip_unchanged=True,
|
|
32
|
+
after_import_row='apps.myapp.tasks.test_after_import',
|
|
33
|
+
batch_size=100,
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. BackgroundTaskConfig
|
|
38
|
+
|
|
39
|
+
Configuration for background task processing.
|
|
40
|
+
|
|
41
|
+
**Location:** `django_cfg/modules/django_admin/config/background_task_config.py`
|
|
42
|
+
|
|
43
|
+
**Features:**
|
|
44
|
+
- Task runner selection (rearq, celery, django_q, sync)
|
|
45
|
+
- Batch size configuration
|
|
46
|
+
- Timeout settings
|
|
47
|
+
- Retry policy
|
|
48
|
+
- Priority levels
|
|
49
|
+
|
|
50
|
+
**Example:**
|
|
51
|
+
```python
|
|
52
|
+
from django_cfg.modules.django_admin import BackgroundTaskConfig
|
|
53
|
+
|
|
54
|
+
background_config = BackgroundTaskConfig(
|
|
55
|
+
enabled=True,
|
|
56
|
+
task_runner='rearq',
|
|
57
|
+
batch_size=50,
|
|
58
|
+
timeout=300,
|
|
59
|
+
retry_on_failure=True,
|
|
60
|
+
max_retries=3,
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Enhanced AdminConfig
|
|
65
|
+
|
|
66
|
+
**Updated:** `django_cfg/modules/django_admin/config/admin_config.py`
|
|
67
|
+
|
|
68
|
+
**New Fields:**
|
|
69
|
+
```python
|
|
70
|
+
class AdminConfig(BaseModel):
|
|
71
|
+
# ... existing fields ...
|
|
72
|
+
|
|
73
|
+
# Import/Export enhancement
|
|
74
|
+
resource_config: Optional[ResourceConfig] = None
|
|
75
|
+
|
|
76
|
+
# Background task processing
|
|
77
|
+
background_task_config: Optional[BackgroundTaskConfig] = None
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 4. Enhanced PydanticAdmin
|
|
81
|
+
|
|
82
|
+
**Updated:** `django_cfg/modules/django_admin/base/pydantic_admin.py`
|
|
83
|
+
|
|
84
|
+
**Key Changes:**
|
|
85
|
+
- `_generate_resource_class()` now uses ResourceConfig if provided
|
|
86
|
+
- Auto-generates Resource with hooks support
|
|
87
|
+
- Dynamically attaches hook methods to Resource class
|
|
88
|
+
|
|
89
|
+
**Hook Support:**
|
|
90
|
+
- `before_import` - Called before import starts
|
|
91
|
+
- `after_import` - Called after import completes
|
|
92
|
+
- `before_import_row` - Called before each row import
|
|
93
|
+
- `after_import_row` - Called after each row import (★ most useful)
|
|
94
|
+
|
|
95
|
+
## Usage Example
|
|
96
|
+
|
|
97
|
+
### Complete Proxy Admin with Import/Export
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from django_cfg.modules.django_admin import (
|
|
101
|
+
ActionConfig,
|
|
102
|
+
AdminConfig,
|
|
103
|
+
BackgroundTaskConfig,
|
|
104
|
+
ResourceConfig,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
proxy_config = AdminConfig(
|
|
108
|
+
model=Proxy,
|
|
109
|
+
|
|
110
|
+
# Enable import/export with ResourceConfig
|
|
111
|
+
import_export_enabled=True,
|
|
112
|
+
resource_config=ResourceConfig(
|
|
113
|
+
fields=[
|
|
114
|
+
'host', 'port', 'proxy_type', 'proxy_mode',
|
|
115
|
+
'username', 'password',
|
|
116
|
+
'provider', 'country',
|
|
117
|
+
],
|
|
118
|
+
exclude=['metadata', 'config', 'last_error'],
|
|
119
|
+
import_id_fields=['host', 'port', 'provider'],
|
|
120
|
+
skip_unchanged=True,
|
|
121
|
+
# Auto-test after import
|
|
122
|
+
after_import_row='apps.proxies.tasks.after_import_row_test_proxy',
|
|
123
|
+
),
|
|
124
|
+
|
|
125
|
+
# Background task configuration
|
|
126
|
+
background_task_config=BackgroundTaskConfig(
|
|
127
|
+
enabled=True,
|
|
128
|
+
task_runner='rearq',
|
|
129
|
+
batch_size=50,
|
|
130
|
+
timeout=300,
|
|
131
|
+
),
|
|
132
|
+
|
|
133
|
+
# Admin actions
|
|
134
|
+
actions=[
|
|
135
|
+
ActionConfig(
|
|
136
|
+
name='test_selected_proxies',
|
|
137
|
+
description='Test selected proxies',
|
|
138
|
+
variant='warning',
|
|
139
|
+
icon='speed',
|
|
140
|
+
handler='apps.proxies.admin.actions.test_selected_proxies',
|
|
141
|
+
),
|
|
142
|
+
],
|
|
143
|
+
|
|
144
|
+
list_display=['host', 'port', 'status', 'success_rate'],
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
@admin.register(Proxy)
|
|
148
|
+
class ProxyAdmin(PydanticAdmin):
|
|
149
|
+
config = proxy_config
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Hook Implementation
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# apps/proxies/tasks.py
|
|
156
|
+
|
|
157
|
+
def after_import_row_test_proxy(row, row_result, **kwargs):
|
|
158
|
+
"""Hook called after each proxy import."""
|
|
159
|
+
|
|
160
|
+
# Skip if dry run
|
|
161
|
+
if kwargs.get('dry_run'):
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
# Only test new proxies
|
|
165
|
+
if row_result.import_type == 'new':
|
|
166
|
+
proxy = row_result.instance
|
|
167
|
+
|
|
168
|
+
# Queue async test
|
|
169
|
+
from api.workers import get_worker
|
|
170
|
+
worker = get_worker()
|
|
171
|
+
worker.enqueue_task(
|
|
172
|
+
'apps.proxies.tasks.test_proxy_async',
|
|
173
|
+
proxy_id=str(proxy.id)
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Benefits
|
|
178
|
+
|
|
179
|
+
### Before (Manual Resource Class)
|
|
180
|
+
```python
|
|
181
|
+
from import_export import resources
|
|
182
|
+
|
|
183
|
+
class ProxyResource(resources.ModelResource):
|
|
184
|
+
class Meta:
|
|
185
|
+
model = Proxy
|
|
186
|
+
fields = ('host', 'port', 'username', 'password')
|
|
187
|
+
import_id_fields = ['host', 'port']
|
|
188
|
+
skip_unchanged = True
|
|
189
|
+
|
|
190
|
+
def after_import_row(self, row, row_result, **kwargs):
|
|
191
|
+
# Custom logic here
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
proxy_config = AdminConfig(
|
|
195
|
+
model=Proxy,
|
|
196
|
+
import_export_enabled=True,
|
|
197
|
+
resource_class=ProxyResource, # Manual class
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### After (Declarative ResourceConfig)
|
|
202
|
+
```python
|
|
203
|
+
proxy_config = AdminConfig(
|
|
204
|
+
model=Proxy,
|
|
205
|
+
import_export_enabled=True,
|
|
206
|
+
resource_config=ResourceConfig(
|
|
207
|
+
fields=['host', 'port', 'username', 'password'],
|
|
208
|
+
import_id_fields=['host', 'port'],
|
|
209
|
+
skip_unchanged=True,
|
|
210
|
+
after_import_row='apps.proxies.tasks.after_import_row_test_proxy',
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Advantages:**
|
|
216
|
+
- ✅ No separate Resource class needed
|
|
217
|
+
- ✅ All configuration in one place
|
|
218
|
+
- ✅ Type-safe with Pydantic validation
|
|
219
|
+
- ✅ Reusable across multiple admins
|
|
220
|
+
- ✅ Hook as string path (lazy import)
|
|
221
|
+
- ✅ Auto-generated Resource with full control
|
|
222
|
+
|
|
223
|
+
## Dependencies Added
|
|
224
|
+
|
|
225
|
+
**pyproject.toml updates:**
|
|
226
|
+
```toml
|
|
227
|
+
dependencies = [
|
|
228
|
+
# ... existing ...
|
|
229
|
+
"pytz>=2025.1",
|
|
230
|
+
"httpx>=0.28.1,<1.0",
|
|
231
|
+
]
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
- `pytz` - Timezone support for datetime operations
|
|
235
|
+
- `httpx` - Modern HTTP client for proxy testing
|
|
236
|
+
|
|
237
|
+
## Files Changed
|
|
238
|
+
|
|
239
|
+
### django-cfg Core
|
|
240
|
+
1. `config/resource_config.py` - NEW
|
|
241
|
+
2. `config/background_task_config.py` - NEW
|
|
242
|
+
3. `config/admin_config.py` - MODIFIED (added new configs)
|
|
243
|
+
4. `config/__init__.py` - MODIFIED (exports)
|
|
244
|
+
5. `base/pydantic_admin.py` - MODIFIED (Resource generation)
|
|
245
|
+
6. `__init__.py` - MODIFIED (exports)
|
|
246
|
+
7. `pyproject.toml` - MODIFIED (dependencies)
|
|
247
|
+
|
|
248
|
+
### stockapis Implementation
|
|
249
|
+
1. `apps/proxies/admin/proxy_admin.py` - Uses ResourceConfig
|
|
250
|
+
2. `apps/proxies/admin/actions.py` - NEW (admin actions)
|
|
251
|
+
3. `apps/proxies/services/proxy_tester.py` - NEW (testing logic)
|
|
252
|
+
4. `apps/proxies/tasks.py` - NEW (background tasks)
|
|
253
|
+
5. `apps/proxies/@docs/IMPORT_EXPORT_SETUP.md` - NEW (docs)
|
|
254
|
+
|
|
255
|
+
## Backward Compatibility
|
|
256
|
+
|
|
257
|
+
✅ **100% Backward Compatible**
|
|
258
|
+
|
|
259
|
+
Old code still works:
|
|
260
|
+
```python
|
|
261
|
+
# Old way - still works
|
|
262
|
+
proxy_config = AdminConfig(
|
|
263
|
+
model=Proxy,
|
|
264
|
+
import_export_enabled=True,
|
|
265
|
+
resource_class=MyCustomResource, # Manual class
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# New way - alternative
|
|
269
|
+
proxy_config = AdminConfig(
|
|
270
|
+
model=Proxy,
|
|
271
|
+
import_export_enabled=True,
|
|
272
|
+
resource_config=ResourceConfig(...), # Declarative
|
|
273
|
+
)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Priority:
|
|
277
|
+
1. If `resource_class` provided → use it (legacy)
|
|
278
|
+
2. If `resource_config` provided → auto-generate Resource
|
|
279
|
+
3. If neither → auto-generate basic Resource
|
|
280
|
+
|
|
281
|
+
## Testing
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
# Test django-cfg imports
|
|
285
|
+
cd django-cfg-dev
|
|
286
|
+
poetry run python -c "from django_cfg.modules.django_admin import ResourceConfig, BackgroundTaskConfig; print('✅ OK')"
|
|
287
|
+
|
|
288
|
+
# Test stockapis admin
|
|
289
|
+
cd stockapis
|
|
290
|
+
poetry run python manage.py check --tag admin
|
|
291
|
+
|
|
292
|
+
# Test in browser
|
|
293
|
+
poetry run python manage.py runserver
|
|
294
|
+
# Visit: http://localhost:8000/admin/proxies/proxy/
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Future Enhancements
|
|
298
|
+
|
|
299
|
+
- [ ] `ResourceConfig.field_widgets` - Custom widgets for fields
|
|
300
|
+
- [ ] `ResourceConfig.validators` - Custom validation rules
|
|
301
|
+
- [ ] `ResourceConfig.transformers` - Data transformation before import
|
|
302
|
+
- [ ] Progress tracking for large imports
|
|
303
|
+
- [ ] Import history and rollback
|
|
304
|
+
- [ ] Scheduled imports via cron
|
|
305
|
+
- [ ] API endpoint for programmatic imports
|
|
306
|
+
|
|
307
|
+
## Migration Guide
|
|
308
|
+
|
|
309
|
+
### For Existing Projects
|
|
310
|
+
|
|
311
|
+
1. Update django-cfg to latest version
|
|
312
|
+
2. Optional: Replace manual Resource classes with ResourceConfig
|
|
313
|
+
3. Optional: Add BackgroundTaskConfig for async operations
|
|
314
|
+
4. Optional: Add after_import_row hooks for post-import processing
|
|
315
|
+
|
|
316
|
+
### Example Migration
|
|
317
|
+
|
|
318
|
+
**Before:**
|
|
319
|
+
```python
|
|
320
|
+
# resources.py
|
|
321
|
+
class ProxyResource(resources.ModelResource):
|
|
322
|
+
class Meta:
|
|
323
|
+
model = Proxy
|
|
324
|
+
fields = ('host', 'port')
|
|
325
|
+
|
|
326
|
+
# admin.py
|
|
327
|
+
config = AdminConfig(
|
|
328
|
+
model=Proxy,
|
|
329
|
+
import_export_enabled=True,
|
|
330
|
+
resource_class=ProxyResource,
|
|
331
|
+
)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**After:**
|
|
335
|
+
```python
|
|
336
|
+
# admin.py only - no resources.py needed
|
|
337
|
+
config = AdminConfig(
|
|
338
|
+
model=Proxy,
|
|
339
|
+
import_export_enabled=True,
|
|
340
|
+
resource_config=ResourceConfig(
|
|
341
|
+
fields=['host', 'port'],
|
|
342
|
+
),
|
|
343
|
+
)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Version
|
|
347
|
+
|
|
348
|
+
- **django-cfg**: 1.4.106+
|
|
349
|
+
- **Python**: 3.12+
|
|
350
|
+
- **Django**: 5.2+
|
|
@@ -46,6 +46,7 @@ Example:
|
|
|
46
46
|
from .config import (
|
|
47
47
|
ActionConfig,
|
|
48
48
|
AdminConfig,
|
|
49
|
+
BackgroundTaskConfig,
|
|
49
50
|
BadgeField,
|
|
50
51
|
BooleanField,
|
|
51
52
|
CurrencyField,
|
|
@@ -53,6 +54,7 @@ from .config import (
|
|
|
53
54
|
FieldConfig,
|
|
54
55
|
FieldsetConfig,
|
|
55
56
|
ImageField,
|
|
57
|
+
ResourceConfig,
|
|
56
58
|
TextField,
|
|
57
59
|
UserField,
|
|
58
60
|
)
|
|
@@ -101,6 +103,8 @@ __all__ = [
|
|
|
101
103
|
"FieldConfig",
|
|
102
104
|
"FieldsetConfig",
|
|
103
105
|
"ActionConfig",
|
|
106
|
+
"ResourceConfig",
|
|
107
|
+
"BackgroundTaskConfig",
|
|
104
108
|
# Specialized Field Types
|
|
105
109
|
"BadgeField",
|
|
106
110
|
"BooleanField",
|
|
@@ -154,37 +154,92 @@ class PydanticAdminMixin:
|
|
|
154
154
|
|
|
155
155
|
@classmethod
|
|
156
156
|
def _generate_resource_class(cls, config: AdminConfig):
|
|
157
|
-
"""
|
|
157
|
+
"""
|
|
158
|
+
Auto-generate a ModelResource class for import/export.
|
|
159
|
+
|
|
160
|
+
Uses ResourceConfig if provided, otherwise generates basic Resource.
|
|
161
|
+
"""
|
|
158
162
|
from import_export import resources
|
|
159
163
|
|
|
160
164
|
target_model = config.model
|
|
165
|
+
resource_config = config.resource_config
|
|
161
166
|
|
|
162
|
-
#
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
# Determine fields to include
|
|
168
|
+
if resource_config and resource_config.fields:
|
|
169
|
+
# Use explicitly specified fields
|
|
170
|
+
model_fields = resource_config.fields
|
|
171
|
+
else:
|
|
172
|
+
# Auto-detect fields from model
|
|
173
|
+
model_fields = []
|
|
174
|
+
for field in target_model._meta.get_fields():
|
|
175
|
+
# Skip relations and auto fields that shouldn't be imported
|
|
176
|
+
if field.concrete and not field.many_to_many:
|
|
177
|
+
# Skip password fields for security
|
|
178
|
+
if 'password' not in field.name.lower():
|
|
179
|
+
model_fields.append(field.name)
|
|
180
|
+
|
|
181
|
+
# Apply exclusions if specified
|
|
182
|
+
if resource_config and resource_config.exclude:
|
|
183
|
+
model_fields = [f for f in model_fields if f not in resource_config.exclude]
|
|
184
|
+
|
|
185
|
+
# Build Meta attributes
|
|
172
186
|
meta_attrs = {
|
|
173
187
|
'model': target_model,
|
|
174
188
|
'fields': tuple(model_fields),
|
|
175
|
-
'import_id_fields': ['id'] if 'id' in model_fields else [],
|
|
176
|
-
'skip_unchanged': True,
|
|
177
|
-
'report_skipped': True,
|
|
178
189
|
}
|
|
179
190
|
|
|
191
|
+
# Add ResourceConfig settings
|
|
192
|
+
if resource_config:
|
|
193
|
+
meta_attrs['import_id_fields'] = resource_config.import_id_fields
|
|
194
|
+
meta_attrs['skip_unchanged'] = resource_config.skip_unchanged
|
|
195
|
+
meta_attrs['report_skipped'] = resource_config.report_skipped
|
|
196
|
+
meta_attrs['use_transactions'] = resource_config.use_transactions
|
|
197
|
+
|
|
198
|
+
if resource_config.export_order:
|
|
199
|
+
meta_attrs['export_order'] = tuple(resource_config.export_order)
|
|
200
|
+
else:
|
|
201
|
+
# Default settings
|
|
202
|
+
meta_attrs['import_id_fields'] = ['id'] if 'id' in model_fields else []
|
|
203
|
+
meta_attrs['skip_unchanged'] = True
|
|
204
|
+
meta_attrs['report_skipped'] = True
|
|
205
|
+
|
|
180
206
|
# Create Meta class
|
|
181
207
|
ResourceMeta = type('Meta', (), meta_attrs)
|
|
182
208
|
|
|
209
|
+
# Build Resource class attributes (methods + Meta)
|
|
210
|
+
resource_attrs = {'Meta': ResourceMeta}
|
|
211
|
+
|
|
212
|
+
# Add hooks from ResourceConfig
|
|
213
|
+
if resource_config:
|
|
214
|
+
# before_import hook
|
|
215
|
+
if resource_config.before_import:
|
|
216
|
+
hook = resource_config.get_callable('before_import')
|
|
217
|
+
if hook:
|
|
218
|
+
resource_attrs['before_import'] = hook
|
|
219
|
+
|
|
220
|
+
# after_import hook
|
|
221
|
+
if resource_config.after_import:
|
|
222
|
+
hook = resource_config.get_callable('after_import')
|
|
223
|
+
if hook:
|
|
224
|
+
resource_attrs['after_import'] = hook
|
|
225
|
+
|
|
226
|
+
# before_import_row hook
|
|
227
|
+
if resource_config.before_import_row:
|
|
228
|
+
hook = resource_config.get_callable('before_import_row')
|
|
229
|
+
if hook:
|
|
230
|
+
resource_attrs['before_import_row'] = hook
|
|
231
|
+
|
|
232
|
+
# after_import_row hook
|
|
233
|
+
if resource_config.after_import_row:
|
|
234
|
+
hook = resource_config.get_callable('after_import_row')
|
|
235
|
+
if hook:
|
|
236
|
+
resource_attrs['after_import_row'] = hook
|
|
237
|
+
|
|
183
238
|
# Create Resource class
|
|
184
239
|
AutoGeneratedResource = type(
|
|
185
240
|
f'{target_model.__name__}Resource',
|
|
186
241
|
(resources.ModelResource,),
|
|
187
|
-
|
|
242
|
+
resource_attrs
|
|
188
243
|
)
|
|
189
244
|
|
|
190
245
|
return AutoGeneratedResource
|
|
@@ -4,6 +4,7 @@ Configuration models for declarative Django Admin.
|
|
|
4
4
|
|
|
5
5
|
from .action_config import ActionConfig
|
|
6
6
|
from .admin_config import AdminConfig
|
|
7
|
+
from .background_task_config import BackgroundTaskConfig
|
|
7
8
|
from .field_config import (
|
|
8
9
|
FieldConfig,
|
|
9
10
|
BadgeField,
|
|
@@ -15,12 +16,15 @@ from .field_config import (
|
|
|
15
16
|
UserField,
|
|
16
17
|
)
|
|
17
18
|
from .fieldset_config import FieldsetConfig
|
|
19
|
+
from .resource_config import ResourceConfig
|
|
18
20
|
|
|
19
21
|
__all__ = [
|
|
20
22
|
"AdminConfig",
|
|
21
23
|
"FieldConfig",
|
|
22
24
|
"FieldsetConfig",
|
|
23
25
|
"ActionConfig",
|
|
26
|
+
"ResourceConfig",
|
|
27
|
+
"BackgroundTaskConfig",
|
|
24
28
|
# Specialized Field Types
|
|
25
29
|
"BadgeField",
|
|
26
30
|
"BooleanField",
|
|
@@ -8,8 +8,10 @@ from django.db import models
|
|
|
8
8
|
from pydantic import BaseModel, ConfigDict, Field
|
|
9
9
|
|
|
10
10
|
from .action_config import ActionConfig
|
|
11
|
+
from .background_task_config import BackgroundTaskConfig
|
|
11
12
|
from .field_config import FieldConfig
|
|
12
13
|
from .fieldset_config import FieldsetConfig
|
|
14
|
+
from .resource_config import ResourceConfig
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class AdminConfig(BaseModel):
|
|
@@ -127,7 +129,17 @@ class AdminConfig(BaseModel):
|
|
|
127
129
|
|
|
128
130
|
# Import/Export options
|
|
129
131
|
import_export_enabled: bool = Field(False, description="Enable import/export functionality")
|
|
130
|
-
resource_class: Optional[Type] = Field(None, description="Resource class for import/export")
|
|
132
|
+
resource_class: Optional[Type] = Field(None, description="Custom Resource class for import/export")
|
|
133
|
+
resource_config: Optional[ResourceConfig] = Field(
|
|
134
|
+
None,
|
|
135
|
+
description="Declarative resource configuration (alternative to resource_class)"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Background task processing
|
|
139
|
+
background_task_config: Optional[BackgroundTaskConfig] = Field(
|
|
140
|
+
None,
|
|
141
|
+
description="Configuration for background task processing"
|
|
142
|
+
)
|
|
131
143
|
|
|
132
144
|
def get_display_field_config(self, field_name: str) -> Optional[FieldConfig]:
|
|
133
145
|
"""Get FieldConfig for a specific field."""
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Background task configuration for async operations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Literal, Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BackgroundTaskConfig(BaseModel):
|
|
11
|
+
"""
|
|
12
|
+
Configuration for background task processing.
|
|
13
|
+
|
|
14
|
+
Used for async operations like testing imported items,
|
|
15
|
+
bulk processing, or long-running operations.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
```python
|
|
19
|
+
background_config = BackgroundTaskConfig(
|
|
20
|
+
enabled=True,
|
|
21
|
+
task_runner='rearq',
|
|
22
|
+
batch_size=100,
|
|
23
|
+
timeout=300,
|
|
24
|
+
)
|
|
25
|
+
```
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
model_config = ConfigDict(
|
|
29
|
+
validate_assignment=True,
|
|
30
|
+
extra="forbid"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
enabled: bool = Field(
|
|
34
|
+
True,
|
|
35
|
+
description="Enable background task processing"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
task_runner: Literal['rearq', 'celery', 'django_q', 'sync'] = Field(
|
|
39
|
+
'rearq',
|
|
40
|
+
description="Task runner to use for background operations"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
batch_size: int = Field(
|
|
44
|
+
100,
|
|
45
|
+
ge=1,
|
|
46
|
+
le=10000,
|
|
47
|
+
description="Number of items to process in each batch"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
timeout: int = Field(
|
|
51
|
+
300,
|
|
52
|
+
ge=1,
|
|
53
|
+
le=3600,
|
|
54
|
+
description="Task timeout in seconds (max 1 hour)"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
retry_on_failure: bool = Field(
|
|
58
|
+
True,
|
|
59
|
+
description="Automatically retry failed tasks"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
max_retries: int = Field(
|
|
63
|
+
3,
|
|
64
|
+
ge=0,
|
|
65
|
+
le=10,
|
|
66
|
+
description="Maximum number of retry attempts"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
priority: Literal['high', 'normal', 'low'] = Field(
|
|
70
|
+
'normal',
|
|
71
|
+
description="Task priority in queue"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def should_use_background(self) -> bool:
|
|
75
|
+
"""Check if background processing should be used."""
|
|
76
|
+
return self.enabled and self.task_runner != 'sync'
|