django-cfg 1.3.7__py3-none-any.whl → 1.3.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +258 -0
- django_cfg/apps/payments/admin/payments_admin.py +171 -461
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +105 -34
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
- django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
- django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +1 -1
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +95 -39
- django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
- django_cfg/apps/payments/services/providers/models/base.py +122 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/{nowpayments.py → nowpayments/provider.py} +240 -209
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +4 -32
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
- django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +5 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +4 -3
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/app_agent_diagnose.py +470 -0
- django_cfg/management/commands/app_agent_generate.py +342 -0
- django_cfg/management/commands/app_agent_info.py +308 -0
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_app_agent/__init__.py +87 -0
- django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
- django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
- django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
- django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
- django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
- django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
- django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
- django_cfg/modules/django_app_agent/core/__init__.py +33 -0
- django_cfg/modules/django_app_agent/core/config.py +300 -0
- django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
- django_cfg/modules/django_app_agent/models/__init__.py +71 -0
- django_cfg/modules/django_app_agent/models/base.py +283 -0
- django_cfg/modules/django_app_agent/models/context.py +496 -0
- django_cfg/modules/django_app_agent/models/enums.py +481 -0
- django_cfg/modules/django_app_agent/models/requests.py +500 -0
- django_cfg/modules/django_app_agent/models/responses.py +585 -0
- django_cfg/modules/django_app_agent/pytest.ini +6 -0
- django_cfg/modules/django_app_agent/services/__init__.py +42 -0
- django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
- django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
- django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
- django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
- django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
- django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
- django_cfg/modules/django_app_agent/services/base.py +437 -0
- django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
- django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
- django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
- django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
- django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
- django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
- django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
- django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
- django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
- django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
- django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
- django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
- django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
- django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
- django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
- django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
- django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
- django_cfg/modules/django_app_agent/services/report_service.py +332 -0
- django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
- django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
- django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
- django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
- django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
- django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
- django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
- django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
- django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
- django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
- django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
- django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
- django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
- django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
- django_cfg/modules/django_app_agent/ui/cli.py +419 -0
- django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
- django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
- django_cfg/modules/django_app_agent/utils/logging.py +360 -0
- django_cfg/modules/django_app_agent/utils/validation.py +417 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +3 -0
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/RECORD +223 -117
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,352 @@
|
|
1
|
+
"""
|
2
|
+
Code Quality Validator for Django App Agent.
|
3
|
+
|
4
|
+
This module validates code quality metrics, patterns,
|
5
|
+
and maintainability aspects of generated code.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import List, Dict, Any, Optional
|
9
|
+
import ast
|
10
|
+
import re
|
11
|
+
|
12
|
+
from pydantic import BaseModel, Field
|
13
|
+
|
14
|
+
from ...models.responses import GeneratedFile, QualityMetrics
|
15
|
+
from ..base import ServiceDependencies
|
16
|
+
from .models import ValidationIssue
|
17
|
+
|
18
|
+
|
19
|
+
class QualityValidator(BaseModel):
|
20
|
+
"""Validates code quality and maintainability patterns."""
|
21
|
+
|
22
|
+
quality_rules: Dict[str, Dict[str, Any]] = Field(
|
23
|
+
default_factory=lambda: {
|
24
|
+
"function_complexity": {
|
25
|
+
"description": "Functions should not be overly complex",
|
26
|
+
"severity": "warning",
|
27
|
+
"max_complexity": 10
|
28
|
+
},
|
29
|
+
"line_length": {
|
30
|
+
"description": "Lines should not exceed reasonable length",
|
31
|
+
"severity": "warning",
|
32
|
+
"max_length": 120
|
33
|
+
},
|
34
|
+
"docstring_coverage": {
|
35
|
+
"description": "Classes and functions should have docstrings",
|
36
|
+
"severity": "info"
|
37
|
+
},
|
38
|
+
"type_hints": {
|
39
|
+
"description": "Functions should have type hints",
|
40
|
+
"severity": "info"
|
41
|
+
},
|
42
|
+
"naming_conventions": {
|
43
|
+
"description": "Follow Python naming conventions",
|
44
|
+
"severity": "warning"
|
45
|
+
},
|
46
|
+
"code_duplication": {
|
47
|
+
"description": "Avoid code duplication",
|
48
|
+
"severity": "warning"
|
49
|
+
}
|
50
|
+
},
|
51
|
+
description="Code quality validation rules"
|
52
|
+
)
|
53
|
+
|
54
|
+
async def validate_quality(
|
55
|
+
self,
|
56
|
+
file: GeneratedFile,
|
57
|
+
dependencies: ServiceDependencies
|
58
|
+
) -> List[ValidationIssue]:
|
59
|
+
"""Validate code quality for a single file."""
|
60
|
+
issues = []
|
61
|
+
|
62
|
+
if file.file_type != "python":
|
63
|
+
return issues
|
64
|
+
|
65
|
+
try:
|
66
|
+
tree = ast.parse(file.content)
|
67
|
+
|
68
|
+
# Apply quality checks
|
69
|
+
issues.extend(self._check_complexity(file, tree, dependencies))
|
70
|
+
issues.extend(self._check_line_length(file, dependencies))
|
71
|
+
issues.extend(self._check_docstrings(file, tree, dependencies))
|
72
|
+
issues.extend(self._check_type_hints(file, tree, dependencies))
|
73
|
+
issues.extend(self._check_naming_conventions(file, tree, dependencies))
|
74
|
+
issues.extend(self._check_code_duplication(file, tree, dependencies))
|
75
|
+
|
76
|
+
except SyntaxError:
|
77
|
+
# Skip quality validation if syntax is invalid
|
78
|
+
pass
|
79
|
+
except Exception as e:
|
80
|
+
dependencies.log_error(f"Quality validation failed for {file.path}", e)
|
81
|
+
|
82
|
+
return issues
|
83
|
+
|
84
|
+
async def calculate_quality_metrics(
|
85
|
+
self,
|
86
|
+
files: List[GeneratedFile],
|
87
|
+
dependencies: ServiceDependencies
|
88
|
+
) -> QualityMetrics:
|
89
|
+
"""Calculate comprehensive quality metrics."""
|
90
|
+
total_lines = 0
|
91
|
+
total_functions = 0
|
92
|
+
functions_with_docstrings = 0
|
93
|
+
functions_with_type_hints = 0
|
94
|
+
classes_with_docstrings = 0
|
95
|
+
total_classes = 0
|
96
|
+
complexity_scores = []
|
97
|
+
|
98
|
+
for file in files:
|
99
|
+
if file.file_type != "python":
|
100
|
+
continue
|
101
|
+
|
102
|
+
try:
|
103
|
+
tree = ast.parse(file.content)
|
104
|
+
lines = file.content.split('\n')
|
105
|
+
total_lines += len([line for line in lines if line.strip()])
|
106
|
+
|
107
|
+
for node in ast.walk(tree):
|
108
|
+
if isinstance(node, ast.FunctionDef):
|
109
|
+
total_functions += 1
|
110
|
+
|
111
|
+
# Check docstring
|
112
|
+
if ast.get_docstring(node):
|
113
|
+
functions_with_docstrings += 1
|
114
|
+
|
115
|
+
# Check type hints
|
116
|
+
if self._has_type_hints(node):
|
117
|
+
functions_with_type_hints += 1
|
118
|
+
|
119
|
+
# Calculate complexity
|
120
|
+
complexity = self._calculate_cyclomatic_complexity(node)
|
121
|
+
complexity_scores.append(complexity)
|
122
|
+
|
123
|
+
elif isinstance(node, ast.ClassDef):
|
124
|
+
total_classes += 1
|
125
|
+
if ast.get_docstring(node):
|
126
|
+
classes_with_docstrings += 1
|
127
|
+
|
128
|
+
except SyntaxError:
|
129
|
+
continue
|
130
|
+
|
131
|
+
# Calculate metrics
|
132
|
+
docstring_coverage = 0.0
|
133
|
+
if total_functions + total_classes > 0:
|
134
|
+
docstring_coverage = (functions_with_docstrings + classes_with_docstrings) / (total_functions + total_classes) * 100
|
135
|
+
|
136
|
+
type_hint_coverage = 0.0
|
137
|
+
if total_functions > 0:
|
138
|
+
type_hint_coverage = functions_with_type_hints / total_functions * 100
|
139
|
+
|
140
|
+
avg_complexity = sum(complexity_scores) / len(complexity_scores) if complexity_scores else 0
|
141
|
+
|
142
|
+
# Calculate overall scores
|
143
|
+
overall_score = (docstring_coverage * 0.2 + type_hint_coverage * 0.3 +
|
144
|
+
max(0, 100 - avg_complexity * 10) * 0.5) / 10
|
145
|
+
|
146
|
+
return QualityMetrics(
|
147
|
+
overall_score=min(10.0, max(0.0, overall_score)),
|
148
|
+
type_safety_score=min(10.0, type_hint_coverage / 10),
|
149
|
+
pattern_consistency=8.5, # Would need more sophisticated analysis
|
150
|
+
code_complexity=max(0.0, 10.0 - avg_complexity),
|
151
|
+
test_coverage=0.0, # Would need test analysis
|
152
|
+
documentation_coverage=min(100.0, docstring_coverage), # Keep as percentage
|
153
|
+
performance_score=8.0, # Would need performance analysis
|
154
|
+
security_score=8.5, # Would need security analysis
|
155
|
+
maintainability_score=min(10.0, max(0.0, overall_score))
|
156
|
+
)
|
157
|
+
|
158
|
+
def _check_complexity(
|
159
|
+
self,
|
160
|
+
file: GeneratedFile,
|
161
|
+
tree: ast.AST,
|
162
|
+
dependencies: ServiceDependencies
|
163
|
+
) -> List[ValidationIssue]:
|
164
|
+
"""Check cyclomatic complexity of functions."""
|
165
|
+
issues = []
|
166
|
+
max_complexity = self.quality_rules["function_complexity"]["max_complexity"]
|
167
|
+
|
168
|
+
for node in ast.walk(tree):
|
169
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
170
|
+
complexity = self._calculate_cyclomatic_complexity(node)
|
171
|
+
|
172
|
+
if complexity > max_complexity:
|
173
|
+
issues.append(ValidationIssue(
|
174
|
+
severity="warning",
|
175
|
+
category="quality",
|
176
|
+
message=f"Function '{node.name}' has high complexity ({complexity})",
|
177
|
+
file_path=file.path,
|
178
|
+
line_number=node.lineno,
|
179
|
+
rule_id="high_complexity",
|
180
|
+
suggestion=f"Consider breaking down {node.name} into smaller functions"
|
181
|
+
))
|
182
|
+
|
183
|
+
return issues
|
184
|
+
|
185
|
+
def _check_line_length(
|
186
|
+
self,
|
187
|
+
file: GeneratedFile,
|
188
|
+
dependencies: ServiceDependencies
|
189
|
+
) -> List[ValidationIssue]:
|
190
|
+
"""Check for overly long lines."""
|
191
|
+
issues = []
|
192
|
+
max_length = self.quality_rules["line_length"]["max_length"]
|
193
|
+
|
194
|
+
lines = file.content.split('\n')
|
195
|
+
for i, line in enumerate(lines, 1):
|
196
|
+
if len(line) > max_length:
|
197
|
+
issues.append(ValidationIssue(
|
198
|
+
severity="info",
|
199
|
+
category="quality",
|
200
|
+
message=f"Line {i} exceeds {max_length} characters ({len(line)})",
|
201
|
+
file_path=file.path,
|
202
|
+
line_number=i,
|
203
|
+
rule_id="line_too_long",
|
204
|
+
suggestion="Break long lines for better readability"
|
205
|
+
))
|
206
|
+
|
207
|
+
return issues
|
208
|
+
|
209
|
+
def _check_docstrings(
|
210
|
+
self,
|
211
|
+
file: GeneratedFile,
|
212
|
+
tree: ast.AST,
|
213
|
+
dependencies: ServiceDependencies
|
214
|
+
) -> List[ValidationIssue]:
|
215
|
+
"""Check for missing docstrings."""
|
216
|
+
issues = []
|
217
|
+
|
218
|
+
for node in ast.walk(tree):
|
219
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
220
|
+
if not ast.get_docstring(node):
|
221
|
+
node_type = "Class" if isinstance(node, ast.ClassDef) else "Function"
|
222
|
+
issues.append(ValidationIssue(
|
223
|
+
severity="info",
|
224
|
+
category="quality",
|
225
|
+
message=f"{node_type} '{node.name}' lacks docstring",
|
226
|
+
file_path=file.path,
|
227
|
+
line_number=node.lineno,
|
228
|
+
rule_id="missing_docstring",
|
229
|
+
suggestion=f"Add a docstring to {node.name} explaining its purpose"
|
230
|
+
))
|
231
|
+
|
232
|
+
return issues
|
233
|
+
|
234
|
+
def _check_type_hints(
|
235
|
+
self,
|
236
|
+
file: GeneratedFile,
|
237
|
+
tree: ast.AST,
|
238
|
+
dependencies: ServiceDependencies
|
239
|
+
) -> List[ValidationIssue]:
|
240
|
+
"""Check for missing type hints."""
|
241
|
+
issues = []
|
242
|
+
|
243
|
+
for node in ast.walk(tree):
|
244
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
245
|
+
if not self._has_type_hints(node):
|
246
|
+
issues.append(ValidationIssue(
|
247
|
+
severity="info",
|
248
|
+
category="quality",
|
249
|
+
message=f"Function '{node.name}' lacks type hints",
|
250
|
+
file_path=file.path,
|
251
|
+
line_number=node.lineno,
|
252
|
+
rule_id="missing_type_hints",
|
253
|
+
suggestion=f"Add type hints to {node.name} parameters and return value"
|
254
|
+
))
|
255
|
+
|
256
|
+
return issues
|
257
|
+
|
258
|
+
def _check_naming_conventions(
|
259
|
+
self,
|
260
|
+
file: GeneratedFile,
|
261
|
+
tree: ast.AST,
|
262
|
+
dependencies: ServiceDependencies
|
263
|
+
) -> List[ValidationIssue]:
|
264
|
+
"""Check Python naming conventions."""
|
265
|
+
issues = []
|
266
|
+
|
267
|
+
for node in ast.walk(tree):
|
268
|
+
if isinstance(node, ast.ClassDef):
|
269
|
+
# Class names should be PascalCase
|
270
|
+
if not re.match(r'^[A-Z][a-zA-Z0-9]*$', node.name):
|
271
|
+
issues.append(ValidationIssue(
|
272
|
+
severity="warning",
|
273
|
+
category="quality",
|
274
|
+
message=f"Class '{node.name}' should use PascalCase",
|
275
|
+
file_path=file.path,
|
276
|
+
line_number=node.lineno,
|
277
|
+
rule_id="class_naming",
|
278
|
+
suggestion=f"Rename {node.name} to follow PascalCase convention"
|
279
|
+
))
|
280
|
+
|
281
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
282
|
+
# Function names should be snake_case
|
283
|
+
if not re.match(r'^[a-z_][a-z0-9_]*$', node.name) and not node.name.startswith('__'):
|
284
|
+
issues.append(ValidationIssue(
|
285
|
+
severity="warning",
|
286
|
+
category="quality",
|
287
|
+
message=f"Function '{node.name}' should use snake_case",
|
288
|
+
file_path=file.path,
|
289
|
+
line_number=node.lineno,
|
290
|
+
rule_id="function_naming",
|
291
|
+
suggestion=f"Rename {node.name} to follow snake_case convention"
|
292
|
+
))
|
293
|
+
|
294
|
+
return issues
|
295
|
+
|
296
|
+
def _check_code_duplication(
|
297
|
+
self,
|
298
|
+
file: GeneratedFile,
|
299
|
+
tree: ast.AST,
|
300
|
+
dependencies: ServiceDependencies
|
301
|
+
) -> List[ValidationIssue]:
|
302
|
+
"""Check for code duplication patterns."""
|
303
|
+
issues = []
|
304
|
+
|
305
|
+
# Simple duplication check - look for identical function bodies
|
306
|
+
function_bodies = {}
|
307
|
+
|
308
|
+
for node in ast.walk(tree):
|
309
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
310
|
+
# Convert function body to string for comparison
|
311
|
+
body_str = ast.dump(node.body)
|
312
|
+
|
313
|
+
if body_str in function_bodies:
|
314
|
+
issues.append(ValidationIssue(
|
315
|
+
severity="warning",
|
316
|
+
category="quality",
|
317
|
+
message=f"Function '{node.name}' has duplicate body with '{function_bodies[body_str]}'",
|
318
|
+
file_path=file.path,
|
319
|
+
line_number=node.lineno,
|
320
|
+
rule_id="code_duplication",
|
321
|
+
suggestion=f"Consider extracting common logic from {node.name} and {function_bodies[body_str]}"
|
322
|
+
))
|
323
|
+
else:
|
324
|
+
function_bodies[body_str] = node.name
|
325
|
+
|
326
|
+
return issues
|
327
|
+
|
328
|
+
def _calculate_cyclomatic_complexity(self, node: ast.AST) -> int:
|
329
|
+
"""Calculate cyclomatic complexity of a function."""
|
330
|
+
complexity = 1 # Base complexity
|
331
|
+
|
332
|
+
for child in ast.walk(node):
|
333
|
+
if isinstance(child, (ast.If, ast.While, ast.For, ast.AsyncFor)):
|
334
|
+
complexity += 1
|
335
|
+
elif isinstance(child, ast.ExceptHandler):
|
336
|
+
complexity += 1
|
337
|
+
elif isinstance(child, (ast.And, ast.Or)):
|
338
|
+
complexity += 1
|
339
|
+
elif isinstance(child, ast.comprehension):
|
340
|
+
complexity += 1
|
341
|
+
|
342
|
+
return complexity
|
343
|
+
|
344
|
+
def _has_type_hints(self, node: ast.FunctionDef) -> bool:
|
345
|
+
"""Check if function has type hints."""
|
346
|
+
# Check return annotation
|
347
|
+
has_return_hint = node.returns is not None
|
348
|
+
|
349
|
+
# Check parameter annotations
|
350
|
+
has_param_hints = any(arg.annotation is not None for arg in node.args.args)
|
351
|
+
|
352
|
+
return has_return_hint or has_param_hints
|
@@ -0,0 +1,272 @@
|
|
1
|
+
"""
|
2
|
+
Security Validator for Django App Agent.
|
3
|
+
|
4
|
+
This module validates security patterns and identifies potential
|
5
|
+
security vulnerabilities in generated Django code.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import List, Dict, Any, Optional
|
9
|
+
import ast
|
10
|
+
import re
|
11
|
+
|
12
|
+
from pydantic import BaseModel, Field
|
13
|
+
|
14
|
+
from ...models.responses import GeneratedFile
|
15
|
+
from ..base import ServiceDependencies
|
16
|
+
from .models import ValidationIssue
|
17
|
+
|
18
|
+
|
19
|
+
class SecurityValidator(BaseModel):
|
20
|
+
"""Validates security patterns and identifies vulnerabilities."""
|
21
|
+
|
22
|
+
security_rules: Dict[str, Dict[str, Any]] = Field(
|
23
|
+
default_factory=lambda: {
|
24
|
+
"sql_injection": {
|
25
|
+
"description": "Prevent SQL injection vulnerabilities",
|
26
|
+
"severity": "error"
|
27
|
+
},
|
28
|
+
"xss_protection": {
|
29
|
+
"description": "Ensure XSS protection in templates",
|
30
|
+
"severity": "error"
|
31
|
+
},
|
32
|
+
"csrf_protection": {
|
33
|
+
"description": "CSRF protection should be enabled",
|
34
|
+
"severity": "warning"
|
35
|
+
},
|
36
|
+
"secure_settings": {
|
37
|
+
"description": "Security settings should be properly configured",
|
38
|
+
"severity": "warning"
|
39
|
+
},
|
40
|
+
"input_validation": {
|
41
|
+
"description": "User input should be properly validated",
|
42
|
+
"severity": "warning"
|
43
|
+
},
|
44
|
+
"authentication_required": {
|
45
|
+
"description": "Sensitive views should require authentication",
|
46
|
+
"severity": "warning"
|
47
|
+
}
|
48
|
+
},
|
49
|
+
description="Security validation rules"
|
50
|
+
)
|
51
|
+
|
52
|
+
async def validate_security(
|
53
|
+
self,
|
54
|
+
file: GeneratedFile,
|
55
|
+
dependencies: ServiceDependencies
|
56
|
+
) -> List[ValidationIssue]:
|
57
|
+
"""Validate security patterns for a single file."""
|
58
|
+
issues = []
|
59
|
+
|
60
|
+
if file.file_type != "python":
|
61
|
+
return issues
|
62
|
+
|
63
|
+
try:
|
64
|
+
tree = ast.parse(file.content)
|
65
|
+
|
66
|
+
# Apply security checks
|
67
|
+
issues.extend(self._check_sql_injection(file, tree, dependencies))
|
68
|
+
issues.extend(self._check_authentication(file, tree, dependencies))
|
69
|
+
issues.extend(self._check_input_validation(file, tree, dependencies))
|
70
|
+
issues.extend(self._check_dangerous_functions(file, tree, dependencies))
|
71
|
+
|
72
|
+
except SyntaxError:
|
73
|
+
# Skip security validation if syntax is invalid
|
74
|
+
pass
|
75
|
+
except Exception as e:
|
76
|
+
dependencies.log_error(f"Security validation failed for {file.path}", e)
|
77
|
+
|
78
|
+
return issues
|
79
|
+
|
80
|
+
def _check_sql_injection(
|
81
|
+
self,
|
82
|
+
file: GeneratedFile,
|
83
|
+
tree: ast.AST,
|
84
|
+
dependencies: ServiceDependencies
|
85
|
+
) -> List[ValidationIssue]:
|
86
|
+
"""Check for potential SQL injection vulnerabilities."""
|
87
|
+
issues = []
|
88
|
+
|
89
|
+
# Look for raw SQL usage
|
90
|
+
for node in ast.walk(tree):
|
91
|
+
if isinstance(node, ast.Call):
|
92
|
+
# Check for raw() method calls
|
93
|
+
if (isinstance(node.func, ast.Attribute) and
|
94
|
+
node.func.attr == "raw"):
|
95
|
+
issues.append(ValidationIssue(
|
96
|
+
severity="warning",
|
97
|
+
category="security",
|
98
|
+
message="Raw SQL query detected - ensure parameters are properly escaped",
|
99
|
+
file_path=file.path,
|
100
|
+
line_number=node.lineno,
|
101
|
+
rule_id="raw_sql_usage",
|
102
|
+
suggestion="Use Django ORM or parameterized queries instead of raw SQL"
|
103
|
+
))
|
104
|
+
|
105
|
+
# Check for extra() method with potentially unsafe parameters
|
106
|
+
if (isinstance(node.func, ast.Attribute) and
|
107
|
+
node.func.attr == "extra"):
|
108
|
+
issues.append(ValidationIssue(
|
109
|
+
severity="warning",
|
110
|
+
category="security",
|
111
|
+
message="QuerySet.extra() usage detected - ensure SQL is safe",
|
112
|
+
file_path=file.path,
|
113
|
+
line_number=node.lineno,
|
114
|
+
rule_id="extra_sql_usage",
|
115
|
+
suggestion="Prefer Django ORM methods over extra() when possible"
|
116
|
+
))
|
117
|
+
|
118
|
+
# Check for string formatting in SQL-like contexts
|
119
|
+
content = file.content
|
120
|
+
if re.search(r'["\'].*%s.*["\'].*%', content):
|
121
|
+
issues.append(ValidationIssue(
|
122
|
+
severity="warning",
|
123
|
+
category="security",
|
124
|
+
message="String formatting in SQL-like context detected",
|
125
|
+
file_path=file.path,
|
126
|
+
line_number=1,
|
127
|
+
rule_id="string_format_sql",
|
128
|
+
suggestion="Use parameterized queries instead of string formatting"
|
129
|
+
))
|
130
|
+
|
131
|
+
return issues
|
132
|
+
|
133
|
+
def _check_authentication(
|
134
|
+
self,
|
135
|
+
file: GeneratedFile,
|
136
|
+
tree: ast.AST,
|
137
|
+
dependencies: ServiceDependencies
|
138
|
+
) -> List[ValidationIssue]:
|
139
|
+
"""Check for proper authentication patterns."""
|
140
|
+
issues = []
|
141
|
+
|
142
|
+
# Check views for authentication decorators/mixins
|
143
|
+
if "views.py" in file.path:
|
144
|
+
for node in ast.walk(tree):
|
145
|
+
if isinstance(node, ast.FunctionDef):
|
146
|
+
# Check for authentication decorators
|
147
|
+
has_auth_decorator = any(
|
148
|
+
(isinstance(dec, ast.Name) and dec.id == "login_required") or
|
149
|
+
(isinstance(dec, ast.Attribute) and dec.attr == "login_required")
|
150
|
+
for dec in node.decorator_list
|
151
|
+
)
|
152
|
+
|
153
|
+
# Skip views that clearly don't need authentication
|
154
|
+
if not has_auth_decorator and not self._is_public_view(node.name):
|
155
|
+
issues.append(ValidationIssue(
|
156
|
+
severity="info",
|
157
|
+
category="security",
|
158
|
+
message=f"View '{node.name}' may need authentication",
|
159
|
+
file_path=file.path,
|
160
|
+
line_number=node.lineno,
|
161
|
+
rule_id="missing_authentication",
|
162
|
+
suggestion=f"Consider adding @login_required decorator to {node.name} if it handles sensitive data"
|
163
|
+
))
|
164
|
+
|
165
|
+
elif isinstance(node, ast.ClassDef):
|
166
|
+
# Check class-based views for LoginRequiredMixin
|
167
|
+
has_login_mixin = any(
|
168
|
+
isinstance(base, ast.Name) and base.id == "LoginRequiredMixin"
|
169
|
+
for base in node.bases
|
170
|
+
)
|
171
|
+
|
172
|
+
if not has_login_mixin and not self._is_public_view(node.name):
|
173
|
+
issues.append(ValidationIssue(
|
174
|
+
severity="info",
|
175
|
+
category="security",
|
176
|
+
message=f"View class '{node.name}' may need authentication",
|
177
|
+
file_path=file.path,
|
178
|
+
line_number=node.lineno,
|
179
|
+
rule_id="missing_login_mixin",
|
180
|
+
suggestion=f"Consider adding LoginRequiredMixin to {node.name} if it handles sensitive data"
|
181
|
+
))
|
182
|
+
|
183
|
+
return issues
|
184
|
+
|
185
|
+
def _check_input_validation(
|
186
|
+
self,
|
187
|
+
file: GeneratedFile,
|
188
|
+
tree: ast.AST,
|
189
|
+
dependencies: ServiceDependencies
|
190
|
+
) -> List[ValidationIssue]:
|
191
|
+
"""Check for proper input validation."""
|
192
|
+
issues = []
|
193
|
+
|
194
|
+
# Check forms for validation methods
|
195
|
+
if "forms.py" in file.path:
|
196
|
+
for node in ast.walk(tree):
|
197
|
+
if isinstance(node, ast.ClassDef):
|
198
|
+
is_form = any(
|
199
|
+
isinstance(base, ast.Attribute) and
|
200
|
+
base.attr in ["Form", "ModelForm"]
|
201
|
+
for base in node.bases
|
202
|
+
)
|
203
|
+
|
204
|
+
if is_form:
|
205
|
+
# Check for clean methods
|
206
|
+
has_validation = any(
|
207
|
+
isinstance(item, ast.FunctionDef) and
|
208
|
+
(item.name.startswith("clean_") or item.name == "clean")
|
209
|
+
for item in node.body
|
210
|
+
)
|
211
|
+
|
212
|
+
if not has_validation:
|
213
|
+
issues.append(ValidationIssue(
|
214
|
+
severity="info",
|
215
|
+
category="security",
|
216
|
+
message=f"Form '{node.name}' lacks custom validation",
|
217
|
+
file_path=file.path,
|
218
|
+
line_number=node.lineno,
|
219
|
+
rule_id="missing_form_validation",
|
220
|
+
suggestion=f"Consider adding clean_* methods to {node.name} for input validation"
|
221
|
+
))
|
222
|
+
|
223
|
+
return issues
|
224
|
+
|
225
|
+
def _check_dangerous_functions(
|
226
|
+
self,
|
227
|
+
file: GeneratedFile,
|
228
|
+
tree: ast.AST,
|
229
|
+
dependencies: ServiceDependencies
|
230
|
+
) -> List[ValidationIssue]:
|
231
|
+
"""Check for usage of potentially dangerous functions."""
|
232
|
+
issues = []
|
233
|
+
|
234
|
+
dangerous_functions = {
|
235
|
+
"eval": "Use of eval() can execute arbitrary code",
|
236
|
+
"exec": "Use of exec() can execute arbitrary code",
|
237
|
+
"compile": "Use of compile() with user input can be dangerous",
|
238
|
+
"__import__": "Dynamic imports can be security risks"
|
239
|
+
}
|
240
|
+
|
241
|
+
for node in ast.walk(tree):
|
242
|
+
if isinstance(node, ast.Call):
|
243
|
+
func_name = None
|
244
|
+
|
245
|
+
if isinstance(node.func, ast.Name):
|
246
|
+
func_name = node.func.id
|
247
|
+
elif isinstance(node.func, ast.Attribute):
|
248
|
+
func_name = node.func.attr
|
249
|
+
|
250
|
+
if func_name in dangerous_functions:
|
251
|
+
issues.append(ValidationIssue(
|
252
|
+
severity="error",
|
253
|
+
category="security",
|
254
|
+
message=f"Dangerous function '{func_name}' detected: {dangerous_functions[func_name]}",
|
255
|
+
file_path=file.path,
|
256
|
+
line_number=node.lineno,
|
257
|
+
rule_id="dangerous_function",
|
258
|
+
suggestion=f"Avoid using {func_name}() or ensure input is properly sanitized"
|
259
|
+
))
|
260
|
+
|
261
|
+
return issues
|
262
|
+
|
263
|
+
def _is_public_view(self, view_name: str) -> bool:
|
264
|
+
"""Check if a view is likely meant to be public."""
|
265
|
+
public_patterns = [
|
266
|
+
"index", "home", "landing", "about", "contact",
|
267
|
+
"login", "logout", "register", "signup",
|
268
|
+
"public", "api", "health", "status"
|
269
|
+
]
|
270
|
+
|
271
|
+
view_lower = view_name.lower()
|
272
|
+
return any(pattern in view_lower for pattern in public_patterns)
|