django-cfg 1.4.72__py3-none-any.whl → 1.4.74__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 +9 -0
- django_cfg/apps/api/health/serializers.py +4 -0
- django_cfg/models/django/crypto_fields.py +11 -0
- django_cfg/modules/django_client/core/__init__.py +2 -1
- django_cfg/modules/django_client/core/archive/manager.py +14 -0
- django_cfg/modules/django_client/core/generator/__init__.py +40 -2
- django_cfg/modules/django_client/core/generator/proto/__init__.py +17 -0
- django_cfg/modules/django_client/core/generator/proto/generator.py +461 -0
- django_cfg/modules/django_client/core/generator/proto/messages_generator.py +260 -0
- django_cfg/modules/django_client/core/generator/proto/services_generator.py +295 -0
- django_cfg/modules/django_client/core/generator/proto/test_proto_generator.py +262 -0
- django_cfg/modules/django_client/core/generator/proto/type_mapper.py +153 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +49 -3
- django_cfg/pyproject.toml +1 -1
- {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/METADATA +1 -1
- {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/RECORD +31 -20
- /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.72.dist-info → django_cfg-1.4.74.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.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,14 @@ 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
|
+
|
|
105
114
|
# Return appropriate HTTP status
|
|
106
115
|
http_status = status.HTTP_200_OK
|
|
107
116
|
if health_data["status"] == "unhealthy":
|
|
@@ -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):
|
|
@@ -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
|
|
@@ -19,7 +19,7 @@ from .config import (
|
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
# Generators
|
|
22
|
-
from .generator import GoGenerator, PythonGenerator, TypeScriptGenerator
|
|
22
|
+
from .generator import GoGenerator, ProtoGenerator, PythonGenerator, TypeScriptGenerator
|
|
23
23
|
|
|
24
24
|
# Groups
|
|
25
25
|
from .groups import GroupDetector, GroupManager
|
|
@@ -53,4 +53,5 @@ __all__ = [
|
|
|
53
53
|
"PythonGenerator",
|
|
54
54
|
"TypeScriptGenerator",
|
|
55
55
|
"GoGenerator",
|
|
56
|
+
"ProtoGenerator",
|
|
56
57
|
]
|
|
@@ -34,6 +34,8 @@ class ArchiveManager:
|
|
|
34
34
|
group_name: str,
|
|
35
35
|
python_dir: Optional[Path] = None,
|
|
36
36
|
typescript_dir: Optional[Path] = None,
|
|
37
|
+
go_dir: Optional[Path] = None,
|
|
38
|
+
proto_dir: Optional[Path] = None,
|
|
37
39
|
) -> Dict:
|
|
38
40
|
"""
|
|
39
41
|
Archive generated clients.
|
|
@@ -42,6 +44,8 @@ class ArchiveManager:
|
|
|
42
44
|
group_name: Name of the group
|
|
43
45
|
python_dir: Python client directory
|
|
44
46
|
typescript_dir: TypeScript client directory
|
|
47
|
+
go_dir: Go client directory
|
|
48
|
+
proto_dir: Protocol Buffer definitions directory
|
|
45
49
|
|
|
46
50
|
Returns:
|
|
47
51
|
Archive result dictionary
|
|
@@ -65,6 +69,16 @@ class ArchiveManager:
|
|
|
65
69
|
shutil.copytree(typescript_dir, dest, dirs_exist_ok=True)
|
|
66
70
|
copied["typescript"] = str(dest)
|
|
67
71
|
|
|
72
|
+
if go_dir and go_dir.exists():
|
|
73
|
+
dest = archive_path / "go"
|
|
74
|
+
shutil.copytree(go_dir, dest, dirs_exist_ok=True)
|
|
75
|
+
copied["go"] = str(dest)
|
|
76
|
+
|
|
77
|
+
if proto_dir and proto_dir.exists():
|
|
78
|
+
dest = archive_path / "proto"
|
|
79
|
+
shutil.copytree(proto_dir, dest, dirs_exist_ok=True)
|
|
80
|
+
copied["proto"] = str(dest)
|
|
81
|
+
|
|
68
82
|
# Create metadata
|
|
69
83
|
metadata = {
|
|
70
84
|
"group": group_name,
|