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.
Files changed (251) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/__init__.py +24 -8
  3. django_cfg/apps/accounts/admin/activity_admin.py +146 -0
  4. django_cfg/apps/accounts/admin/filters.py +98 -22
  5. django_cfg/apps/accounts/admin/group_admin.py +86 -0
  6. django_cfg/apps/accounts/admin/inlines.py +42 -13
  7. django_cfg/apps/accounts/admin/otp_admin.py +115 -0
  8. django_cfg/apps/accounts/admin/registration_admin.py +173 -0
  9. django_cfg/apps/accounts/admin/resources.py +123 -19
  10. django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
  11. django_cfg/apps/accounts/admin/user_admin.py +362 -0
  12. django_cfg/apps/agents/admin/__init__.py +17 -4
  13. django_cfg/apps/agents/admin/execution_admin.py +204 -183
  14. django_cfg/apps/agents/admin/registry_admin.py +230 -255
  15. django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
  16. django_cfg/apps/agents/core/__init__.py +1 -1
  17. django_cfg/apps/agents/core/django_agent.py +221 -0
  18. django_cfg/apps/agents/core/exceptions.py +14 -0
  19. django_cfg/apps/agents/core/orchestrator.py +18 -3
  20. django_cfg/apps/knowbase/admin/__init__.py +1 -1
  21. django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
  22. django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
  23. django_cfg/apps/knowbase/admin/document_admin.py +269 -262
  24. django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
  25. django_cfg/apps/knowbase/config/settings.py +21 -4
  26. django_cfg/apps/knowbase/views/chat_views.py +3 -0
  27. django_cfg/apps/leads/admin/__init__.py +3 -1
  28. django_cfg/apps/leads/admin/leads_admin.py +235 -35
  29. django_cfg/apps/maintenance/admin/__init__.py +2 -2
  30. django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
  31. django_cfg/apps/maintenance/admin/log_admin.py +143 -61
  32. django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
  33. django_cfg/apps/maintenance/admin/site_admin.py +213 -352
  34. django_cfg/apps/newsletter/admin/__init__.py +29 -2
  35. django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
  36. django_cfg/apps/payments/admin/__init__.py +18 -27
  37. django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
  38. django_cfg/apps/payments/admin/balance_admin.py +166 -632
  39. django_cfg/apps/payments/admin/currencies_admin.py +235 -607
  40. django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
  41. django_cfg/apps/payments/admin/filters.py +83 -3
  42. django_cfg/apps/payments/admin/networks_admin.py +258 -0
  43. django_cfg/apps/payments/admin/payments_admin.py +171 -461
  44. django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
  45. django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
  46. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +105 -34
  47. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
  48. django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
  49. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
  50. django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
  51. django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
  52. django_cfg/apps/payments/middleware/api_access.py +32 -6
  53. django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
  54. django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
  55. django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -0
  56. django_cfg/apps/payments/models/balance.py +12 -0
  57. django_cfg/apps/payments/models/currencies.py +106 -32
  58. django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
  59. django_cfg/apps/payments/services/core/currency_service.py +35 -28
  60. django_cfg/apps/payments/services/core/payment_service.py +1 -1
  61. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  62. django_cfg/apps/payments/services/providers/base.py +95 -39
  63. django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
  64. django_cfg/apps/payments/services/providers/models/base.py +122 -0
  65. django_cfg/apps/payments/services/providers/models/providers.py +87 -0
  66. django_cfg/apps/payments/services/providers/models/universal.py +48 -0
  67. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
  68. django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
  69. django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
  70. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
  71. django_cfg/apps/payments/services/providers/{nowpayments.py → nowpayments/provider.py} +240 -209
  72. django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
  73. django_cfg/apps/payments/services/providers/registry.py +4 -32
  74. django_cfg/apps/payments/services/providers/sync_service.py +277 -0
  75. django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
  76. django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
  77. django_cfg/apps/payments/tasks/__init__.py +39 -0
  78. django_cfg/apps/payments/tasks/types.py +73 -0
  79. django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
  80. django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
  81. django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
  82. django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
  83. django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
  84. django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
  85. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
  86. django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
  87. django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
  88. django_cfg/apps/payments/urls_admin.py +1 -1
  89. django_cfg/apps/payments/views/api/currencies.py +5 -5
  90. django_cfg/apps/payments/views/overview/services.py +2 -2
  91. django_cfg/apps/payments/views/serializers/currencies.py +4 -3
  92. django_cfg/apps/support/admin/__init__.py +10 -1
  93. django_cfg/apps/support/admin/support_admin.py +338 -141
  94. django_cfg/apps/tasks/admin/__init__.py +11 -0
  95. django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
  96. django_cfg/config.py +1 -1
  97. django_cfg/core/config.py +10 -5
  98. django_cfg/core/generation.py +1 -1
  99. django_cfg/management/commands/__init__.py +13 -1
  100. django_cfg/management/commands/app_agent_diagnose.py +470 -0
  101. django_cfg/management/commands/app_agent_generate.py +342 -0
  102. django_cfg/management/commands/app_agent_info.py +308 -0
  103. django_cfg/management/commands/migrate_all.py +9 -3
  104. django_cfg/management/commands/migrator.py +11 -6
  105. django_cfg/management/commands/rundramatiq.py +3 -2
  106. django_cfg/middleware/__init__.py +0 -2
  107. django_cfg/models/api_keys.py +115 -0
  108. django_cfg/modules/django_admin/__init__.py +64 -0
  109. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  110. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  111. django_cfg/modules/django_admin/decorators/display.py +106 -0
  112. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  113. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  114. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  115. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  116. django_cfg/modules/django_admin/models/__init__.py +20 -0
  117. django_cfg/modules/django_admin/models/action_models.py +33 -0
  118. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  119. django_cfg/modules/django_admin/models/base.py +26 -0
  120. django_cfg/modules/django_admin/models/display_models.py +31 -0
  121. django_cfg/modules/django_admin/utils/badges.py +159 -0
  122. django_cfg/modules/django_admin/utils/displays.py +247 -0
  123. django_cfg/modules/django_app_agent/__init__.py +87 -0
  124. django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
  125. django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
  126. django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
  127. django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
  128. django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
  129. django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
  130. django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
  131. django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
  132. django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
  133. django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
  134. django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
  135. django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
  136. django_cfg/modules/django_app_agent/core/__init__.py +33 -0
  137. django_cfg/modules/django_app_agent/core/config.py +300 -0
  138. django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
  139. django_cfg/modules/django_app_agent/models/__init__.py +71 -0
  140. django_cfg/modules/django_app_agent/models/base.py +283 -0
  141. django_cfg/modules/django_app_agent/models/context.py +496 -0
  142. django_cfg/modules/django_app_agent/models/enums.py +481 -0
  143. django_cfg/modules/django_app_agent/models/requests.py +500 -0
  144. django_cfg/modules/django_app_agent/models/responses.py +585 -0
  145. django_cfg/modules/django_app_agent/pytest.ini +6 -0
  146. django_cfg/modules/django_app_agent/services/__init__.py +42 -0
  147. django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
  148. django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
  149. django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
  150. django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
  151. django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
  152. django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
  153. django_cfg/modules/django_app_agent/services/base.py +437 -0
  154. django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
  155. django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
  156. django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
  157. django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
  158. django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
  159. django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
  160. django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
  161. django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
  162. django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
  163. django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
  164. django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
  165. django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
  166. django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
  167. django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
  168. django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
  169. django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
  170. django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
  171. django_cfg/modules/django_app_agent/services/report_service.py +332 -0
  172. django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
  173. django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
  174. django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
  175. django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
  176. django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
  177. django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
  178. django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
  179. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
  180. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
  181. django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
  182. django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
  183. django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
  184. django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
  185. django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
  186. django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
  187. django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
  188. django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
  189. django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
  190. django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
  191. django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
  192. django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
  193. django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
  194. django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
  195. django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
  196. django_cfg/modules/django_app_agent/ui/cli.py +419 -0
  197. django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
  198. django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
  199. django_cfg/modules/django_app_agent/utils/logging.py +360 -0
  200. django_cfg/modules/django_app_agent/utils/validation.py +417 -0
  201. django_cfg/modules/django_currency/__init__.py +2 -2
  202. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  203. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  204. django_cfg/modules/django_currency/core/converter.py +12 -12
  205. django_cfg/modules/django_currency/database/__init__.py +2 -2
  206. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  207. django_cfg/modules/django_llm/llm/client.py +10 -2
  208. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  209. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  210. django_cfg/modules/django_unfold/dashboard.py +14 -13
  211. django_cfg/modules/django_unfold/models/config.py +1 -1
  212. django_cfg/registry/core.py +3 -0
  213. django_cfg/registry/third_party.py +2 -2
  214. django_cfg/template_archive/django_sample.zip +0 -0
  215. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
  216. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/RECORD +223 -117
  217. django_cfg/apps/accounts/admin/activity.py +0 -96
  218. django_cfg/apps/accounts/admin/group.py +0 -17
  219. django_cfg/apps/accounts/admin/otp.py +0 -59
  220. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  221. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  222. django_cfg/apps/accounts/admin/user.py +0 -300
  223. django_cfg/apps/agents/core/agent.py +0 -281
  224. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  225. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  226. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  227. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  228. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  229. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  230. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  231. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  232. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  233. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  234. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  235. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  236. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  237. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  238. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  239. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  240. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  241. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  242. django_cfg/apps/tasks/admin.py +0 -320
  243. django_cfg/middleware/static_nocache.py +0 -55
  244. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  245. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  246. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  247. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  248. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  249. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
  250. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
  251. {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)