import json from typing import Any, Mapping from django import forms from django.core.files.uploadedfile import UploadedFile from django.forms.renderers import BaseRenderer from django.forms.widgets import PasswordInput from django.utils.datastructures import MultiValueDict from django.utils.safestring import SafeString class PasswordInputRenderValue(PasswordInput): """PasswordInput with render_value=True so constance re-displays the current value.""" def __init__(self, attrs=None): super().__init__(attrs=attrs, render_value=True) class JSONTableWidget(forms.Widget): template_name = "json_table_widget.html" def format_value(self, value: str | dict[str, Any]) -> str | dict[str, Any]: # ty: ignore[invalid-method-override] if isinstance(value, dict): return value try: if isinstance(value, str): value = json.loads(value) except json.JSONDecodeError: value = {} return value def render( self, name: str, value: str | dict[str, Any], attrs: dict[str, Any] | None = None, renderer: BaseRenderer | None = None, ) -> SafeString: value = self.format_value(value) return super().render(name, value, attrs, renderer) def value_from_datadict( self, data: Mapping[str, Any], files: MultiValueDict[str, UploadedFile], name: str, ) -> str | None: json_data = {} try: keys = data.getlist(f"{name}_key") # ty: ignore[unresolved-attribute] values = data.getlist(f"{name}_value") # ty: ignore[unresolved-attribute] for key, value in zip(keys, values, strict=True): if key.strip(): try: json_data[key] = json.loads(value) except (json.JSONDecodeError, ValueError): json_data[key] = value except TypeError: pass return None if not json_data else json.dumps(json_data)