问题
I have a model with a field for a json object. This object is used on the site to control some css variables, among other things.
Right now in the admin, I have a text field where a user can save a json object. I'd like to show a form with all the attributes that, upon saving, will generate a json object.
Basically, the user sees, and the data is stored, like this:
{
"name":"hookedonwinter",
"user-id":123,
"basics":{
"height":150,
"weight":150
}
}
And I'd rather have the user see this:
Name: <input field>
User Id: <input field>
Height: <input field>
Weight: <input field>
and the data still be stored in json.
Any guidance would be appreciated. Links to docs that explain this, doubly appreciated.
Thanks!
回答1:
Idea
Basically what you need to do is render your JSON into fields.
- Create field for your model that stores JSON data.
- Create form field
- Create widget that:
- Renders fields as multiple inputs
- 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
- Widgets: https://docs.djangoproject.com/en/1.3/ref/forms/widgets/
- Django code for reference how to create widgets: https://code.djangoproject.com/browser/django/trunk/django/forms/widgets.py
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:

回答2:
I had similar task. I resolved it by creating Django form widget. You can try it for yours applications django-SplitJSONWidget-form
回答3:
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 }}
回答4:
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.
回答5:
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.
回答6:
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.
回答7:
django-submodel may help you, although it cannot represent layered key-value now.
It's a pity to miss such HUGE bounty =p
来源:https://stackoverflow.com/questions/9541924/pseudo-form-in-django-admin-that-generates-a-json-object-on-save