|
1 | 1 | from typing import Any, Optional
|
2 | 2 |
|
3 | 3 | from django.apps import apps
|
4 |
| -from django.core.exceptions import FieldDoesNotExist |
5 | 4 | from django.db.models import Field, Model
|
6 | 5 | from django.http import HttpRequest
|
7 | 6 | from django.urls import NoReverseMatch, reverse
|
@@ -157,133 +156,164 @@ class PluginDefinitionSerializer(serializers.Serializer):
|
157 | 156 | type = serializers.CharField(help_text="Schema type")
|
158 | 157 | properties = serializers.DictField(help_text="Property definitions")
|
159 | 158 |
|
| 159 | + @staticmethod |
| 160 | + def get_field_type(field: Field) -> str: |
| 161 | + """ |
| 162 | + Convert Django field types to JSON Schema types. |
| 163 | +
|
| 164 | + Args: |
| 165 | + field (Field): Django model field instance |
| 166 | +
|
| 167 | + Returns: |
| 168 | + str: JSON Schema type corresponding to the Django field type |
| 169 | + """ |
| 170 | + field_mapping = { |
| 171 | + "CharField": "string", |
| 172 | + "TextField": "string", |
| 173 | + "URLField": "string", |
| 174 | + "EmailField": "string", |
| 175 | + "IntegerField": "integer", |
| 176 | + "FloatField": "number", |
| 177 | + "DecimalField": "number", |
| 178 | + "BooleanField": "boolean", |
| 179 | + "DateField": "string", |
| 180 | + "DateTimeField": "string", |
| 181 | + "TimeField": "string", |
| 182 | + "FileField": "string", |
| 183 | + "ImageField": "string", |
| 184 | + "JSONField": "object", |
| 185 | + "ForeignKey": "integer", |
| 186 | + } |
| 187 | + return field_mapping.get(field.__class__.__name__, "string") |
| 188 | + |
| 189 | + @staticmethod |
| 190 | + def get_field_format(field: Field) -> Optional[str]: |
| 191 | + """ |
| 192 | + Get the format for specific field types. |
| 193 | +
|
| 194 | + Args: |
| 195 | + field (Field): Django model field instance |
| 196 | +
|
| 197 | + Returns: |
| 198 | + Optional[str]: JSON Schema format string if applicable, None otherwise |
| 199 | + """ |
| 200 | + format_mapping = { |
| 201 | + "URLField": "uri", |
| 202 | + "EmailField": "email", |
| 203 | + "DateField": "date", |
| 204 | + "DateTimeField": "date-time", |
| 205 | + "TimeField": "time", |
| 206 | + "FileField": "uri", |
| 207 | + "ImageField": "uri", |
| 208 | + } |
| 209 | + return format_mapping.get(field.__class__.__name__) |
160 | 210 |
|
161 |
| -def get_field_type(field: Field) -> str: |
162 |
| - """ |
163 |
| - Convert Django field types to JSON Schema types. |
| 211 | + @staticmethod |
| 212 | + def generate_plugin_definitions() -> dict[str, Any]: |
| 213 | + """ |
| 214 | + Generate simple plugin definitions for rendering. |
| 215 | + """ |
| 216 | + definitions = {} |
164 | 217 |
|
165 |
| - Args: |
166 |
| - field (Field): Django model field instance |
| 218 | + for plugin in plugin_pool.get_all_plugins(): |
| 219 | + # Use plugin's serializer_class or create a simple fallback |
| 220 | + serializer_cls = getattr(plugin, "serializer_class", None) |
167 | 221 |
|
168 |
| - Returns: |
169 |
| - str: JSON Schema type corresponding to the Django field type |
170 |
| - """ |
171 |
| - field_mapping = { |
172 |
| - "CharField": "string", |
173 |
| - "TextField": "string", |
174 |
| - "URLField": "string", |
175 |
| - "EmailField": "string", |
176 |
| - "IntegerField": "integer", |
177 |
| - "FloatField": "number", |
178 |
| - "DecimalField": "number", |
179 |
| - "BooleanField": "boolean", |
180 |
| - "DateField": "string", |
181 |
| - "DateTimeField": "string", |
182 |
| - "TimeField": "string", |
183 |
| - "FileField": "string", |
184 |
| - "ImageField": "string", |
185 |
| - "JSONField": "object", |
186 |
| - "ForeignKey": "integer", |
187 |
| - } |
188 |
| - return field_mapping.get(field.__class__.__name__, "string") |
189 |
| - |
190 |
| - |
191 |
| -def get_field_format(field: Field) -> Optional[str]: |
192 |
| - """ |
193 |
| - Get the format for specific field types. |
| 222 | + if not serializer_cls: |
194 | 223 |
|
195 |
| - Args: |
196 |
| - field (Field): Django model field instance |
| 224 | + class DynamicModelSerializer(serializers.ModelSerializer): |
| 225 | + class Meta: |
| 226 | + model = plugin.model |
| 227 | + fields = "__all__" |
197 | 228 |
|
198 |
| - Returns: |
199 |
| - Optional[str]: JSON Schema format string if applicable, None otherwise |
200 |
| - """ |
201 |
| - format_mapping = { |
202 |
| - "URLField": "uri", |
203 |
| - "EmailField": "email", |
204 |
| - "DateField": "date", |
205 |
| - "DateTimeField": "date-time", |
206 |
| - "TimeField": "time", |
207 |
| - "FileField": "uri", |
208 |
| - "ImageField": "uri", |
209 |
| - } |
210 |
| - return format_mapping.get(field.__class__.__name__) |
211 |
| - |
212 |
| - |
213 |
| -def generate_plugin_definitions() -> dict[str, Any]: |
214 |
| - """ |
215 |
| - Generate plugin definitions from registered plugins. |
216 |
| -
|
217 |
| - Returns: |
218 |
| - Dict[str, Any]: A dictionary mapping plugin types to their definitions. |
219 |
| - Each definition contains: |
220 |
| - - title: Human readable name |
221 |
| - - type: Schema type (always "object") |
222 |
| - - properties: Field definitions following JSON Schema format |
223 |
| - - required: List of required field names |
224 |
| - """ |
225 |
| - definitions = {} |
226 |
| - |
227 |
| - excluded_fields = { |
228 |
| - "cmsplugin_ptr", |
229 |
| - "id", |
230 |
| - "parent", |
231 |
| - "creation_date", |
232 |
| - "changed_date", |
233 |
| - "position", |
234 |
| - "language", |
235 |
| - "plugin_type", |
236 |
| - "placeholder", |
237 |
| - } |
238 |
| - |
239 |
| - for plugin in plugin_pool.get_all_plugins(): |
240 |
| - model = plugin.model |
241 |
| - plugin_class = plugin_pool.get_plugin(plugin.__name__) |
242 |
| - |
243 |
| - properties = {} |
244 |
| - required = [] |
245 |
| - |
246 |
| - # Get fields from the model |
247 |
| - for field in model._meta.get_fields(): |
248 |
| - # Skip excluded and relation fields |
249 |
| - if field.name in excluded_fields or field.is_relation: |
250 |
| - continue |
| 229 | + serializer_cls = DynamicModelSerializer |
251 | 230 |
|
252 | 231 | try:
|
253 |
| - model_field = model._meta.get_field(field.name) |
254 |
| - field_def = { |
255 |
| - "type": get_field_type(model_field), |
256 |
| - "description": str(getattr(model_field, "help_text", "") or ""), |
257 |
| - } |
258 |
| - |
259 |
| - # Add format if applicable |
260 |
| - field_format = get_field_format(model_field) |
261 |
| - if field_format: |
262 |
| - field_def["format"] = field_format |
263 |
| - |
264 |
| - properties[field.name] = field_def |
| 232 | + serializer_instance = serializer_cls() |
| 233 | + properties = {} |
| 234 | + |
| 235 | + for field_name, field in serializer_instance.fields.items(): |
| 236 | + # Skip internal CMS fields |
| 237 | + if field_name in base_exclude: |
| 238 | + continue |
| 239 | + |
| 240 | + properties[ |
| 241 | + field_name |
| 242 | + ] = PluginDefinitionSerializer.map_field_to_schema( |
| 243 | + field, field_name |
| 244 | + ) |
265 | 245 |
|
266 |
| - # Add to required fields if not nullable |
267 |
| - if not getattr(model_field, "blank", True): |
268 |
| - required.append(field.name) |
| 246 | + definitions[plugin.__name__] = { |
| 247 | + "name": getattr(plugin, "name", plugin.__name__), |
| 248 | + "title": getattr(plugin, "name", plugin.__name__), |
| 249 | + "type": "object", |
| 250 | + "properties": properties, |
| 251 | + } |
269 | 252 |
|
270 |
| - except FieldDoesNotExist: |
| 253 | + except Exception as e: |
| 254 | + # Skip plugins that fail to process |
| 255 | + input(e) |
271 | 256 | continue
|
272 | 257 |
|
273 |
| - # Add plugin_type to properties and required |
274 |
| - properties["plugin_type"] = { |
275 |
| - "type": "string", |
276 |
| - "const": plugin.__name__, |
277 |
| - "description": "Plugin identifier", |
278 |
| - } |
279 |
| - required.append("plugin_type") |
280 |
| - |
281 |
| - definitions[plugin.__name__] = { |
282 |
| - "title": getattr(plugin_class, "name", plugin.__name__), |
283 |
| - "type": "object", |
284 |
| - "properties": properties, |
285 |
| - "required": required, |
286 |
| - "additionalProperties": False, |
| 258 | + return definitions |
| 259 | + |
| 260 | + @staticmethod |
| 261 | + def map_field_to_schema(field: serializers.Field, field_name: str = "") -> dict: |
| 262 | + """ |
| 263 | + Map DRF field to simple JSON Schema definition for rendering. |
| 264 | +
|
| 265 | + Args: |
| 266 | + field: DRF serializer field instance |
| 267 | + field_name: Name of the field (unused but kept for compatibility) |
| 268 | +
|
| 269 | + Returns: |
| 270 | + dict: Basic JSON Schema definition for the field for TypeScript compatibility |
| 271 | + """ |
| 272 | + |
| 273 | + # Field type mapping for TypeScript compatibility |
| 274 | + field_mapping = { |
| 275 | + "CharField": {"type": "string"}, |
| 276 | + "TextField": {"type": "string"}, |
| 277 | + "URLField": {"type": "string", "format": "uri"}, |
| 278 | + "EmailField": {"type": "string", "format": "email"}, |
| 279 | + "IntegerField": {"type": "integer"}, |
| 280 | + "FloatField": {"type": "number"}, |
| 281 | + "DecimalField": {"type": "number"}, |
| 282 | + "BooleanField": {"type": "boolean"}, |
| 283 | + "DateField": {"type": "string", "format": "date"}, |
| 284 | + "DateTimeField": {"type": "string", "format": "date-time"}, |
| 285 | + "TimeField": {"type": "string", "format": "time"}, |
| 286 | + "FileField": {"type": "string", "format": "uri"}, |
| 287 | + "ImageField": {"type": "string", "format": "uri"}, |
| 288 | + "JSONField": {"type": "object"}, |
| 289 | + "ForeignKey": {"type": "integer"}, |
| 290 | + "PrimaryKeyRelatedField": {"type": "integer"}, |
| 291 | + "ListField": {"type": "array"}, |
| 292 | + "DictField": {"type": "object"}, |
| 293 | + "UUIDField": {"type": "string", "format": "uuid"}, |
287 | 294 | }
|
288 | 295 |
|
289 |
| - return definitions |
| 296 | + # Handle special cases first |
| 297 | + if isinstance(field, serializers.ChoiceField): |
| 298 | + schema = {"type": "string", "enum": list(field.choices.keys())} |
| 299 | + elif hasattr(field, "fields"): # Nested serializer |
| 300 | + schema = {"type": "object"} |
| 301 | + # Extract nested properties |
| 302 | + properties = {} |
| 303 | + for nested_field_name, nested_field in field.fields.items(): |
| 304 | + properties[ |
| 305 | + nested_field_name |
| 306 | + ] = PluginDefinitionSerializer.map_field_to_schema( |
| 307 | + nested_field, nested_field_name |
| 308 | + ) |
| 309 | + if properties: |
| 310 | + schema["properties"] = properties |
| 311 | + else: |
| 312 | + # Use mapping or default to string |
| 313 | + schema = field_mapping.get(field.__class__.__name__, {"type": "string"}) |
| 314 | + |
| 315 | + # Description from help_text |
| 316 | + if getattr(field, "help_text", None): |
| 317 | + schema["description"] = str(field.help_text) |
| 318 | + |
| 319 | + return schema |
0 commit comments