Coverage for formkit_ninja / management / commands / generate_all_schemas.py: 0.00%
77 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-06 04:12 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-06 04:12 +0000
1"""
2Django management command to generate code from all root FormKit nodes in the database.
4This command reads all root nodes (groups/repeaters without parents) from the database
5and generates Django models, Pydantic schemas, admin classes, and API endpoints
6using the per-schema subdirectory structure.
7"""
9from pathlib import Path
11from django.core.management.base import BaseCommand, CommandError
12from django.db import connection
14from formkit_ninja import formkit_schema, models
15from formkit_ninja.parser.formatter import CodeFormatter
16from formkit_ninja.parser.generator import CodeGenerator
17from formkit_ninja.parser.generator_config import GeneratorConfig
18from formkit_ninja.parser.template_loader import DefaultTemplateLoader
21class Command(BaseCommand):
22 """Management command to generate code from all root FormKit nodes."""
24 help = "Generate Django models, schemas, admin, and API code from all root FormKit nodes in the database"
26 def add_arguments(self, parser):
27 """Add command-line arguments."""
28 parser.add_argument(
29 "--app-name",
30 type=str,
31 required=True,
32 help="Name of the Django app (required)",
33 )
34 parser.add_argument(
35 "--output-dir",
36 type=str,
37 required=True,
38 help="Directory where generated code will be written (required)",
39 )
41 def handle(self, *args, **options):
42 """Execute the command."""
43 app_name = options["app_name"]
44 output_dir_str = options["output_dir"]
46 # Validate and convert output directory
47 try:
48 output_dir = Path(output_dir_str)
49 except Exception as e:
50 raise CommandError(f"Invalid output directory: {e}") from e
52 # Validate output directory exists (or can be created)
53 if not output_dir.exists():
54 try:
55 output_dir.mkdir(parents=True, exist_ok=True)
56 self.stdout.write(
57 self.style.SUCCESS(f"Created output directory: {output_dir}"),
58 )
59 except OSError as e:
60 raise CommandError(
61 f"Cannot create output directory {output_dir}: {e}",
62 ) from e
63 elif not output_dir.is_dir():
64 raise CommandError(f"Output path exists but is not a directory: {output_dir}")
66 # Validate database connection
67 try:
68 with connection.cursor() as cursor:
69 cursor.execute("SELECT 1")
70 except Exception as e:
71 raise CommandError(
72 f"Cannot connect to database. Check your DATABASES setting and environment variables: {e}",
73 ) from e
75 # Query all root nodes from database
76 # Root nodes are groups or repeaters that don't have a parent
77 # (i.e., they're not children of other nodes)
78 # Get all nodes that are children (to exclude them)
79 child_node_ids = models.NodeChildren.objects.values_list("child_id", flat=True)
81 # Query for active formkit nodes that are not children
82 # and filter for groups/repeaters in Python (since $ is special in JSONField lookups)
83 all_candidate_nodes = models.FormKitSchemaNode.objects.filter(
84 is_active=True,
85 node_type="$formkit",
86 ).exclude(pk__in=child_node_ids)
88 # Filter in Python for groups and repeaters
89 root_nodes = []
90 for node in all_candidate_nodes:
91 if not node.node or not isinstance(node.node, dict):
92 continue
93 formkit_type = node.node.get("$formkit") or node.node.get("formkit")
94 if formkit_type in ["group", "repeater"]:
95 root_nodes.append(node)
97 if not root_nodes:
98 raise CommandError("No root nodes found in database")
100 root_count = len(root_nodes)
101 self.stdout.write(
102 self.style.SUCCESS(f"Found {root_count} root node(s) in database"),
103 )
105 # Initialize shared generator components
106 template_loader = DefaultTemplateLoader()
107 formatter = CodeFormatter()
109 # Generate code for each root node
110 success_count = 0
111 error_count = 0
113 for root_node in root_nodes:
114 try:
115 # Get schema name from node label or use node ID
116 schema_name = root_node.label or root_node.node.get("name") or str(root_node.id)
117 self.stdout.write(f"Generating code for root node: {schema_name}")
119 # Initialize generator components with schema-specific config
120 config = GeneratorConfig(
121 app_name=app_name,
122 output_dir=output_dir,
123 schema_name=schema_name,
124 )
125 generator = CodeGenerator(
126 config=config,
127 template_loader=template_loader,
128 formatter=formatter,
129 )
131 # Convert root node and its descendants to Pydantic format
132 # Use the node's get_node_values method to build the schema structure
133 root_dict = root_node.get_node_values(recursive=True, options=True)
134 if not root_dict:
135 self.stdout.write(
136 self.style.WARNING(
137 f"Skipping root node {schema_name}: unable to build schema structure",
138 ),
139 )
140 error_count += 1
141 continue
143 # Convert to Pydantic schema format
144 # get_node_values returns a dict, wrap it in a list for FormKitSchema
145 pydantic_schema = formkit_schema.FormKitSchema.parse_obj([root_dict])
147 # Generate code
148 generator.generate(pydantic_schema)
150 self.stdout.write(
151 self.style.SUCCESS(
152 f"Successfully generated code for root node: {schema_name}",
153 ),
154 )
155 success_count += 1
156 except Exception as e:
157 error_count += 1
158 self.stdout.write(
159 self.style.ERROR(
160 f"Error generating code for root node {schema_name}: {e}",
161 ),
162 )
163 # Continue processing remaining schemas
164 continue
166 # Summary
167 self.stdout.write("")
168 if success_count > 0:
169 self.stdout.write(
170 self.style.SUCCESS(
171 f"Successfully generated code for {success_count} root node(s)",
172 ),
173 )
174 if error_count > 0:
175 self.stdout.write(
176 self.style.WARNING(
177 f"Failed to generate code for {error_count} root node(s)",
178 ),
179 )
181 if error_count > 0 and success_count == 0:
182 raise CommandError("Code generation failed for all root nodes")
184 self.stdout.write(
185 self.style.SUCCESS(
186 f"\nCode generation complete. Output directory: {output_dir}",
187 ),
188 )