import json from typing import Any, Optional from django import forms from django.db import models from django.utils.safestring import mark_safe from django.forms import MultiWidget, Widget JSON_EDITOR_CLASSES = [ "border", "border-base-200", "rounded", "group-[.errors]:border-red-600", "w-full", "dark:border-base-700", "dark:group-[.errors]:border-red-500", ] class JsonEditorWidget(Widget): template_name = 'account/json_editor_field.html' class Media: css = { 'all': ('https://cdn.jsdelivr.net/npm/@json-editor/json-editor@latest/dist/css/jsoneditor.min.css',) } js = ('https://cdn.jsdelivr.net/npm/@json-editor/json-editor@latest/dist/jsoneditor.min.js',) def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None: if attrs is None: attrs = {} # Set default title if not provided if 'title' not in attrs and 'label' in attrs: attrs['title'] = attrs['label'] elif 'title' not in attrs: attrs['title'] = 'JSON Editor' super().__init__(attrs) self.attrs.update({ 'class': ' '.join(JSON_EDITOR_CLASSES), }) def render(self, name, value, attrs=None, renderer=None): if value is None: value = '{}' elif isinstance(value, dict): value = json.dumps(value) attrs = self.build_attrs(self.attrs, attrs) attrs['name'] = name # Ensure the schema is properly passed to the template if 'schema' in self.attrs: attrs['schema'] = self.attrs['schema'] # Pass field name as title if not set if 'title' not in attrs: attrs['title'] = name.replace('_', ' ').title() return super().render(name, value, attrs, renderer) class JsonEditorField(models.JSONField): schema = {} def __init__(self, *args, schema: dict, **kwargs): self.schema = schema super().__init__(*args, **kwargs) def formfield(self, **kwargs): schema = self.schema() if callable(self.schema) else self.schema kwargs.update({ 'widget': JsonEditorWidget(attrs={'schema': json.dumps(schema)}), }) return super(JsonEditorField, self).formfield(**kwargs) def deconstruct(self): name, path, args, kwargs = super().deconstruct() kwargs['schema'] = self.schema return name, path, args, kwargs