Coverage for formkit_ninja / code_generation_config.py: 27.50%
52 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-27 04:15 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-27 04:15 +0000
1"""
2Code generation configuration model.
4This model stores database-driven configuration for code generation,
5allowing users to override type mappings and field arguments via Django admin
6instead of creating Python subclasses.
7"""
9from django.db import models
12class CodeGenerationConfig(models.Model):
13 """
14 Database-stored configuration for code generation.
16 Priority cascade:
17 1. Node-specific match (by node_name)
18 2. Options pattern match (by options_pattern)
19 3. FormKit type match (by formkit_type)
20 4. Django settings
21 5. Default converters
23 Example configurations:
24 - FormKit type level: formkit_type="datepicker", django_type="DateField"
25 - Node-specific: node_name="district", django_type="ForeignKey"
26 - Options pattern: options_pattern="$ida(", pydantic_type="int"
27 """
29 # Matching criteria
30 formkit_type = models.CharField(
31 max_length=100,
32 db_index=True,
33 help_text='FormKit type (e.g., "text", "datepicker", "repeater")',
34 )
35 node_name = models.CharField(
36 max_length=256,
37 null=True,
38 blank=True,
39 db_index=True,
40 help_text="Optional: match specific node name (higher priority than formkit_type)",
41 )
42 options_pattern = models.CharField(
43 max_length=256,
44 null=True,
45 blank=True,
46 help_text='Optional: match if options starts with this pattern (e.g., "$ida(")',
47 )
49 # Type overrides
50 pydantic_type = models.CharField(
51 max_length=100,
52 null=True,
53 blank=True,
54 help_text='Override Pydantic type (e.g., "int", "str", "Decimal", "date")',
55 )
56 django_type = models.CharField(
57 max_length=100,
58 null=True,
59 blank=True,
60 help_text='Override Django field type (e.g., "ForeignKey", "IntegerField", "DateField")',
61 )
62 django_args = models.JSONField(
63 default=dict,
64 blank=True,
65 help_text='Django field arguments as JSON dict (e.g., {"null": true, "blank": true, "to": "app.Model"})',
66 )
67 django_positional_args = models.JSONField(
68 default=list,
69 blank=True,
70 help_text='Django field positional arguments as JSON list (e.g., ["auth.User"])',
71 )
73 # Extra configuration
74 extra_imports = models.JSONField(
75 default=list,
76 blank=True,
77 help_text='List of import statements to add to generated files (e.g., ["from decimal import Decimal"])',
78 )
79 validators = models.JSONField(
80 default=list,
81 blank=True,
82 help_text="List of validator strings (e.g., Pydantic field_validator decorators)",
83 )
85 # Priority and status
86 priority = models.IntegerField(
87 default=0,
88 help_text="Matching priority (higher = checked first). Use for fine-grained control.",
89 )
90 is_active = models.BooleanField(
91 default=True,
92 help_text="Set to False to disable this configuration without deleting it",
93 )
95 # Tracking
96 created = models.DateTimeField(auto_now_add=True)
97 updated = models.DateTimeField(auto_now=True)
99 class Meta:
100 ordering = ["-priority", "formkit_type", "node_name"]
101 constraints = [
102 models.UniqueConstraint(
103 fields=["formkit_type", "node_name", "options_pattern"],
104 name="unique_code_gen_config",
105 )
106 ]
107 verbose_name = "Code generation config"
108 verbose_name_plural = "Code generation configs"
110 def __str__(self):
111 parts = [f"{self.formkit_type}"]
112 if self.node_name:
113 parts.append(f"name={self.node_name}")
114 if self.options_pattern:
115 parts.append(f"opts={self.options_pattern}")
116 return " | ".join(parts)
118 def get_django_args_str(self) -> str:
119 """Convert django_args dict and positional args to string format for generated code."""
120 parts = []
122 # Add positional arguments first
123 if self.django_positional_args:
124 for value in self.django_positional_args:
125 if isinstance(value, bool):
126 parts.append(str(value))
127 elif isinstance(value, (int, float)):
128 parts.append(str(value))
129 elif isinstance(value, str):
130 # Handle model references
131 if value.startswith("models.") or "." in value and not value.startswith('"'):
132 parts.append(value)
133 else:
134 parts.append(f'"{value}"')
135 else:
136 parts.append(str(value))
138 # Add keyword arguments
139 if self.django_args:
140 for key, value in self.django_args.items():
141 if isinstance(value, bool):
142 parts.append(f"{key}={str(value)}")
143 elif isinstance(value, (int, float)):
144 parts.append(f"{key}={value}")
145 elif isinstance(value, str):
146 # Handle model references (e.g., "app.Model" or already quoted "'app.Model'")
147 if value.startswith("models.") or "." in value and not value.startswith('"'):
148 # Model reference or function like models.CASCADE
149 parts.append(f"{key}={value}")
150 else:
151 # String value
152 parts.append(f'{key}="{value}"')
153 else:
154 # Other types, convert to string
155 parts.append(f"{key}={value}")
157 return ", ".join(parts)