[Fixed]-Pseudo-form in Django admin that generates a json object on save

27πŸ‘

βœ…

Idea

Basically what you need to do is render your JSON into fields.

  1. Create field for your model that stores JSON data.
  2. Create form field
  3. Create widget that:
    1. Renders fields as multiple inputs
    2. Takes data from POST/GET and transforms it back into JSON

You can also skip steps 1, 2 by overriding widget for TextField.

Documentation links

Proof of concept

I tried coding this solution and here is solution that worked for me without some edge cases.

fields.py

import json

from django.db import models
from django import forms
from django import utils
from django.utils.translation import ugettext_lazy as _


class JSONEditableField(models.Field):
    description = _("JSON")

    def formfield(self, **kwargs):
        defaults = {'form_class': JSONEditableFormField}
        defaults.update(kwargs)
        return super(JSONEditableField, self).formfield(**defaults)

class JSONEditableWidget(forms.Widget):
    def as_field(self, name, key, value):
        """ Render key, value as field """
        attrs = self.build_attrs(name="%s__%s" % (name, key))
        attrs['value'] = utils.encoding.force_unicode(value)
        return u'%s: <input%s />' % (key, forms.util.flatatt(attrs))

    def to_fields(self, name, json_obj):
        """Get list of rendered fields for json object"""
        inputs = []
        for key, value in json_obj.items():
            if type(value) in (str, unicode, int):
                inputs.append(self.as_field(name, key, value))
            elif type(value) in (dict,):
                inputs.extend(self.to_fields("%s__%s" % (name, key), value))

        return inputs

    def value_from_datadict(self, data, files, name):
        """
        Take values from POST or GET and convert back to JSON..
        Basically what this does is it takes all data variables
        that starts with fieldname__ and converts
        fieldname__key__key = value into json[key][key] = value
        TODO: cleaner syntax?
        TODO: integer values don't need to be stored as string
        """
        json_obj = {}

        separator = "__"

        for key, value in data.items():
            if key.startswith(name+separator):
                dict_key = key[len(name+separator):].split(separator)

                prev_dict = json_obj
                for k in dict_key[:-1]:
                    if prev_dict.has_key(k):
                        prev_dict = prev_dict[k]
                    else:
                        prev_dict[k] = {}
                        prev_dict = prev_dict[k]

                prev_dict[dict_key[-1:][0]] = value

        return json.dumps(prev_dict)


    def render(self, name, value, attrs=None):
        # TODO: handle empty value (render text field?)

        if value is None or value == '':
            value = '{}'

        json_obj = json.loads(value)
        inputs = self.to_fields(name, json_obj)

        # render json as well
        inputs.append(value)

        return utils.safestring.mark_safe(u"<br />".join(inputs))

class JSONEditableFormField(forms.Field):
    widget = JSONEditableWidget

models.py

from django.db import models
from .fields import JSONEditableField

class Foo(models.Model):
    text = models.TextField()
    json = JSONEditableField()

Hope this helps and here is how it looks:
Result

2πŸ‘

I had similar task. I resolved it by creating Django form widget. You can try it for yours applications django-SplitJSONWidget-form

1πŸ‘

Interesting question! I’d like to see good and elegant solution for it πŸ™‚
But it seems to me, that django-admin is not suitable for your task. I’d try to play with Forms. Smth like this:

class HmmForm(forms.Form):
    name = forms.CharField(max_length = 128)
    user_id = forms.IntegerField()
    height = forms.IntegerField()
    weight = forms.IntegerField()


def test(request, pk):
    form = HmmForm()
    if pk > 0:
        hmm = Hmm.objects.get(pk = pk)
        form = HmmForm( initial = {"name": hmm.name} )
    return render_to_response("test/test.html", {"form": form})

And then simple render form in template, as you wish:

{{ form.as_table }} or {{ form.as_p }}

1πŸ‘

It’s looks simple like this:

#Creating custom form 
class MyCoolForm(forms.ModelForm):
    class Meta: 
        model = MyModel
        exclude = ('field_that_stores_json', ) 
    #field_that_shows_json1 = forms.CharField() 
    #field_that_shows_jsons = forms.CharField() 

    def __init__(self, *args, **kwargs):
        #Deserizlize field that stores json here

    def save(self, *args, **kwargs):
        #Serialize fields that shows json here

After all, just set this form as a form for admin.

P.S.: Also you can write your own widget for form, that transforms json object into fields on js level and has textarea underneath.

1πŸ‘

Basically it sounds like you want a custom widget for your text field. The snippet on this page gives an example on how to render json key-value pairs. Even if it doesn’t suit your needs entirely, especially as your nested json adds some complexity, it perhaps can give you some ideas.

As for the pure storage and retrieval of json objects into Python dicts, a few reusable JSONField implementations exist, like this one. You might want to add it to the mix.

πŸ‘€Dirk Eschler

0πŸ‘

Try using YAML as the format for user input, and then deserialize the object and serialize it back to json in the back end. Django already has serializers for that.

πŸ‘€Chris

0πŸ‘

django-submodel may help you, although it cannot represent layered key-value now.

It’s a pity to miss such HUGE bounty =p

πŸ‘€okm

Leave a comment