django-cfg 1.4.73__py3-none-any.whl → 1.4.75__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/api/endpoints/README.md +144 -0
- django_cfg/apps/api/endpoints/endpoints_status/__init__.py +13 -0
- django_cfg/apps/api/endpoints/urls.py +13 -6
- django_cfg/apps/api/endpoints/urls_list/__init__.py +10 -0
- django_cfg/apps/api/endpoints/urls_list/serializers.py +74 -0
- django_cfg/apps/api/endpoints/urls_list/views.py +231 -0
- django_cfg/apps/api/health/drf_views.py +36 -0
- django_cfg/apps/api/health/serializers.py +4 -0
- django_cfg/core/generation/integration_generators/api.py +6 -0
- django_cfg/middleware/authentication.py +27 -0
- django_cfg/models/django/crypto_fields.py +11 -0
- django_cfg/pyproject.toml +1 -1
- {django_cfg-1.4.73.dist-info → django_cfg-1.4.75.dist-info}/METADATA +1 -1
- {django_cfg-1.4.73.dist-info → django_cfg-1.4.75.dist-info}/RECORD +23 -18
- /django_cfg/apps/api/endpoints/{checker.py → endpoints_status/checker.py} +0 -0
- /django_cfg/apps/api/endpoints/{drf_views.py → endpoints_status/drf_views.py} +0 -0
- /django_cfg/apps/api/endpoints/{serializers.py → endpoints_status/serializers.py} +0 -0
- /django_cfg/apps/api/endpoints/{tests.py → endpoints_status/tests.py} +0 -0
- /django_cfg/apps/api/endpoints/{views.py → endpoints_status/views.py} +0 -0
- {django_cfg-1.4.73.dist-info → django_cfg-1.4.75.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.73.dist-info → django_cfg-1.4.75.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.73.dist-info → django_cfg-1.4.75.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Django CFG Endpoints API
|
|
2
|
+
|
|
3
|
+
Модуль для работы с Django URL endpoints.
|
|
4
|
+
|
|
5
|
+
## Структура
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
endpoints/
|
|
9
|
+
├── endpoints_status/ # Проверка статуса всех endpoints
|
|
10
|
+
│ ├── checker.py # Логика проверки endpoints
|
|
11
|
+
│ ├── drf_views.py # DRF views
|
|
12
|
+
│ ├── serializers.py # Serializers
|
|
13
|
+
│ ├── tests.py # Tests
|
|
14
|
+
│ └── views.py # Plain Django views
|
|
15
|
+
├── urls_list/ # Список всех URL в Django
|
|
16
|
+
│ ├── views.py # DRF views для вывода URLs
|
|
17
|
+
│ └── serializers.py # Serializers
|
|
18
|
+
└── urls.py # URL routing
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Endpoints
|
|
22
|
+
|
|
23
|
+
### Endpoints Status
|
|
24
|
+
|
|
25
|
+
Проверяет здоровье всех зарегистрированных endpoints в Django.
|
|
26
|
+
|
|
27
|
+
- **DRF (Browsable)**: `/cfg/endpoints/drf/`
|
|
28
|
+
- **JSON**: `/cfg/endpoints/`
|
|
29
|
+
|
|
30
|
+
**Query параметры:**
|
|
31
|
+
- `include_unnamed` (bool): Включить endpoints без имени (default: false)
|
|
32
|
+
- `timeout` (int): Timeout запроса в секундах (default: 5)
|
|
33
|
+
- `auto_auth` (bool): Автоматически повторить с JWT при 401/403 (default: true)
|
|
34
|
+
|
|
35
|
+
### URLs List
|
|
36
|
+
|
|
37
|
+
Выводит список всех зарегистрированных URL patterns в Django.
|
|
38
|
+
|
|
39
|
+
- **Full details**: `/cfg/endpoints/urls/`
|
|
40
|
+
- **Compact**: `/cfg/endpoints/urls/compact/`
|
|
41
|
+
|
|
42
|
+
**Compact** возвращает только pattern + name для каждого URL.
|
|
43
|
+
|
|
44
|
+
**Full details** возвращает:
|
|
45
|
+
- Pattern (regex или typed)
|
|
46
|
+
- Name
|
|
47
|
+
- Full name (с namespace)
|
|
48
|
+
- Namespace
|
|
49
|
+
- View name
|
|
50
|
+
- View class
|
|
51
|
+
- HTTP methods
|
|
52
|
+
- Module path
|
|
53
|
+
|
|
54
|
+
## Примеры
|
|
55
|
+
|
|
56
|
+
### URLs List (Full)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
curl http://localhost:8000/cfg/endpoints/urls/
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"status": "success",
|
|
65
|
+
"service": "Django CFG",
|
|
66
|
+
"version": "2.0.0",
|
|
67
|
+
"base_url": "http://localhost:8000",
|
|
68
|
+
"total_urls": 150,
|
|
69
|
+
"urls": [
|
|
70
|
+
{
|
|
71
|
+
"pattern": "/api/accounts/profile/",
|
|
72
|
+
"name": "account_profile",
|
|
73
|
+
"full_name": "api:account_profile",
|
|
74
|
+
"namespace": "api",
|
|
75
|
+
"view": "ProfileViewSet",
|
|
76
|
+
"view_class": "ProfileViewSet",
|
|
77
|
+
"methods": ["get", "post", "put", "patch", "delete"],
|
|
78
|
+
"module": "apps.accounts.views"
|
|
79
|
+
},
|
|
80
|
+
...
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### URLs List (Compact)
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
curl http://localhost:8000/cfg/endpoints/urls/compact/
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"status": "success",
|
|
94
|
+
"total": 150,
|
|
95
|
+
"urls": [
|
|
96
|
+
{
|
|
97
|
+
"pattern": "/api/accounts/profile/",
|
|
98
|
+
"name": "account_profile"
|
|
99
|
+
},
|
|
100
|
+
...
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Endpoints Status
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
curl http://localhost:8000/cfg/endpoints/drf/
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"status": "healthy",
|
|
114
|
+
"timestamp": "2025-10-26T10:30:00Z",
|
|
115
|
+
"total_endpoints": 100,
|
|
116
|
+
"healthy": 95,
|
|
117
|
+
"unhealthy": 0,
|
|
118
|
+
"warnings": 3,
|
|
119
|
+
"errors": 0,
|
|
120
|
+
"skipped": 2,
|
|
121
|
+
"endpoints": [...]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Health Check Integration
|
|
126
|
+
|
|
127
|
+
URLs list endpoint доступен из health check:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
curl http://localhost:8000/cfg/health/drf/
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"status": "healthy",
|
|
136
|
+
...
|
|
137
|
+
"links": {
|
|
138
|
+
"urls_list": "http://localhost:8000/cfg/endpoints/urls/",
|
|
139
|
+
"urls_list_compact": "http://localhost:8000/cfg/endpoints/urls/compact/",
|
|
140
|
+
"endpoints_status": "http://localhost:8000/cfg/endpoints/drf/",
|
|
141
|
+
"quick_health": "http://localhost:8000/cfg/health/drf/quick/"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django CFG Endpoints Status module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .checker import check_all_endpoints
|
|
6
|
+
from .drf_views import DRFEndpointsStatusView
|
|
7
|
+
from .views import EndpointsStatusView
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'check_all_endpoints',
|
|
11
|
+
'DRFEndpointsStatusView',
|
|
12
|
+
'EndpointsStatusView',
|
|
13
|
+
]
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Django CFG Endpoints
|
|
2
|
+
Django CFG Endpoints URLs.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from django.urls import path
|
|
6
6
|
|
|
7
|
-
from . import
|
|
7
|
+
from .endpoints_status import DRFEndpointsStatusView, EndpointsStatusView
|
|
8
|
+
from .urls_list import DRFURLsListCompactView, DRFURLsListView
|
|
8
9
|
|
|
9
10
|
urlpatterns = [
|
|
10
|
-
# Original JSON endpoint
|
|
11
|
-
path('',
|
|
11
|
+
# Endpoints Status - Original JSON endpoint
|
|
12
|
+
path('', EndpointsStatusView.as_view(), name='endpoints_status'),
|
|
12
13
|
|
|
13
|
-
# DRF Browsable API endpoint with Tailwind theme
|
|
14
|
-
path('drf/',
|
|
14
|
+
# Endpoints Status - DRF Browsable API endpoint with Tailwind theme
|
|
15
|
+
path('drf/', DRFEndpointsStatusView.as_view(), name='endpoints_status_drf'),
|
|
16
|
+
|
|
17
|
+
# URLs List - Full details
|
|
18
|
+
path('urls/', DRFURLsListView.as_view(), name='urls_list'),
|
|
19
|
+
|
|
20
|
+
# URLs List - Compact (pattern + name only)
|
|
21
|
+
path('urls/compact/', DRFURLsListCompactView.as_view(), name='urls_list_compact'),
|
|
15
22
|
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django CFG URLs List Serializers
|
|
3
|
+
|
|
4
|
+
DRF serializers for URLs list API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from rest_framework import serializers
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class URLPatternSerializer(serializers.Serializer):
|
|
11
|
+
"""Serializer for single URL pattern."""
|
|
12
|
+
|
|
13
|
+
pattern = serializers.CharField(
|
|
14
|
+
help_text="URL pattern (e.g., ^api/users/(?P<pk>[^/.]+)/$)"
|
|
15
|
+
)
|
|
16
|
+
name = serializers.CharField(
|
|
17
|
+
required=False,
|
|
18
|
+
allow_null=True,
|
|
19
|
+
help_text="URL name (if defined)"
|
|
20
|
+
)
|
|
21
|
+
full_name = serializers.CharField(
|
|
22
|
+
required=False,
|
|
23
|
+
allow_null=True,
|
|
24
|
+
help_text="Full URL name with namespace (e.g., admin:index)"
|
|
25
|
+
)
|
|
26
|
+
namespace = serializers.CharField(
|
|
27
|
+
required=False,
|
|
28
|
+
allow_null=True,
|
|
29
|
+
help_text="URL namespace"
|
|
30
|
+
)
|
|
31
|
+
view = serializers.CharField(
|
|
32
|
+
required=False,
|
|
33
|
+
allow_null=True,
|
|
34
|
+
help_text="View function/class name"
|
|
35
|
+
)
|
|
36
|
+
view_class = serializers.CharField(
|
|
37
|
+
required=False,
|
|
38
|
+
allow_null=True,
|
|
39
|
+
help_text="View class name (for CBV/ViewSets)"
|
|
40
|
+
)
|
|
41
|
+
methods = serializers.ListField(
|
|
42
|
+
child=serializers.CharField(),
|
|
43
|
+
required=False,
|
|
44
|
+
help_text="Allowed HTTP methods"
|
|
45
|
+
)
|
|
46
|
+
module = serializers.CharField(
|
|
47
|
+
required=False,
|
|
48
|
+
allow_null=True,
|
|
49
|
+
help_text="View module path"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class URLsListSerializer(serializers.Serializer):
|
|
54
|
+
"""Serializer for URLs list response."""
|
|
55
|
+
|
|
56
|
+
status = serializers.CharField(
|
|
57
|
+
help_text="Status: success or error"
|
|
58
|
+
)
|
|
59
|
+
service = serializers.CharField(
|
|
60
|
+
help_text="Service name"
|
|
61
|
+
)
|
|
62
|
+
version = serializers.CharField(
|
|
63
|
+
help_text="Django-CFG version"
|
|
64
|
+
)
|
|
65
|
+
base_url = serializers.CharField(
|
|
66
|
+
help_text="Base URL of the service"
|
|
67
|
+
)
|
|
68
|
+
total_urls = serializers.IntegerField(
|
|
69
|
+
help_text="Total number of registered URLs"
|
|
70
|
+
)
|
|
71
|
+
urls = URLPatternSerializer(
|
|
72
|
+
many=True,
|
|
73
|
+
help_text="List of all registered URL patterns"
|
|
74
|
+
)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django CFG URLs List DRF View
|
|
3
|
+
|
|
4
|
+
DRF browsable API view for listing all Django URLs with Tailwind theme support.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List
|
|
8
|
+
from urllib.parse import urljoin
|
|
9
|
+
|
|
10
|
+
from django.conf import settings
|
|
11
|
+
from django.urls import URLPattern, URLResolver, get_resolver
|
|
12
|
+
from rest_framework import status
|
|
13
|
+
from rest_framework.permissions import AllowAny
|
|
14
|
+
from rest_framework.response import Response
|
|
15
|
+
from rest_framework.views import APIView
|
|
16
|
+
|
|
17
|
+
from django_cfg.core.integration import get_current_version
|
|
18
|
+
|
|
19
|
+
from .serializers import URLsListSerializer
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DRFURLsListView(APIView):
|
|
23
|
+
"""
|
|
24
|
+
Django CFG URLs list endpoint with DRF Browsable API.
|
|
25
|
+
|
|
26
|
+
Lists all registered Django URLs with their:
|
|
27
|
+
- Pattern
|
|
28
|
+
- Name
|
|
29
|
+
- View/ViewSet
|
|
30
|
+
- Full URL
|
|
31
|
+
- HTTP methods
|
|
32
|
+
|
|
33
|
+
This endpoint uses DRF Browsable API with Tailwind CSS theme! 🎨
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
permission_classes = [AllowAny] # Public endpoint (can be restricted)
|
|
37
|
+
serializer_class = URLsListSerializer # For schema generation
|
|
38
|
+
|
|
39
|
+
def get(self, request):
|
|
40
|
+
"""Return all registered URLs."""
|
|
41
|
+
try:
|
|
42
|
+
config = getattr(settings, 'config', None)
|
|
43
|
+
|
|
44
|
+
# Get base URL from config or settings
|
|
45
|
+
base_url = getattr(config, 'site_url', None) if config else None
|
|
46
|
+
if not base_url:
|
|
47
|
+
base_url = request.build_absolute_uri('/').rstrip('/')
|
|
48
|
+
|
|
49
|
+
urls_data = {
|
|
50
|
+
"status": "success",
|
|
51
|
+
"service": config.project_name if config else "Django CFG",
|
|
52
|
+
"version": get_current_version(),
|
|
53
|
+
"base_url": base_url,
|
|
54
|
+
"total_urls": 0,
|
|
55
|
+
"urls": []
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Extract all URLs
|
|
59
|
+
url_patterns = self._get_all_urls()
|
|
60
|
+
urls_data["urls"] = url_patterns
|
|
61
|
+
urls_data["total_urls"] = len(url_patterns)
|
|
62
|
+
|
|
63
|
+
return Response(urls_data, status=status.HTTP_200_OK)
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
return Response({
|
|
67
|
+
"status": "error",
|
|
68
|
+
"error": str(e)
|
|
69
|
+
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
70
|
+
|
|
71
|
+
def _get_all_urls(self, urlpatterns=None, prefix='', namespace=None) -> List[Dict[str, Any]]:
|
|
72
|
+
"""
|
|
73
|
+
Recursively extract all URL patterns from Django URLconf.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
urlpatterns: URL patterns to process
|
|
77
|
+
prefix: URL prefix from parent resolvers
|
|
78
|
+
namespace: Current namespace
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
List of URL pattern dictionaries
|
|
82
|
+
"""
|
|
83
|
+
if urlpatterns is None:
|
|
84
|
+
urlpatterns = get_resolver().url_patterns
|
|
85
|
+
|
|
86
|
+
url_list = []
|
|
87
|
+
|
|
88
|
+
for pattern in urlpatterns:
|
|
89
|
+
if isinstance(pattern, URLResolver):
|
|
90
|
+
# Recursively process URL resolver (include())
|
|
91
|
+
new_prefix = prefix + str(pattern.pattern)
|
|
92
|
+
new_namespace = f"{namespace}:{pattern.namespace}" if namespace and pattern.namespace else pattern.namespace or namespace
|
|
93
|
+
|
|
94
|
+
url_list.extend(
|
|
95
|
+
self._get_all_urls(
|
|
96
|
+
pattern.url_patterns,
|
|
97
|
+
prefix=new_prefix,
|
|
98
|
+
namespace=new_namespace
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
elif isinstance(pattern, URLPattern):
|
|
102
|
+
# Extract URL pattern details
|
|
103
|
+
url_pattern = prefix + str(pattern.pattern)
|
|
104
|
+
url_name = pattern.name
|
|
105
|
+
|
|
106
|
+
# Build full name with namespace
|
|
107
|
+
if namespace and url_name:
|
|
108
|
+
full_name = f"{namespace}:{url_name}"
|
|
109
|
+
else:
|
|
110
|
+
full_name = url_name
|
|
111
|
+
|
|
112
|
+
# Get view information
|
|
113
|
+
view_info = self._get_view_info(pattern)
|
|
114
|
+
|
|
115
|
+
url_list.append({
|
|
116
|
+
"pattern": url_pattern,
|
|
117
|
+
"name": url_name,
|
|
118
|
+
"full_name": full_name,
|
|
119
|
+
"namespace": namespace,
|
|
120
|
+
"view": view_info["view"],
|
|
121
|
+
"view_class": view_info["view_class"],
|
|
122
|
+
"methods": view_info["methods"],
|
|
123
|
+
"module": view_info["module"],
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
return url_list
|
|
127
|
+
|
|
128
|
+
def _get_view_info(self, pattern: URLPattern) -> Dict[str, Any]:
|
|
129
|
+
"""
|
|
130
|
+
Extract view information from URL pattern.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
pattern: URLPattern instance
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dictionary with view information
|
|
137
|
+
"""
|
|
138
|
+
view_info = {
|
|
139
|
+
"view": None,
|
|
140
|
+
"view_class": None,
|
|
141
|
+
"methods": [],
|
|
142
|
+
"module": None,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
callback = pattern.callback
|
|
147
|
+
|
|
148
|
+
if callback is None:
|
|
149
|
+
return view_info
|
|
150
|
+
|
|
151
|
+
# Get view name
|
|
152
|
+
if hasattr(callback, '__name__'):
|
|
153
|
+
view_info["view"] = callback.__name__
|
|
154
|
+
elif hasattr(callback, '__class__'):
|
|
155
|
+
view_info["view"] = callback.__class__.__name__
|
|
156
|
+
|
|
157
|
+
# Get view class (for CBV/ViewSets)
|
|
158
|
+
if hasattr(callback, 'cls'):
|
|
159
|
+
view_info["view_class"] = callback.cls.__name__
|
|
160
|
+
|
|
161
|
+
# Get HTTP methods from ViewSet/APIView
|
|
162
|
+
if hasattr(callback.cls, 'http_method_names'):
|
|
163
|
+
view_info["methods"] = callback.cls.http_method_names
|
|
164
|
+
|
|
165
|
+
# Get module
|
|
166
|
+
if hasattr(callback.cls, '__module__'):
|
|
167
|
+
view_info["module"] = callback.cls.__module__
|
|
168
|
+
|
|
169
|
+
# For function-based views
|
|
170
|
+
elif hasattr(callback, '__module__'):
|
|
171
|
+
view_info["module"] = callback.__module__
|
|
172
|
+
|
|
173
|
+
# Try to determine methods from decorator
|
|
174
|
+
if hasattr(callback, 'methods'):
|
|
175
|
+
view_info["methods"] = list(callback.methods)
|
|
176
|
+
else:
|
|
177
|
+
view_info["methods"] = ['GET'] # Default for FBV
|
|
178
|
+
|
|
179
|
+
except Exception:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
return view_info
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class DRFURLsListCompactView(APIView):
|
|
186
|
+
"""
|
|
187
|
+
Compact URLs list endpoint - just patterns and names.
|
|
188
|
+
|
|
189
|
+
This endpoint uses DRF Browsable API with Tailwind CSS theme! 🎨
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
permission_classes = [AllowAny]
|
|
193
|
+
|
|
194
|
+
def get(self, request):
|
|
195
|
+
"""Return compact URL list."""
|
|
196
|
+
try:
|
|
197
|
+
url_patterns = self._get_compact_urls()
|
|
198
|
+
|
|
199
|
+
return Response({
|
|
200
|
+
"status": "success",
|
|
201
|
+
"total": len(url_patterns),
|
|
202
|
+
"urls": url_patterns
|
|
203
|
+
}, status=status.HTTP_200_OK)
|
|
204
|
+
|
|
205
|
+
except Exception as e:
|
|
206
|
+
return Response({
|
|
207
|
+
"status": "error",
|
|
208
|
+
"error": str(e)
|
|
209
|
+
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
210
|
+
|
|
211
|
+
def _get_compact_urls(self, urlpatterns=None, prefix='') -> List[Dict[str, str]]:
|
|
212
|
+
"""Extract URLs in compact format."""
|
|
213
|
+
if urlpatterns is None:
|
|
214
|
+
urlpatterns = get_resolver().url_patterns
|
|
215
|
+
|
|
216
|
+
url_list = []
|
|
217
|
+
|
|
218
|
+
for pattern in urlpatterns:
|
|
219
|
+
if isinstance(pattern, URLResolver):
|
|
220
|
+
new_prefix = prefix + str(pattern.pattern)
|
|
221
|
+
url_list.extend(
|
|
222
|
+
self._get_compact_urls(pattern.url_patterns, prefix=new_prefix)
|
|
223
|
+
)
|
|
224
|
+
elif isinstance(pattern, URLPattern):
|
|
225
|
+
url_pattern = prefix + str(pattern.pattern)
|
|
226
|
+
url_list.append({
|
|
227
|
+
"pattern": url_pattern,
|
|
228
|
+
"name": pattern.name or "unnamed",
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
return url_list
|
|
@@ -13,6 +13,7 @@ import psutil
|
|
|
13
13
|
from django.conf import settings
|
|
14
14
|
from django.core.cache import cache
|
|
15
15
|
from django.db import connections
|
|
16
|
+
from django.urls import reverse
|
|
16
17
|
from django.utils import timezone
|
|
17
18
|
from rest_framework import status
|
|
18
19
|
from rest_framework.permissions import AllowAny
|
|
@@ -102,6 +103,17 @@ class DRFHealthCheckView(APIView):
|
|
|
102
103
|
"python_version": f"{os.sys.version_info.major}.{os.sys.version_info.minor}.{os.sys.version_info.micro}",
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
# Add useful links using reverse()
|
|
107
|
+
health_data["links"] = {
|
|
108
|
+
"urls_list": request.build_absolute_uri(reverse('urls_list')),
|
|
109
|
+
"urls_list_compact": request.build_absolute_uri(reverse('urls_list_compact')),
|
|
110
|
+
"endpoints_status": request.build_absolute_uri(reverse('endpoints_status_drf')),
|
|
111
|
+
"quick_health": request.build_absolute_uri(reverse('django_cfg_drf_quick_health')),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Add OpenAPI schema links
|
|
115
|
+
health_data["links"]["openapi_schemas"] = self._get_openapi_schema_links(request)
|
|
116
|
+
|
|
105
117
|
# Return appropriate HTTP status
|
|
106
118
|
http_status = status.HTTP_200_OK
|
|
107
119
|
if health_data["status"] == "unhealthy":
|
|
@@ -191,6 +203,30 @@ class DRFHealthCheckView(APIView):
|
|
|
191
203
|
"error": str(e)
|
|
192
204
|
}
|
|
193
205
|
|
|
206
|
+
def _get_openapi_schema_links(self, request) -> Dict[str, str]:
|
|
207
|
+
"""Get OpenAPI schema links for all configured groups."""
|
|
208
|
+
try:
|
|
209
|
+
from django_cfg.modules.django_client.core import get_openapi_service
|
|
210
|
+
|
|
211
|
+
service = get_openapi_service()
|
|
212
|
+
|
|
213
|
+
if not service.config or not service.is_enabled():
|
|
214
|
+
return {}
|
|
215
|
+
|
|
216
|
+
schema_links = {}
|
|
217
|
+
for group_name in service.get_group_names():
|
|
218
|
+
try:
|
|
219
|
+
schema_url_name = f'openapi-schema-{group_name}'
|
|
220
|
+
schema_links[group_name] = request.build_absolute_uri(reverse(schema_url_name))
|
|
221
|
+
except Exception:
|
|
222
|
+
# Skip if URL name doesn't exist
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
return schema_links
|
|
226
|
+
|
|
227
|
+
except Exception:
|
|
228
|
+
return {}
|
|
229
|
+
|
|
194
230
|
def _check_system_resources(self) -> Dict[str, Any]:
|
|
195
231
|
"""Check system resource usage."""
|
|
196
232
|
try:
|
|
@@ -28,6 +28,10 @@ class HealthCheckSerializer(serializers.Serializer):
|
|
|
28
28
|
environment = serializers.DictField(
|
|
29
29
|
help_text="Environment information"
|
|
30
30
|
)
|
|
31
|
+
links = serializers.DictField(
|
|
32
|
+
required=False,
|
|
33
|
+
help_text="Useful API endpoint links"
|
|
34
|
+
)
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
class QuickHealthSerializer(serializers.Serializer):
|
|
@@ -219,6 +219,12 @@ class APIFrameworksGenerator:
|
|
|
219
219
|
Returns:
|
|
220
220
|
Dictionary with Spectacular settings
|
|
221
221
|
"""
|
|
222
|
+
# Import authentication extension to register it with drf-spectacular
|
|
223
|
+
try:
|
|
224
|
+
from django_cfg.middleware import authentication # noqa: F401
|
|
225
|
+
except ImportError:
|
|
226
|
+
pass
|
|
227
|
+
|
|
222
228
|
# Check if Spectacular settings exist (from OpenAPI Client or elsewhere)
|
|
223
229
|
if not hasattr(self, '_has_spectacular_settings'):
|
|
224
230
|
return {}
|
|
@@ -16,6 +16,33 @@ logger = logging.getLogger(__name__)
|
|
|
16
16
|
User = get_user_model()
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
# Register OpenAPI extension for drf-spectacular
|
|
20
|
+
try:
|
|
21
|
+
from drf_spectacular.extensions import OpenApiAuthenticationExtension
|
|
22
|
+
|
|
23
|
+
class JWTAuthenticationWithLastLoginScheme(OpenApiAuthenticationExtension):
|
|
24
|
+
"""
|
|
25
|
+
OpenAPI authentication scheme for JWTAuthenticationWithLastLogin.
|
|
26
|
+
|
|
27
|
+
Registers the authentication scheme with drf-spectacular so it appears
|
|
28
|
+
correctly in the generated OpenAPI schema.
|
|
29
|
+
"""
|
|
30
|
+
target_class = 'django_cfg.middleware.authentication.JWTAuthenticationWithLastLogin'
|
|
31
|
+
name = 'jwtAuth'
|
|
32
|
+
|
|
33
|
+
def get_security_definition(self, auto_schema):
|
|
34
|
+
"""Return JWT Bearer token security definition."""
|
|
35
|
+
return {
|
|
36
|
+
'type': 'http',
|
|
37
|
+
'scheme': 'bearer',
|
|
38
|
+
'bearerFormat': 'JWT',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
except ImportError:
|
|
42
|
+
# drf-spectacular not installed, skip extension registration
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
19
46
|
class JWTAuthenticationWithLastLogin(JWTAuthentication):
|
|
20
47
|
"""
|
|
21
48
|
JWT Authentication that updates last_login on successful authentication.
|
|
@@ -58,6 +58,12 @@ class CryptoFieldsConfig(BaseModel):
|
|
|
58
58
|
description="Auto-create encryption keys if missing (None = auto: True in DEBUG, False in production)"
|
|
59
59
|
)
|
|
60
60
|
|
|
61
|
+
# === Django Revision Settings (for django-audit-fields dependency) ===
|
|
62
|
+
ignore_git_dir: bool = Field(
|
|
63
|
+
default=True,
|
|
64
|
+
description="Ignore git directory for django-revision (set DJANGO_REVISION_IGNORE_WORKING_DIR=True)"
|
|
65
|
+
)
|
|
66
|
+
|
|
61
67
|
def to_django_settings(self, base_dir: Path, is_production: bool, debug: bool) -> dict:
|
|
62
68
|
"""
|
|
63
69
|
Convert to Django settings dictionary with environment-aware defaults.
|
|
@@ -91,6 +97,11 @@ class CryptoFieldsConfig(BaseModel):
|
|
|
91
97
|
"AUTO_CREATE_KEYS": auto_create_keys,
|
|
92
98
|
}
|
|
93
99
|
|
|
100
|
+
# Disable django-revision git integration (required by django-audit-fields)
|
|
101
|
+
# RevisionField will use package metadata or pyproject.toml instead
|
|
102
|
+
if self.ignore_git_dir:
|
|
103
|
+
settings["DJANGO_REVISION_IGNORE_WORKING_DIR"] = True
|
|
104
|
+
|
|
94
105
|
return settings
|
|
95
106
|
|
|
96
107
|
@property
|
django_cfg/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "django-cfg"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.75"
|
|
8
8
|
description = "Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "ai-agents", "enterprise-django", "django-settings", "type-safe-config",]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-cfg
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.75
|
|
4
4
|
Summary: Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django.
|
|
5
5
|
Project-URL: Homepage, https://djangocfg.com
|
|
6
6
|
Project-URL: Documentation, https://djangocfg.com
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
django_cfg/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
django_cfg/__init__.py,sha256=
|
|
2
|
+
django_cfg/__init__.py,sha256=S1h1KwyZdE0iuWBEfVIhzPf3N8noHSKlzkrn6IwOo5k,1620
|
|
3
3
|
django_cfg/apps.py,sha256=72m3uuvyqGiLx6gOfE-BD3P61jddCCERuBOYpxTX518,1605
|
|
4
4
|
django_cfg/config.py,sha256=y4Z3rnYsHBE0TehpwAIPaxr---mkvyKrZGGsNwYso74,1398
|
|
5
5
|
django_cfg/apps/__init__.py,sha256=JtDmEYt1OcleWM2ZaeX0LKDnRQzPOavfaXBWG4ECB5Q,26
|
|
@@ -107,16 +107,21 @@ django_cfg/apps/api/__init__.py,sha256=fHkKXld_pw5Eiqz4tNb3Z02nx-7T7PnqgMh-qjkGB
|
|
|
107
107
|
django_cfg/apps/api/commands/__init__.py,sha256=FTmBMxSpI9rO6EljgkWn8e9pxh07ao5Y1kx2TzQmZSY,88
|
|
108
108
|
django_cfg/apps/api/commands/urls.py,sha256=GSZgAsB-VIhAxq9LU5DzUtYAy-RGDRiB9P41P1jqbcc,361
|
|
109
109
|
django_cfg/apps/api/commands/views.py,sha256=daSkYPRl1HYgepCCowYz7DJOZtqdp1mapUkXHZwkTpw,9827
|
|
110
|
+
django_cfg/apps/api/endpoints/README.md,sha256=eLqzpUWcOtBtrKL_r9zfE4mNK863lkJpbAtL_L9wGmI,3326
|
|
110
111
|
django_cfg/apps/api/endpoints/__init__.py,sha256=uHjV4E24Aj0UFgv7bW1Z0kH_NFe8PItNFuS105vvRz0,108
|
|
111
|
-
django_cfg/apps/api/endpoints/
|
|
112
|
-
django_cfg/apps/api/endpoints/
|
|
113
|
-
django_cfg/apps/api/endpoints/
|
|
114
|
-
django_cfg/apps/api/endpoints/
|
|
115
|
-
django_cfg/apps/api/endpoints/
|
|
116
|
-
django_cfg/apps/api/endpoints/
|
|
112
|
+
django_cfg/apps/api/endpoints/urls.py,sha256=vovwkICejTYtk0ZWOZpTOVrbi2hP_bR1YNNn4v8MPIU,726
|
|
113
|
+
django_cfg/apps/api/endpoints/endpoints_status/__init__.py,sha256=KjdWJjbRZ1-1LiBfR1Wch3oPV4ANwiykIKFBW3woOJQ,270
|
|
114
|
+
django_cfg/apps/api/endpoints/endpoints_status/checker.py,sha256=Qu4VtsG9wRd2isqo4-Kk_gDhWlpT_fkqb7GoERvW_C8,19367
|
|
115
|
+
django_cfg/apps/api/endpoints/endpoints_status/drf_views.py,sha256=nAGIofRtdp4ne7dWouJuMJ984kB93kYruW3HnPfsLZg,1954
|
|
116
|
+
django_cfg/apps/api/endpoints/endpoints_status/serializers.py,sha256=W5Az0m1jUi8rLLIMoKVBADk6KHFyAWrwJ_gzSinktno,3656
|
|
117
|
+
django_cfg/apps/api/endpoints/endpoints_status/tests.py,sha256=PpOwpZ7Rm5eOtbVn-6Ha8yagvpuwusbHkB5FYYs7GHY,9993
|
|
118
|
+
django_cfg/apps/api/endpoints/endpoints_status/views.py,sha256=10zyQyQllrVJS44R2GAY9VV4_4Zl14a7R4dYrQmVJKw,1243
|
|
119
|
+
django_cfg/apps/api/endpoints/urls_list/__init__.py,sha256=aspLNziW0Ys4v-Bbf0lu_1G0RgySHwg16mMTnMY2dws,165
|
|
120
|
+
django_cfg/apps/api/endpoints/urls_list/serializers.py,sha256=hhtAM-PkskoJI9JLF7VKMczkJIaz2IkRMAI3r9Lyoks,1955
|
|
121
|
+
django_cfg/apps/api/endpoints/urls_list/views.py,sha256=Z3ULXsv1Vl8m0kpL6JxTTainMD1ZQIAVOX-3FOWjqKQ,7502
|
|
117
122
|
django_cfg/apps/api/health/__init__.py,sha256=ypFUiCo0I9VJpaZ_KqEPPX3-ma_MhRm5U1XYWahUaFI,93
|
|
118
|
-
django_cfg/apps/api/health/drf_views.py,sha256=
|
|
119
|
-
django_cfg/apps/api/health/serializers.py,sha256=
|
|
123
|
+
django_cfg/apps/api/health/drf_views.py,sha256=oaKaF5ZPR_lCMH_GyKcNOHX5blbW6nbE8VgpPQS27vA,10237
|
|
124
|
+
django_cfg/apps/api/health/serializers.py,sha256=JuFgWBRsycXsAZjZST33QO5li1z3S19l4xsLJ-ahWP8,1381
|
|
120
125
|
django_cfg/apps/api/health/urls.py,sha256=eCTzgB4rv_H2W0NrixPO6qsh0pT2oUePrYAT5D35QIY,550
|
|
121
126
|
django_cfg/apps/api/health/views.py,sha256=65oA6O3gaz2ECY2SiXlT3n4JpORt0nDufa_10um0nRw,8194
|
|
122
127
|
django_cfg/apps/centrifugo/__init__.py,sha256=VeMqGhK9OgFikSbF3ZgBEqQJ8y7YvKzbWmXsxunJ7FI,1833
|
|
@@ -550,7 +555,7 @@ django_cfg/core/generation/data_generators/__init__.py,sha256=O7cmt_k34hWRgrdaAz
|
|
|
550
555
|
django_cfg/core/generation/data_generators/cache.py,sha256=bdcbnaDarl_8UQ18gk7bIKrQ9PDZOhpDAVjCEgYVeUU,3840
|
|
551
556
|
django_cfg/core/generation/data_generators/database.py,sha256=mYR2mBvqWU93YP9XUUVlcZaQJtSNeAQ7Dk4Vj6Tu95k,3481
|
|
552
557
|
django_cfg/core/generation/integration_generators/__init__.py,sha256=-H4xlNAp_3yytmAnh7HuGJCloLL7C_WWlEJw3KZt4VY,599
|
|
553
|
-
django_cfg/core/generation/integration_generators/api.py,sha256=
|
|
558
|
+
django_cfg/core/generation/integration_generators/api.py,sha256=X9J7TNG7VX0Yc1An_6Ld50sN6IpJSSNZalfSRjkgbok,10916
|
|
554
559
|
django_cfg/core/generation/integration_generators/sessions.py,sha256=GbRC72Gny4dzj7oTIrbuhYyvhCQBoWI-0GxB8ZHTrEU,1644
|
|
555
560
|
django_cfg/core/generation/integration_generators/tailwind.py,sha256=YgiV5c-l0DDJnMKx6mWcUtH5zyMdewAJXjLDwmHrSuc,1208
|
|
556
561
|
django_cfg/core/generation/integration_generators/tasks.py,sha256=B-oeneUUdvjiE6N5MVC98imptlj4dkAxeoxfv50sbkM,2514
|
|
@@ -610,7 +615,7 @@ django_cfg/management/commands/validate_openapi.py,sha256=CdcoMhtxQhjTL6nCifAYkn
|
|
|
610
615
|
django_cfg/middleware/README.md,sha256=M-SvjLUygS6t_flzG4mcKSCSzU1xAVGvm9Lr3Qbsvn8,13089
|
|
611
616
|
django_cfg/middleware/__init__.py,sha256=EVah9AYIWT3R8inTqPY20oB4xb-KejhwqngM1Z4UrD8,465
|
|
612
617
|
django_cfg/middleware/admin_notifications.py,sha256=EVoXjSLaDz4r-lL5hy6V9sC1BpNF0rKsa-s6L31foRQ,6856
|
|
613
|
-
django_cfg/middleware/authentication.py,sha256=
|
|
618
|
+
django_cfg/middleware/authentication.py,sha256=1deekm3lyAMJ2pK7Usd5q1FtUN1aDq_CGJuZrJuHZtE,6139
|
|
614
619
|
django_cfg/middleware/pagination.py,sha256=xnaFH1olDH_m4ecwpTTUyFWV6jKoF6IxJvagSZSJR0E,8603
|
|
615
620
|
django_cfg/middleware/public_endpoints.py,sha256=p-VCjeu0k7B4OuQIe2OVUx7Onh9gsM2HweWcYNxEaRA,6775
|
|
616
621
|
django_cfg/middleware/user_activity.py,sha256=bgftHXXeGBS-jCa8mLzTx4Gcz2fHTaQz_nbRCR5Yqio,5980
|
|
@@ -632,7 +637,7 @@ django_cfg/models/base/module.py,sha256=nxN1Y9J4l94kOfSXLQJ2eGgIGWTq8kyh7hUGvCQN
|
|
|
632
637
|
django_cfg/models/django/__init__.py,sha256=xY_ts1oGmMROXfKHLuERqwG35dQf3EpsZxqj9DcRYwI,397
|
|
633
638
|
django_cfg/models/django/axes.py,sha256=-4nk2gSfpj7lNY5vnm_2jHVLz8VAKoEd9yF2TuCR8O8,5624
|
|
634
639
|
django_cfg/models/django/constance.py,sha256=E4U3HS_gFq5_q2nhI1R8inONGtD7IgvSwxOEGNGGrEI,9127
|
|
635
|
-
django_cfg/models/django/crypto_fields.py,sha256=
|
|
640
|
+
django_cfg/models/django/crypto_fields.py,sha256=HZgV0lA8T7hvx8Np0l1OQoJbM_fA1VIrSoMDwXP25RE,3426
|
|
636
641
|
django_cfg/models/django/environment.py,sha256=lBCHBs1lphv9tlu1BCTfLZeH_kUame0p66A_BIjBY7M,9440
|
|
637
642
|
django_cfg/models/django/openapi.py,sha256=avE3iapaCj8eyOqVUum_v2EExR3V-hwHrexqtXMHtTQ,3739
|
|
638
643
|
django_cfg/models/django/revolution_legacy.py,sha256=Z4SPUS7QSv62EuPAeFFoXGEgqLmdXnVEr7Ofk1IDtVc,8918
|
|
@@ -1111,9 +1116,9 @@ django_cfg/utils/version_check.py,sha256=WO51J2m2e-wVqWCRwbultEwu3q1lQasV67Mw2aa
|
|
|
1111
1116
|
django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
|
|
1112
1117
|
django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
|
|
1113
1118
|
django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1114
|
-
django_cfg/pyproject.toml,sha256=
|
|
1115
|
-
django_cfg-1.4.
|
|
1116
|
-
django_cfg-1.4.
|
|
1117
|
-
django_cfg-1.4.
|
|
1118
|
-
django_cfg-1.4.
|
|
1119
|
-
django_cfg-1.4.
|
|
1119
|
+
django_cfg/pyproject.toml,sha256=iIkejWhKhUWuv1BSqIJP0sgY6dG6mHgeV3r9Mo2dqr4,8164
|
|
1120
|
+
django_cfg-1.4.75.dist-info/METADATA,sha256=aUDyk3MZpCZSjSumnm5F-3-IvNOfe_EzCUhvdLCrPhA,22624
|
|
1121
|
+
django_cfg-1.4.75.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
1122
|
+
django_cfg-1.4.75.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
|
|
1123
|
+
django_cfg-1.4.75.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1124
|
+
django_cfg-1.4.75.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|