django-cfg 1.3.7__py3-none-any.whl → 1.3.11__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 +269 -0
- django_cfg/apps/payments/admin/payments_admin.py +183 -460
- 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 +153 -34
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
- django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
- django_cfg/apps/payments/config/__init__.py +14 -15
- django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
- django_cfg/apps/payments/config/helpers.py +8 -13
- 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/0001_initial.py +33 -46
- django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
- django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -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/models/managers/payment_managers.py +142 -25
- django_cfg/apps/payments/models/payments.py +94 -0
- django_cfg/apps/payments/services/core/base.py +4 -4
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +266 -39
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +303 -41
- django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
- django_cfg/apps/payments/services/providers/models/base.py +145 -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/provider.py +557 -0
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +9 -37
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/services/types/requests.py +19 -7
- django_cfg/apps/payments/signals/payment_signals.py +31 -2
- django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
- django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
- 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/templatetags/payment_tags.py +8 -0
- django_cfg/apps/payments/urls.py +3 -2
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +8 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +22 -8
- 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/tasks/static/tasks/css/dashboard.css +68 -217
- django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
- django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
- django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
- django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
- django_cfg/apps/tasks/tasks/__init__.py +10 -0
- django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
- django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
- django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
- django_cfg/apps/tasks/urls.py +2 -2
- django_cfg/apps/tasks/urls_admin.py +2 -2
- django_cfg/apps/tasks/utils/__init__.py +1 -0
- django_cfg/apps/tasks/utils/simulator.py +356 -0
- django_cfg/apps/tasks/views/__init__.py +16 -0
- django_cfg/apps/tasks/views/api.py +569 -0
- django_cfg/apps/tasks/views/dashboard.py +58 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/core/integration/__init__.py +21 -0
- django_cfg/management/commands/__init__.py +13 -1
- 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/management/commands/rundramatiq_simulator.py +430 -0
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/models/constance.py +0 -11
- django_cfg/models/payments.py +137 -3
- 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_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_tasks.py +54 -21
- 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 +7 -9
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
- 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/payments/config/constance/__init__.py +0 -22
- django_cfg/apps/payments/config/constance/config_service.py +0 -123
- django_cfg/apps/payments/config/constance/fields.py +0 -69
- django_cfg/apps/payments/config/constance/settings.py +0 -160
- django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
- django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
- django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
- django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
- django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
- django_cfg/apps/tasks/templates/tasks/base.html +0 -96
- django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
- django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
- django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
- django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
- django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
- django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
- django_cfg/apps/tasks/views.py +0 -461
- django_cfg/management/commands/auto_generate.py +0 -486
- 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.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -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
|
+
]
|
django_cfg/models/constance.py
CHANGED
@@ -181,17 +181,6 @@ class ConstanceConfig(BaseModel, BaseCfgAutoModule):
|
|
181
181
|
except (ImportError, Exception):
|
182
182
|
pass
|
183
183
|
|
184
|
-
# Get fields from payments app (only if enabled)
|
185
|
-
if config and config.payments and config.payments.enabled:
|
186
|
-
try:
|
187
|
-
from django_cfg.apps.payments.config import get_django_cfg_payments_constance_fields
|
188
|
-
payments_fields = get_django_cfg_payments_constance_fields()
|
189
|
-
app_fields.extend(payments_fields)
|
190
|
-
print(f"✅ Added {len(payments_fields)} payments fields to Constance")
|
191
|
-
except (ImportError, Exception) as e:
|
192
|
-
print(f"❌ Failed to load payments constance fields: {e}")
|
193
|
-
traceback.print_exc()
|
194
|
-
|
195
184
|
# Cache the result
|
196
185
|
self._app_fields_cache = app_fields
|
197
186
|
return app_fields
|
django_cfg/models/payments.py
CHANGED
@@ -6,14 +6,110 @@ Simple, clean configuration following django-cfg philosophy.
|
|
6
6
|
|
7
7
|
from pydantic import BaseModel, Field
|
8
8
|
from typing import List, Dict, Optional
|
9
|
+
from django_cfg.models.cfg import BaseCfgAutoModule
|
9
10
|
|
10
11
|
|
11
|
-
class
|
12
|
+
class BaseProviderConfig(BaseModel):
|
13
|
+
"""Base configuration for payment providers."""
|
14
|
+
|
15
|
+
provider_name: str = Field(..., description="Provider name")
|
16
|
+
enabled: bool = Field(default=True, description="Whether provider is enabled")
|
17
|
+
|
18
|
+
def get_provider_config(self) -> Dict[str, any]:
|
19
|
+
"""Get provider-specific configuration."""
|
20
|
+
return {
|
21
|
+
'provider_name': self.provider_name,
|
22
|
+
'enabled': self.enabled,
|
23
|
+
}
|
24
|
+
|
25
|
+
|
26
|
+
class NowPaymentsProviderConfig(BaseProviderConfig):
|
27
|
+
"""NowPayments provider configuration."""
|
28
|
+
|
29
|
+
provider_name: str = Field(default="nowpayments", description="Provider name")
|
30
|
+
api_key: str = Field(default="", description="NowPayments API key")
|
31
|
+
ipn_secret: str = Field(default="", description="NowPayments IPN secret for webhook validation")
|
32
|
+
sandbox_mode: bool = Field(default=True, description="NowPayments sandbox mode")
|
33
|
+
|
34
|
+
def get_provider_config(self) -> Dict[str, any]:
|
35
|
+
"""Get NowPayments-specific configuration."""
|
36
|
+
return {
|
37
|
+
'provider_name': self.provider_name,
|
38
|
+
'enabled': self.enabled and bool(self.api_key.strip()),
|
39
|
+
'api_key': self.api_key,
|
40
|
+
'ipn_secret': self.ipn_secret,
|
41
|
+
'sandbox_mode': self.sandbox_mode,
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
# Future provider configs (commented out for now)
|
46
|
+
# class StripeProviderConfig(BaseProviderConfig):
|
47
|
+
# """Stripe provider configuration."""
|
48
|
+
#
|
49
|
+
# provider_name: str = Field(default="stripe", description="Provider name")
|
50
|
+
# api_key: str = Field(default="", description="Stripe API key")
|
51
|
+
# webhook_secret: str = Field(default="", description="Stripe webhook secret")
|
52
|
+
#
|
53
|
+
# def get_provider_config(self) -> Dict[str, any]:
|
54
|
+
# return {
|
55
|
+
# 'provider_name': self.provider_name,
|
56
|
+
# 'enabled': self.enabled and bool(self.api_key.strip()),
|
57
|
+
# 'api_key': self.api_key,
|
58
|
+
# 'webhook_secret': self.webhook_secret,
|
59
|
+
# }
|
60
|
+
|
61
|
+
|
62
|
+
class ProviderAPIKeysConfig(BaseModel):
|
63
|
+
"""
|
64
|
+
API keys configuration for payment providers.
|
65
|
+
|
66
|
+
Stores list of provider configurations.
|
67
|
+
"""
|
68
|
+
|
69
|
+
providers: List[BaseProviderConfig] = Field(
|
70
|
+
default_factory=list,
|
71
|
+
description="List of provider configurations"
|
72
|
+
)
|
73
|
+
|
74
|
+
def add_provider(self, provider_config: BaseProviderConfig):
|
75
|
+
"""Add a provider configuration."""
|
76
|
+
# Remove existing provider with same name
|
77
|
+
self.providers = [p for p in self.providers if p.provider_name != provider_config.provider_name]
|
78
|
+
self.providers.append(provider_config)
|
79
|
+
|
80
|
+
def get_provider_config(self, provider: str) -> Dict[str, any]:
|
81
|
+
"""Get provider-specific configuration."""
|
82
|
+
provider_lower = provider.lower()
|
83
|
+
|
84
|
+
for provider_config in self.providers:
|
85
|
+
if provider_config.provider_name.lower() == provider_lower:
|
86
|
+
return provider_config.get_provider_config()
|
87
|
+
|
88
|
+
return {'enabled': False, 'provider_name': provider}
|
89
|
+
|
90
|
+
def get_enabled_providers(self) -> List[str]:
|
91
|
+
"""Get list of enabled providers."""
|
92
|
+
enabled = []
|
93
|
+
for provider_config in self.providers:
|
94
|
+
config = provider_config.get_provider_config()
|
95
|
+
if config.get('enabled', False):
|
96
|
+
enabled.append(provider_config.provider_name)
|
97
|
+
return enabled
|
98
|
+
|
99
|
+
def get_provider_by_name(self, provider_name: str) -> Optional[BaseProviderConfig]:
|
100
|
+
"""Get provider configuration by name."""
|
101
|
+
for provider_config in self.providers:
|
102
|
+
if provider_config.provider_name.lower() == provider_name.lower():
|
103
|
+
return provider_config
|
104
|
+
return None
|
105
|
+
|
106
|
+
|
107
|
+
class PaymentsConfig(BaseModel, BaseCfgAutoModule):
|
12
108
|
"""
|
13
109
|
Payments app configuration for django-cfg.
|
14
110
|
|
15
|
-
|
16
|
-
|
111
|
+
Includes both static configuration and API keys.
|
112
|
+
API keys are now managed through BaseCfgAutoModule instead of Constance.
|
17
113
|
"""
|
18
114
|
|
19
115
|
# Core settings
|
@@ -22,6 +118,12 @@ class PaymentsConfig(BaseModel):
|
|
22
118
|
description="Enable payments app"
|
23
119
|
)
|
24
120
|
|
121
|
+
# API keys configuration
|
122
|
+
api_keys: ProviderAPIKeysConfig = Field(
|
123
|
+
default_factory=ProviderAPIKeysConfig,
|
124
|
+
description="API keys and secrets for payment providers"
|
125
|
+
)
|
126
|
+
|
25
127
|
# Middleware settings
|
26
128
|
middleware_enabled: bool = Field(
|
27
129
|
default=True,
|
@@ -207,6 +309,29 @@ class PaymentsConfig(BaseModel):
|
|
207
309
|
|
208
310
|
return items
|
209
311
|
|
312
|
+
# API Keys access methods
|
313
|
+
def get_provider_api_config(self, provider: str) -> Dict[str, any]:
|
314
|
+
"""Get provider-specific API configuration."""
|
315
|
+
return self.api_keys.get_provider_config(provider)
|
316
|
+
|
317
|
+
def get_enabled_providers(self) -> List[str]:
|
318
|
+
"""Get list of enabled providers (those with API keys configured)."""
|
319
|
+
return self.api_keys.get_enabled_providers()
|
320
|
+
|
321
|
+
def is_provider_enabled(self, provider: str) -> bool:
|
322
|
+
"""Check if a specific provider is enabled (has API keys configured)."""
|
323
|
+
config = self.api_keys.get_provider_config(provider)
|
324
|
+
return config.get('enabled', False)
|
325
|
+
|
326
|
+
# BaseCfgAutoModule implementation
|
327
|
+
def get_smart_defaults(self):
|
328
|
+
"""Get smart default configuration for this module."""
|
329
|
+
return PaymentsConfig()
|
330
|
+
|
331
|
+
def get_module_config(self):
|
332
|
+
"""Get the final configuration for this module."""
|
333
|
+
return self
|
334
|
+
|
210
335
|
@classmethod
|
211
336
|
def get_current_config(cls) -> 'PaymentsConfig':
|
212
337
|
"""
|
@@ -220,3 +345,12 @@ class PaymentsConfig(BaseModel):
|
|
220
345
|
return PaymentsConfigManager.get_payments_config_safe()
|
221
346
|
except Exception:
|
222
347
|
return cls()
|
348
|
+
|
349
|
+
|
350
|
+
# Export all payment configuration classes
|
351
|
+
__all__ = [
|
352
|
+
"BaseProviderConfig",
|
353
|
+
"NowPaymentsProviderConfig",
|
354
|
+
"ProviderAPIKeysConfig",
|
355
|
+
"PaymentsConfig",
|
356
|
+
]
|
@@ -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)
|