fields.py 2.32 KB

from django import forms
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.db.backends.base.base import BaseDatabaseWrapper

from simplejson import JSONDecodeError
from typing import Any, Dict, Optional, Union
import simplejson


__all__ = ['JSONField', 'JsonFormWidget']


class JsonFormWidget(forms.Textarea):
    template_name = 'comments/json_field_widget.html'

    def get_context(self, name: str, value: Any, attrs: Optional[Dict]) -> Dict[str, Any]:
        ctx = super().get_context(name, value, attrs)
        ctx['widget']['json_value'] = simplejson.dumps(value)
        return ctx

    class Media:
        js = [
            'https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/8.6.3/jsoneditor.js',
            'base_model_s/widgets/json_editor{min}.js',
        ]
        css = {
            'all': [
                'https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/8.6.3/jsoneditor.min.css',
            ]
        }


class JSONField(models.TextField):

    def formfield(self, **kwargs: Any) -> forms.Field:
        kwargs.setdefault('widget', JsonFormWidget)
        return super().formfield(**kwargs)

    @staticmethod
    def _get_json_value(value: str) -> Optional[Union[dict, list]]:

        if not value:
            return None

        if isinstance(value, list):
            return value

        try:
            return simplejson.loads(value)
        except JSONDecodeError:
            return {'data': str(value)}

    def from_db_value(self, value: Any, expression, connection: BaseDatabaseWrapper) -> Any:
        return self._get_json_value(value)

    def pre_save(self, model_instance: models.Model, add: bool) -> str:
        """ Convert Python to Json format before save in database """

        value = super().pre_save(model_instance, add)
        if isinstance(value, str):
            return value

        return simplejson.dumps(value, cls=DjangoJSONEncoder)

    def clean(self, value, model_instance):
        """
            Convert the value's type and run validation. Validation errors
            from _get_json_value() and validate() are propagated. Return the correct
            value if no error is raised.
        """
        value = self._get_json_value(value)

        self.validate(value, model_instance)
        self.run_validators(value)

        return value