django-cfg 1.3.5__py3-none-any.whl → 1.3.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +258 -0
- django_cfg/apps/payments/admin/payments_admin.py +171 -461
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +105 -34
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
- django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
- django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +1 -1
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +95 -39
- django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
- django_cfg/apps/payments/services/providers/models/base.py +122 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/{nowpayments.py → nowpayments/provider.py} +240 -209
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +4 -32
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
- django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +5 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +4 -3
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/apps/urls.py +1 -2
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/app_agent_diagnose.py +470 -0
- django_cfg/management/commands/app_agent_generate.py +342 -0
- django_cfg/management/commands/app_agent_info.py +308 -0
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_app_agent/__init__.py +87 -0
- django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
- django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
- django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
- django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
- django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
- django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
- django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
- django_cfg/modules/django_app_agent/core/__init__.py +33 -0
- django_cfg/modules/django_app_agent/core/config.py +300 -0
- django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
- django_cfg/modules/django_app_agent/models/__init__.py +71 -0
- django_cfg/modules/django_app_agent/models/base.py +283 -0
- django_cfg/modules/django_app_agent/models/context.py +496 -0
- django_cfg/modules/django_app_agent/models/enums.py +481 -0
- django_cfg/modules/django_app_agent/models/requests.py +500 -0
- django_cfg/modules/django_app_agent/models/responses.py +585 -0
- django_cfg/modules/django_app_agent/pytest.ini +6 -0
- django_cfg/modules/django_app_agent/services/__init__.py +42 -0
- django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
- django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
- django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
- django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
- django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
- django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
- django_cfg/modules/django_app_agent/services/base.py +437 -0
- django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
- django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
- django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
- django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
- django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
- django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
- django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
- django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
- django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
- django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
- django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
- django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
- django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
- django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
- django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
- django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
- django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
- django_cfg/modules/django_app_agent/services/report_service.py +332 -0
- django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
- django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
- django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
- django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
- django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
- django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
- django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
- django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
- django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
- django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
- django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
- django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
- django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
- django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
- django_cfg/modules/django_app_agent/ui/cli.py +419 -0
- django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
- django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
- django_cfg/modules/django_app_agent/utils/logging.py +360 -0
- django_cfg/modules/django_app_agent/utils/validation.py +417 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +3 -0
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/RECORD +224 -118
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -125,6 +125,11 @@ class Command(BaseCommand):
|
|
125
125
|
except Exception as e:
|
126
126
|
self.stdout.write(self.style.WARNING(f"⚠️ Warning creating migrations: {e}"))
|
127
127
|
|
128
|
+
def _raise_system_exit(self, message):
|
129
|
+
self.stdout.write(self.style.ERROR(f"❌ {message}"))
|
130
|
+
logger.error(message)
|
131
|
+
# raise SystemExit(1)
|
132
|
+
|
128
133
|
def migrate_database(self, db_name):
|
129
134
|
"""Migrate specific database"""
|
130
135
|
try:
|
@@ -154,12 +159,12 @@ class Command(BaseCommand):
|
|
154
159
|
self.stdout.write(f" 📦 Migrating {app}...")
|
155
160
|
call_command("migrate", app, database=db_name, verbosity=1)
|
156
161
|
except Exception as e:
|
157
|
-
self.
|
162
|
+
self._raise_system_exit(f"Migration failed for {app} on {db_name}: {e}")
|
158
163
|
|
159
164
|
self.stdout.write(self.style.SUCCESS(f"✅ {db_name} migration completed!"))
|
160
165
|
|
161
166
|
except Exception as e:
|
162
|
-
self.
|
167
|
+
self._raise_system_exit(f"Error migrating {db_name}: {e}")
|
163
168
|
|
164
169
|
def migrate_constance_if_needed(self):
|
165
170
|
"""Always migrate constance app if it's installed"""
|
@@ -173,12 +178,12 @@ class Command(BaseCommand):
|
|
173
178
|
call_command("migrate", "constance", database="default", verbosity=1)
|
174
179
|
self.stdout.write(self.style.SUCCESS("✅ Constance migration completed!"))
|
175
180
|
except Exception as e:
|
176
|
-
self.
|
181
|
+
self._raise_system_exit(f"Constance migration failed: {e}")
|
177
182
|
else:
|
178
183
|
self.stdout.write(self.style.WARNING("⚠️ Constance not found in INSTALLED_APPS"))
|
179
184
|
|
180
185
|
except Exception as e:
|
181
|
-
self.
|
186
|
+
self._raise_system_exit(f"Could not migrate constance: {e}")
|
182
187
|
|
183
188
|
def migrate_app(self, app_name):
|
184
189
|
"""Migrate specific app across all databases"""
|
@@ -192,7 +197,7 @@ class Command(BaseCommand):
|
|
192
197
|
try:
|
193
198
|
call_command("migrate", app_name, database=db_name, verbosity=1)
|
194
199
|
except Exception as e:
|
195
|
-
self.
|
200
|
+
self._raise_system_exit(f"Migration failed for {app_name} on {db_name}: {e}")
|
196
201
|
|
197
202
|
def show_database_status(self):
|
198
203
|
"""Show status of all databases and their apps"""
|
@@ -265,7 +270,7 @@ class Command(BaseCommand):
|
|
265
270
|
self.stdout.write(f"📦 Installed Apps: {len(settings.INSTALLED_APPS)}")
|
266
271
|
|
267
272
|
except Exception as e:
|
268
|
-
self.
|
273
|
+
self._raise_system_exit(f"Error getting Django config info: {e}")
|
269
274
|
|
270
275
|
def get_apps_for_database(self, db_name: str):
|
271
276
|
"""Get apps for specific database with smart logic for default"""
|
@@ -16,8 +16,6 @@ from django.conf import settings
|
|
16
16
|
from django.core.management.base import BaseCommand
|
17
17
|
from django.utils.module_loading import module_has_submodule
|
18
18
|
from django_cfg.modules.django_logger import get_logger
|
19
|
-
|
20
|
-
|
21
19
|
from django_cfg.modules.django_tasks import get_task_service
|
22
20
|
|
23
21
|
|
@@ -111,8 +109,10 @@ class Command(BaseCommand):
|
|
111
109
|
# If dry run, show command and exit
|
112
110
|
if dry_run:
|
113
111
|
executable_name = "dramatiq"
|
112
|
+
|
114
113
|
process_args = [
|
115
114
|
executable_name,
|
115
|
+
"django_cfg.modules.dramatiq_setup", # Broker module
|
116
116
|
"--processes", str(processes),
|
117
117
|
"--threads", str(threads),
|
118
118
|
"--worker-shutdown-timeout", str(worker_shutdown_timeout),
|
@@ -150,6 +150,7 @@ class Command(BaseCommand):
|
|
150
150
|
# Build process arguments exactly like django_dramatiq
|
151
151
|
process_args = [
|
152
152
|
executable_name,
|
153
|
+
"django_cfg.modules.dramatiq_setup", # Broker module
|
153
154
|
"--processes", str(processes),
|
154
155
|
"--threads", str(threads),
|
155
156
|
"--worker-shutdown-timeout", str(worker_shutdown_timeout),
|
@@ -0,0 +1,115 @@
|
|
1
|
+
"""
|
2
|
+
API Keys configuration models for django-cfg.
|
3
|
+
|
4
|
+
Simple model for OpenAI and OpenRouter API keys.
|
5
|
+
Following CRITICAL_REQUIREMENTS.md:
|
6
|
+
- No raw Dict/Any usage - everything through Pydantic models
|
7
|
+
- Proper type annotations for all fields
|
8
|
+
- No mutable default arguments
|
9
|
+
"""
|
10
|
+
|
11
|
+
from typing import Optional
|
12
|
+
from pydantic import BaseModel, Field, field_validator, SecretStr
|
13
|
+
|
14
|
+
|
15
|
+
class ApiKeys(BaseModel):
|
16
|
+
"""
|
17
|
+
API keys configuration for LLM services.
|
18
|
+
|
19
|
+
Simple model for storing OpenAI and OpenRouter API keys.
|
20
|
+
|
21
|
+
Example:
|
22
|
+
```python
|
23
|
+
api_keys = ApiKeys(
|
24
|
+
openai="${OPENAI_API_KEY}",
|
25
|
+
openrouter="${OPENROUTER_API_KEY}"
|
26
|
+
)
|
27
|
+
```
|
28
|
+
"""
|
29
|
+
|
30
|
+
model_config = {
|
31
|
+
"validate_assignment": True,
|
32
|
+
"extra": "forbid",
|
33
|
+
"str_strip_whitespace": True,
|
34
|
+
"validate_default": True,
|
35
|
+
}
|
36
|
+
|
37
|
+
# === LLM Provider Keys ===
|
38
|
+
openai: Optional[SecretStr] = Field(
|
39
|
+
default=None,
|
40
|
+
description="OpenAI API key for GPT models and embeddings"
|
41
|
+
)
|
42
|
+
|
43
|
+
openrouter: Optional[SecretStr] = Field(
|
44
|
+
default=None,
|
45
|
+
description="OpenRouter API key for access to multiple LLM providers"
|
46
|
+
)
|
47
|
+
|
48
|
+
@field_validator("openai")
|
49
|
+
@classmethod
|
50
|
+
def validate_openai_key(cls, v: Optional[SecretStr]) -> Optional[SecretStr]:
|
51
|
+
"""Validate OpenAI API key format."""
|
52
|
+
if v is None:
|
53
|
+
return v
|
54
|
+
|
55
|
+
key_str = v.get_secret_value()
|
56
|
+
if not key_str.startswith(("sk-", "sk-proj-")):
|
57
|
+
raise ValueError("OpenAI API key must start with 'sk-' or 'sk-proj-'")
|
58
|
+
|
59
|
+
if len(key_str) < 20:
|
60
|
+
raise ValueError("OpenAI API key appears to be too short")
|
61
|
+
|
62
|
+
return v
|
63
|
+
|
64
|
+
@field_validator("openrouter")
|
65
|
+
@classmethod
|
66
|
+
def validate_openrouter_key(cls, v: Optional[SecretStr]) -> Optional[SecretStr]:
|
67
|
+
"""Validate OpenRouter API key format."""
|
68
|
+
if v is None:
|
69
|
+
return v
|
70
|
+
|
71
|
+
key_str = v.get_secret_value()
|
72
|
+
if not key_str.startswith(("sk-or-", "sk-proj-")):
|
73
|
+
raise ValueError("OpenRouter API key must start with 'sk-or-' or 'sk-proj-'")
|
74
|
+
|
75
|
+
if len(key_str) < 20:
|
76
|
+
raise ValueError("OpenRouter API key appears to be too short")
|
77
|
+
|
78
|
+
return v
|
79
|
+
|
80
|
+
def get_openai_key(self) -> Optional[str]:
|
81
|
+
"""Get OpenAI API key as string."""
|
82
|
+
return self.openai.get_secret_value() if self.openai else None
|
83
|
+
|
84
|
+
def get_openrouter_key(self) -> Optional[str]:
|
85
|
+
"""Get OpenRouter API key as string."""
|
86
|
+
return self.openrouter.get_secret_value() if self.openrouter else None
|
87
|
+
|
88
|
+
def has_openai(self) -> bool:
|
89
|
+
"""Check if OpenAI key is configured."""
|
90
|
+
return self.openai is not None
|
91
|
+
|
92
|
+
def has_openrouter(self) -> bool:
|
93
|
+
"""Check if OpenRouter key is configured."""
|
94
|
+
return self.openrouter is not None
|
95
|
+
|
96
|
+
def get_preferred_provider(self) -> Optional[str]:
|
97
|
+
"""
|
98
|
+
Get preferred provider based on availability.
|
99
|
+
|
100
|
+
Priority: OpenRouter (default) > OpenAI
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
"openrouter" or "openai" or None
|
104
|
+
"""
|
105
|
+
if self.has_openrouter():
|
106
|
+
return "openrouter"
|
107
|
+
elif self.has_openai():
|
108
|
+
return "openai"
|
109
|
+
return None
|
110
|
+
|
111
|
+
|
112
|
+
# Export the main class
|
113
|
+
__all__ = [
|
114
|
+
"ApiKeys",
|
115
|
+
]
|
@@ -0,0 +1,64 @@
|
|
1
|
+
"""
|
2
|
+
Django Admin Utilities - Universal HTML Builder System
|
3
|
+
|
4
|
+
Clean, type-safe admin utilities with no HTML duplication.
|
5
|
+
"""
|
6
|
+
|
7
|
+
# Core utilities
|
8
|
+
from .utils.displays import UserDisplay, MoneyDisplay, StatusDisplay, DateTimeDisplay
|
9
|
+
from .utils.badges import StatusBadge, ProgressBadge, CounterBadge
|
10
|
+
|
11
|
+
# Icons
|
12
|
+
from .icons import Icons, IconCategories
|
13
|
+
|
14
|
+
# Admin mixins
|
15
|
+
from .mixins.display_mixin import DisplayMixin
|
16
|
+
from .mixins.optimization_mixin import OptimizedModelAdmin
|
17
|
+
from .mixins.standalone_actions_mixin import StandaloneActionsMixin, standalone_action
|
18
|
+
|
19
|
+
# Configuration models
|
20
|
+
from .models.display_models import UserDisplayConfig, MoneyDisplayConfig, DateTimeDisplayConfig
|
21
|
+
from .models.badge_models import BadgeConfig, BadgeVariant, StatusBadgeConfig
|
22
|
+
from .models.action_models import ActionVariant, ActionConfig
|
23
|
+
|
24
|
+
# Decorators
|
25
|
+
from .decorators import display, action
|
26
|
+
|
27
|
+
__version__ = "1.0.0"
|
28
|
+
|
29
|
+
__all__ = [
|
30
|
+
# Display utilities
|
31
|
+
"UserDisplay",
|
32
|
+
"MoneyDisplay",
|
33
|
+
"StatusDisplay",
|
34
|
+
"DateTimeDisplay",
|
35
|
+
|
36
|
+
# Badge utilities
|
37
|
+
"StatusBadge",
|
38
|
+
"ProgressBadge",
|
39
|
+
"CounterBadge",
|
40
|
+
|
41
|
+
# Icons
|
42
|
+
"Icons",
|
43
|
+
"IconCategories",
|
44
|
+
|
45
|
+
# Admin mixins
|
46
|
+
"OptimizedModelAdmin",
|
47
|
+
"DisplayMixin",
|
48
|
+
"StandaloneActionsMixin",
|
49
|
+
"standalone_action",
|
50
|
+
|
51
|
+
# Configuration models
|
52
|
+
"UserDisplayConfig",
|
53
|
+
"MoneyDisplayConfig",
|
54
|
+
"DateTimeDisplayConfig",
|
55
|
+
"BadgeConfig",
|
56
|
+
"BadgeVariant",
|
57
|
+
"StatusBadgeConfig",
|
58
|
+
"ActionVariant",
|
59
|
+
"ActionConfig",
|
60
|
+
|
61
|
+
# Decorators
|
62
|
+
"display",
|
63
|
+
"action",
|
64
|
+
]
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"""
|
2
|
+
Django Admin Decorators - Wrappers for Unfold decorators.
|
3
|
+
|
4
|
+
Provides consistent, type-safe decorators with our admin utilities integration.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .display import display
|
8
|
+
from .actions import action
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
'display',
|
12
|
+
'action',
|
13
|
+
]
|
@@ -0,0 +1,106 @@
|
|
1
|
+
"""
|
2
|
+
Action decorator wrapper for Unfold integration.
|
3
|
+
|
4
|
+
Provides type-safe action decorators with consistent styling.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Optional, Callable, Any
|
8
|
+
from functools import wraps
|
9
|
+
from unfold.decorators import action as unfold_action
|
10
|
+
from unfold.enums import ActionVariant as UnfoldActionVariant
|
11
|
+
|
12
|
+
from django_cfg.modules.django_logger import get_logger
|
13
|
+
from ..models.action_models import ActionVariant
|
14
|
+
|
15
|
+
|
16
|
+
logger = get_logger("django_admin.decorators.actions")
|
17
|
+
|
18
|
+
def action(
|
19
|
+
description: str,
|
20
|
+
variant: Optional[ActionVariant] = None,
|
21
|
+
icon: Optional[str] = None,
|
22
|
+
permissions: Optional[list] = None,
|
23
|
+
url_path: Optional[str] = None,
|
24
|
+
attrs: Optional[dict] = None
|
25
|
+
) -> Callable:
|
26
|
+
"""
|
27
|
+
Enhanced action decorator with Django Admin Utilities integration.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
description: Action description shown in admin
|
31
|
+
variant: Action style variant (ActionVariant enum)
|
32
|
+
icon: Material icon name
|
33
|
+
permissions: Required permissions list
|
34
|
+
url_path: URL path for standalone action buttons (creates separate button)
|
35
|
+
attrs: Additional attributes for the action
|
36
|
+
|
37
|
+
Usage:
|
38
|
+
# Bulk action (works on selected items)
|
39
|
+
@action(description="Activate items", variant=ActionVariant.SUCCESS)
|
40
|
+
def activate_items(self, request, queryset):
|
41
|
+
updated = queryset.update(is_active=True)
|
42
|
+
self.message_user(request, f"Activated {updated} items.")
|
43
|
+
|
44
|
+
# Standalone action button (url_path creates separate button)
|
45
|
+
@action(
|
46
|
+
description="Update Rates",
|
47
|
+
variant=ActionVariant.SUCCESS,
|
48
|
+
url_path="update-rates",
|
49
|
+
icon="sync"
|
50
|
+
)
|
51
|
+
def update_rates(self, request):
|
52
|
+
# Standalone action logic (no queryset parameter)
|
53
|
+
pass
|
54
|
+
"""
|
55
|
+
def decorator(func: Callable) -> Callable:
|
56
|
+
@wraps(func)
|
57
|
+
def wrapper(self, request: Any, *args, **kwargs) -> Any:
|
58
|
+
try:
|
59
|
+
# For url_path actions, there's no queryset parameter
|
60
|
+
if url_path:
|
61
|
+
return func(self, request, *args, **kwargs)
|
62
|
+
else:
|
63
|
+
# For bulk actions, pass queryset as second parameter
|
64
|
+
queryset = args[0] if args else kwargs.get('queryset')
|
65
|
+
return func(self, request, queryset, *args[1:], **kwargs)
|
66
|
+
except Exception as e:
|
67
|
+
# Log error and show user message
|
68
|
+
logger.error(f"Error in action {func.__name__}: {e}")
|
69
|
+
|
70
|
+
self.message_user(
|
71
|
+
request,
|
72
|
+
f"Error executing action: {str(e)}",
|
73
|
+
level='ERROR'
|
74
|
+
)
|
75
|
+
|
76
|
+
# Convert our ActionVariant to Unfold ActionVariant
|
77
|
+
action_variant = None
|
78
|
+
if variant:
|
79
|
+
# Direct mapping since values are the same
|
80
|
+
unfold_variant_mapping = {
|
81
|
+
ActionVariant.DEFAULT: UnfoldActionVariant.DEFAULT,
|
82
|
+
ActionVariant.PRIMARY: UnfoldActionVariant.PRIMARY,
|
83
|
+
ActionVariant.SUCCESS: UnfoldActionVariant.SUCCESS,
|
84
|
+
ActionVariant.INFO: UnfoldActionVariant.INFO,
|
85
|
+
ActionVariant.WARNING: UnfoldActionVariant.WARNING,
|
86
|
+
ActionVariant.DANGER: UnfoldActionVariant.DANGER,
|
87
|
+
}
|
88
|
+
action_variant = unfold_variant_mapping.get(variant, UnfoldActionVariant.DEFAULT)
|
89
|
+
|
90
|
+
# Build decorator kwargs
|
91
|
+
decorator_kwargs = {'description': description}
|
92
|
+
if action_variant:
|
93
|
+
decorator_kwargs['variant'] = action_variant
|
94
|
+
if icon:
|
95
|
+
decorator_kwargs['icon'] = icon
|
96
|
+
if permissions:
|
97
|
+
decorator_kwargs['permissions'] = permissions
|
98
|
+
if url_path:
|
99
|
+
decorator_kwargs['url_path'] = url_path
|
100
|
+
if attrs:
|
101
|
+
decorator_kwargs['attrs'] = attrs
|
102
|
+
|
103
|
+
# Apply Unfold decorator
|
104
|
+
return unfold_action(**decorator_kwargs)(wrapper)
|
105
|
+
|
106
|
+
return decorator
|
@@ -0,0 +1,106 @@
|
|
1
|
+
"""
|
2
|
+
Display decorator wrapper for Unfold integration.
|
3
|
+
|
4
|
+
Provides type-safe display decorators with our admin utilities.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Optional, Callable, Any, Union
|
8
|
+
from functools import wraps
|
9
|
+
from django.utils.safestring import SafeString, mark_safe
|
10
|
+
from unfold.decorators import display as unfold_display
|
11
|
+
from django_cfg.modules.django_logger import get_logger
|
12
|
+
|
13
|
+
logger = get_logger('django_admin.decorators.display')
|
14
|
+
|
15
|
+
def display(
|
16
|
+
function: Optional[Callable] = None,
|
17
|
+
*,
|
18
|
+
boolean: Optional[bool] = None,
|
19
|
+
image: Optional[bool] = None,
|
20
|
+
ordering: Optional[str] = None,
|
21
|
+
description: Optional[str] = None,
|
22
|
+
empty_value: Optional[str] = None,
|
23
|
+
dropdown: Optional[bool] = None,
|
24
|
+
label: Union[bool, str, dict, None] = None,
|
25
|
+
header: Optional[bool] = None
|
26
|
+
) -> Callable:
|
27
|
+
"""
|
28
|
+
Enhanced display decorator with Django Admin Utilities integration.
|
29
|
+
|
30
|
+
This decorator wraps unfold.decorators.display with additional features:
|
31
|
+
- Automatic HTML safety detection and marking
|
32
|
+
- Empty value handling with customizable fallback
|
33
|
+
- Error handling with logging
|
34
|
+
|
35
|
+
Args:
|
36
|
+
function: Function to decorate (for direct usage)
|
37
|
+
boolean: Show as boolean icon (True/False icons)
|
38
|
+
image: Show as image thumbnail
|
39
|
+
ordering: Field name for sorting
|
40
|
+
description: Column header text
|
41
|
+
empty_value: Default value for empty/None fields
|
42
|
+
dropdown: Show as dropdown menu
|
43
|
+
label: Show as label badge (bool, str, or dict for styling)
|
44
|
+
header: Show as header with avatar
|
45
|
+
|
46
|
+
Usage:
|
47
|
+
@display(description="User", header=True)
|
48
|
+
def user_display(self, obj):
|
49
|
+
return self.display_user_with_avatar(obj, 'user')
|
50
|
+
|
51
|
+
@display(description="Status", label=True)
|
52
|
+
def status_display(self, obj):
|
53
|
+
return self.display_status_auto(obj, 'status')
|
54
|
+
|
55
|
+
@display(description="Has Embedding", boolean=True)
|
56
|
+
def has_embedding_display(self, obj):
|
57
|
+
return obj.has_embedding
|
58
|
+
|
59
|
+
@display(description="Avatar", image=True)
|
60
|
+
def avatar_display(self, obj):
|
61
|
+
return obj.avatar.url if obj.avatar else None
|
62
|
+
"""
|
63
|
+
def decorator(func: Callable) -> Callable:
|
64
|
+
@wraps(func)
|
65
|
+
def wrapper(self, obj: Any) -> Any:
|
66
|
+
try:
|
67
|
+
result = func(self, obj)
|
68
|
+
|
69
|
+
# Handle empty values (use our default if unfold's empty_value is None)
|
70
|
+
if result is None or result == "":
|
71
|
+
return empty_value if empty_value is not None else "—"
|
72
|
+
|
73
|
+
# Auto-mark HTML as safe if it contains HTML tags
|
74
|
+
if isinstance(result, str) and ('<' in result and '>' in result):
|
75
|
+
return mark_safe(result)
|
76
|
+
|
77
|
+
# SafeString is already safe
|
78
|
+
if isinstance(result, SafeString):
|
79
|
+
return result
|
80
|
+
|
81
|
+
return result
|
82
|
+
except Exception as e:
|
83
|
+
# Log error and return safe fallback
|
84
|
+
logger.error(f"Error in display method {func.__name__}: {e}")
|
85
|
+
return empty_value if empty_value is not None else "—"
|
86
|
+
|
87
|
+
# Apply Unfold decorator with all parameters
|
88
|
+
return unfold_display(
|
89
|
+
function=None, # We handle the function ourselves
|
90
|
+
boolean=boolean,
|
91
|
+
image=image,
|
92
|
+
ordering=ordering,
|
93
|
+
description=description,
|
94
|
+
empty_value=empty_value,
|
95
|
+
dropdown=dropdown,
|
96
|
+
label=label,
|
97
|
+
header=header
|
98
|
+
)(wrapper)
|
99
|
+
|
100
|
+
# Support both @display and @display(...) usage
|
101
|
+
if function is not None:
|
102
|
+
# Direct usage: @display
|
103
|
+
return decorator(function)
|
104
|
+
else:
|
105
|
+
# Parametrized usage: @display(...)
|
106
|
+
return decorator
|
@@ -0,0 +1,14 @@
|
|
1
|
+
"""
|
2
|
+
Admin mixins for easy integration.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from .display_mixin import DisplayMixin
|
6
|
+
from .optimization_mixin import OptimizedModelAdmin
|
7
|
+
from .standalone_actions_mixin import StandaloneActionsMixin, standalone_action
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"DisplayMixin",
|
11
|
+
"OptimizedModelAdmin",
|
12
|
+
"StandaloneActionsMixin",
|
13
|
+
"standalone_action",
|
14
|
+
]
|
@@ -0,0 +1,81 @@
|
|
1
|
+
"""
|
2
|
+
Display mixin for convenient wrapper methods.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Optional, Any
|
6
|
+
from django.utils.safestring import SafeString
|
7
|
+
|
8
|
+
from ..utils.displays import UserDisplay, MoneyDisplay, StatusDisplay, DateTimeDisplay
|
9
|
+
from ..utils.badges import StatusBadge, CounterBadge
|
10
|
+
from ..models.display_models import UserDisplayConfig, MoneyDisplayConfig, DateTimeDisplayConfig
|
11
|
+
from ..models.badge_models import StatusBadgeConfig
|
12
|
+
|
13
|
+
|
14
|
+
class DisplayMixin:
|
15
|
+
"""Mixin for Django ModelAdmin classes with convenient display methods."""
|
16
|
+
|
17
|
+
def display_user_with_avatar(self, obj: Any, user_field: str = 'user',
|
18
|
+
config: Optional[UserDisplayConfig] = None) -> list:
|
19
|
+
"""Display user with avatar for @display(header=True)."""
|
20
|
+
user = getattr(obj, user_field, None)
|
21
|
+
return UserDisplay.with_avatar(user, config)
|
22
|
+
|
23
|
+
def display_user_simple(self, obj: Any, user_field: str = 'user',
|
24
|
+
config: Optional[UserDisplayConfig] = None) -> SafeString:
|
25
|
+
"""Simple user display."""
|
26
|
+
user = getattr(obj, user_field, None)
|
27
|
+
return UserDisplay.simple(user, config)
|
28
|
+
|
29
|
+
def display_money_amount(self, obj: Any, amount_field: str,
|
30
|
+
config: Optional[MoneyDisplayConfig] = None) -> SafeString:
|
31
|
+
"""Display money amount."""
|
32
|
+
amount = getattr(obj, amount_field, None)
|
33
|
+
return MoneyDisplay.amount(amount, config)
|
34
|
+
|
35
|
+
def display_money_breakdown(self, obj: Any, main_field: str, breakdown_fields: dict,
|
36
|
+
config: Optional[MoneyDisplayConfig] = None) -> SafeString:
|
37
|
+
"""Display money with breakdown."""
|
38
|
+
main_amount = getattr(obj, main_field, 0)
|
39
|
+
|
40
|
+
breakdown_items = []
|
41
|
+
for label, field_name in breakdown_fields.items():
|
42
|
+
amount = getattr(obj, field_name, 0)
|
43
|
+
breakdown_items.append({
|
44
|
+
'label': label,
|
45
|
+
'amount': amount,
|
46
|
+
'color': 'warning' if amount > 0 else 'secondary'
|
47
|
+
})
|
48
|
+
|
49
|
+
return MoneyDisplay.with_breakdown(main_amount, breakdown_items, config)
|
50
|
+
|
51
|
+
def display_status_auto(self, obj: Any, status_field: str = 'status',
|
52
|
+
config: Optional[StatusBadgeConfig] = None) -> SafeString:
|
53
|
+
"""Display status with auto color mapping."""
|
54
|
+
status = getattr(obj, status_field, '')
|
55
|
+
return StatusBadge.auto(status, config)
|
56
|
+
|
57
|
+
def display_datetime_relative(self, obj: Any, datetime_field: str,
|
58
|
+
config: Optional[DateTimeDisplayConfig] = None) -> SafeString:
|
59
|
+
"""Display datetime with relative time."""
|
60
|
+
dt = getattr(obj, datetime_field, None)
|
61
|
+
return DateTimeDisplay.relative(dt, config)
|
62
|
+
|
63
|
+
def display_datetime_compact(self, obj: Any, datetime_field: str,
|
64
|
+
config: Optional[DateTimeDisplayConfig] = None) -> SafeString:
|
65
|
+
"""Display datetime compact."""
|
66
|
+
dt = getattr(obj, datetime_field, None)
|
67
|
+
return DateTimeDisplay.compact(dt, config)
|
68
|
+
|
69
|
+
def display_count_simple(self, obj: Any, count_field: str, label: str = None) -> SafeString:
|
70
|
+
"""Display count as badge."""
|
71
|
+
count = getattr(obj, count_field, 0)
|
72
|
+
return CounterBadge.simple(count, label)
|
73
|
+
|
74
|
+
def display_related_count(self, obj: Any, related_name: str, label: str = None) -> SafeString:
|
75
|
+
"""Display count of related objects."""
|
76
|
+
try:
|
77
|
+
related_manager = getattr(obj, related_name)
|
78
|
+
count = related_manager.count()
|
79
|
+
return CounterBadge.simple(count, label)
|
80
|
+
except AttributeError:
|
81
|
+
return CounterBadge.simple(0, label)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
"""
|
2
|
+
Query optimization mixin for performance.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import List, Dict, Any
|
7
|
+
from django.contrib import admin
|
8
|
+
from django.db.models import QuerySet
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
class OptimizedModelAdmin(admin.ModelAdmin):
|
14
|
+
"""Optimized ModelAdmin with automatic query optimization."""
|
15
|
+
|
16
|
+
# Performance settings
|
17
|
+
list_per_page = 50
|
18
|
+
show_full_result_count = False
|
19
|
+
|
20
|
+
# Fields for optimization - override in subclasses
|
21
|
+
select_related_fields: List[str] = []
|
22
|
+
prefetch_related_fields: List[str] = []
|
23
|
+
annotations: Dict[str, Any] = {}
|
24
|
+
|
25
|
+
def get_queryset(self, request) -> QuerySet:
|
26
|
+
"""Optimize queryset with select_related and prefetch_related."""
|
27
|
+
qs = super().get_queryset(request)
|
28
|
+
|
29
|
+
if self.select_related_fields:
|
30
|
+
qs = qs.select_related(*self.select_related_fields)
|
31
|
+
logger.debug(f"Applied select_related: {self.select_related_fields}")
|
32
|
+
|
33
|
+
if self.prefetch_related_fields:
|
34
|
+
qs = qs.prefetch_related(*self.prefetch_related_fields)
|
35
|
+
logger.debug(f"Applied prefetch_related: {self.prefetch_related_fields}")
|
36
|
+
|
37
|
+
if self.annotations:
|
38
|
+
qs = qs.annotate(**self.annotations)
|
39
|
+
logger.debug(f"Applied annotations: {list(self.annotations.keys())}")
|
40
|
+
|
41
|
+
return qs
|